From d2a818e7b5312275847e2a0a2547961bd1309811 Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Tue, 4 Apr 2023 09:54:48 +0200 Subject: [PATCH 001/258] Created TimeProfileCollectorProvider Meant to keep track of the power transfer from charging stations to vehicles at the charging station --- ...rgerPowerTimeProfileCollectorProvider.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java new file mode 100644 index 00000000000..df2fd19ed7d --- /dev/null +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java @@ -0,0 +1,111 @@ +package org.matsim.contrib.ev.stats; + + +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.matsim.api.core.v01.Id; +import org.matsim.contrib.common.timeprofile.TimeProfileCollector; +import org.matsim.contrib.ev.charging.ChargingEndEvent; +import org.matsim.contrib.ev.charging.ChargingEndEventHandler; +import org.matsim.contrib.ev.charging.ChargingStartEvent; +import org.matsim.contrib.ev.charging.ChargingStartEventHandler; +import org.matsim.contrib.ev.fleet.ElectricFleet; +import org.matsim.contrib.ev.infrastructure.Charger; +import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure; +import org.matsim.core.controler.MatsimServices; +import org.matsim.core.events.MobsimScopeEventHandler; +import org.matsim.core.mobsim.framework.listeners.MobsimListener; +import org.matsim.contrib.common.timeprofile.TimeProfileCollector.ProfileCalculator; +import org.matsim.contrib.common.timeprofile.TimeProfiles; +import org.matsim.vehicles.Vehicle; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; + + +public class ChargerPowerTimeProfileCollectorProvider implements Provider, ChargingStartEventHandler,ChargingEndEventHandler, + MobsimScopeEventHandler { + + private final ElectricFleet evFleet; + private final ChargingInfrastructure chargingInfrastructure; + private final MatsimServices matsimServices; + + @Inject + public ChargerPowerTimeProfileCollectorProvider(ElectricFleet evFleet, ChargingInfrastructure chargingInfrastructure, MatsimServices matsimServices) { + this.evFleet = evFleet; + this.chargingInfrastructure = chargingInfrastructure; + this.matsimServices = matsimServices; + } + + private static final Map,Double> chargerEnergy = new HashMap<>(); + + private static final Map, List>> vehiclesAtCharger = new HashMap<>(); + private static final Map, Double> vehiclesEnergyPreviousTimeStep = new HashMap<>(); + + public static ProfileCalculator createChargerEnergyCalculator (final ChargingInfrastructure chargingInfrastructure) { + List allChargers = new ArrayList<>(chargingInfrastructure.getChargers().values()); + + ImmutableList header = allChargers.stream().map(charger -> charger.getId() + "").collect(toImmutableList()); + + return TimeProfiles.createProfileCalculator(header, () -> allChargers.stream() + .collect(toImmutableMap(charger -> charger.getId() + "", + charger -> chargerEnergy.getOrDefault(charger.getId(), 0.0) + ))); + } + + @Override + public MobsimListener get() { + ProfileCalculator calc = createChargerEnergyCalculator(chargingInfrastructure); + return new TimeProfileCollector(calc,300,"individual_chargers_charge_time_profiles",matsimServices); + } + @Override + public void handleEvent(ChargingStartEvent event) { + vehiclesEnergyPreviousTimeStep.put(event.getVehicleId(), event.getCharge()); + List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); + if (presentVehicles == null) { + ArrayList> firstVehicle = new ArrayList<>(); + firstVehicle.add(event.getVehicleId()); + vehiclesAtCharger.put(event.getChargerId(),firstVehicle); + } else { + presentVehicles.add(event.getVehicleId()); + vehiclesAtCharger.put(event.getChargerId(),presentVehicles); + } + } + @Override + public void handleEvent(ChargingEndEvent event) { + List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); + presentVehicles.remove(event.getVehicleId()); + vehiclesEnergyPreviousTimeStep.remove(event.getVehicleId()); + vehiclesAtCharger.put(event.getChargerId(),presentVehicles); + } + + + + + + + /** Setters and getters to be able to communicate with EvMobsimListener*/ + public Map, List>> getVehiclesAtCharger(){ + return vehiclesAtCharger; + } + public ElectricFleet getEvFleet(){ + return evFleet; + } + public void setChargerEnergy(Id chargerId, double energy){ + chargerEnergy.put(chargerId, energy); + } + + public Map, Double> getVehiclesEnergyPreviousTimeStep() { + return vehiclesEnergyPreviousTimeStep; + } + + public void setVehiclesEnergyPreviousTimeStep(Id vehicleId, double newEnergy) { + vehiclesEnergyPreviousTimeStep.put(vehicleId,newEnergy); + } +} From 321b099af5fcaf4e18ea2e4850fd2664e6db2ff0 Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Tue, 4 Apr 2023 10:00:08 +0200 Subject: [PATCH 002/258] Added MobsimAfterSimStepListener to interact with ChargerPowerTimeProfileCollectorProvider at each chargeTimeStep --- .../contrib/ev/stats/EvMobsimListener.java | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvMobsimListener.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvMobsimListener.java index 92739361cfa..592881d2027 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvMobsimListener.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvMobsimListener.java @@ -29,19 +29,31 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; +import org.matsim.contrib.ev.EvConfigGroup; import org.matsim.contrib.ev.EvUnits; +import org.matsim.contrib.ev.fleet.ElectricFleet; +import org.matsim.contrib.ev.infrastructure.Charger; import org.matsim.core.controler.IterationCounter; import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.mobsim.framework.events.MobsimAfterSimStepEvent; import org.matsim.core.mobsim.framework.events.MobsimBeforeCleanupEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimAfterSimStepListener; import org.matsim.core.mobsim.framework.listeners.MobsimBeforeCleanupListener; import org.matsim.core.utils.misc.Time; +import org.matsim.vehicles.Vehicle; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Objects; import com.google.inject.Inject; /* * created by jbischoff, 26.10.2018 */ -public class EvMobsimListener implements MobsimBeforeCleanupListener { +public class EvMobsimListener implements MobsimBeforeCleanupListener, MobsimAfterSimStepListener { @Inject EnergyConsumptionCollector energyConsumptionCollector; @@ -53,7 +65,10 @@ public class EvMobsimListener implements MobsimBeforeCleanupListener { IterationCounter iterationCounter; @Inject Network network; - + @Inject + EvConfigGroup evConfigGroup; + @Inject + ChargerPowerTimeProfileCollectorProvider chargerPowerTimeProfileCollectorProvider; @Inject EvMobsimListener() { } // to make the constructor non-public. @@ -85,4 +100,32 @@ public void notifyMobsimBeforeCleanup(MobsimBeforeCleanupEvent event) { throw new RuntimeException(e); } } + + /** After each chargeTimeStep, the power transfer from each station to the vehicles present at that station (vehiclesAtCharger) is calculated. + * To do so, the energy at the previous chargeTimeStep needs to be known (vehiclesEnergyPreviousTimeStep). The power is then passed in to the + * chargerPowerTimeProfileCollectorProvider. */ + @Override + public void notifyMobsimAfterSimStep(MobsimAfterSimStepEvent event) { + if ((event.getSimulationTime() + 1) % evConfigGroup.chargeTimeStep == 0) { + ElectricFleet evFleet = chargerPowerTimeProfileCollectorProvider.getEvFleet(); + Map, List>> vehiclesAtCharger = chargerPowerTimeProfileCollectorProvider.getVehiclesAtCharger(); + Map, Double> vehiclesEnergyPreviousTimeStep = chargerPowerTimeProfileCollectorProvider.getVehiclesEnergyPreviousTimeStep(); + vehiclesAtCharger.forEach((charger, vehicleList) -> { + if (!vehicleList.isEmpty()) { + double energy = vehicleList.stream().mapToDouble(vehicleId -> EvUnits.J_to_kWh((Objects.requireNonNull(evFleet.getElectricVehicles().get(vehicleId)).getBattery() + .getCharge() - vehiclesEnergyPreviousTimeStep.get(vehicleId))*(3600.0/evConfigGroup.chargeTimeStep))).sum(); + if (!Double.isNaN(energy) && !(energy == 0.0)) { + chargerPowerTimeProfileCollectorProvider.setChargerEnergy(charger, energy); + vehicleList.forEach(vehicleId -> chargerPowerTimeProfileCollectorProvider + .setVehiclesEnergyPreviousTimeStep(vehicleId, Objects.requireNonNull(evFleet.getElectricVehicles().get(vehicleId)).getBattery().getCharge())); + } else { + chargerPowerTimeProfileCollectorProvider.setChargerEnergy(charger, 0.0); + } + } else { + chargerPowerTimeProfileCollectorProvider.setChargerEnergy(charger, 0.0); + } + }); + + } + } } From 27bace8555632a0dbedb7420cd0515059faa901a Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Tue, 4 Apr 2023 10:01:43 +0200 Subject: [PATCH 003/258] Added ChargerPowerTimeProfileCollectorProvider to the EvStatsModule --- .../main/java/org/matsim/contrib/ev/stats/EvStatsModule.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java index 8c0e0639b82..c220847056e 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java @@ -56,6 +56,9 @@ protected void configureQSim() { bind(EnergyConsumptionCollector.class).asEagerSingleton(); addMobsimScopeEventHandlerBinding().to(EnergyConsumptionCollector.class); // add more time profiles if necessary + bind(ChargerPowerTimeProfileCollectorProvider.class).asEagerSingleton(); + addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider(ChargerPowerTimeProfileCollectorProvider.class); + addMobsimScopeEventHandlerBinding().to(ChargerPowerTimeProfileCollectorProvider.class); } } }); From 30d8cdcf3555a0f74f6d19213de9eaeb4f1a6570 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Wed, 26 Apr 2023 20:53:40 +0200 Subject: [PATCH 004/258] Setup railsim contrib module --- contribs/pom.xml | 1 + contribs/railsim/pom.xml | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 contribs/railsim/pom.xml diff --git a/contribs/pom.xml b/contribs/pom.xml index ea0e3c1a58e..0179b830cff 100644 --- a/contribs/pom.xml +++ b/contribs/pom.xml @@ -90,6 +90,7 @@ parking protobuf pseudosimulation + railsim roadpricing sbb-extensions shared_mobility diff --git a/contribs/railsim/pom.xml b/contribs/railsim/pom.xml new file mode 100644 index 00000000000..5093ee41910 --- /dev/null +++ b/contribs/railsim/pom.xml @@ -0,0 +1,15 @@ + + + + contrib + org.matsim + 16.0-SNAPSHOT + + + 4.0.0 + org.matsim.contrib + railsim + + From 2f3247a72d6cf2de33ee9e952a462a3ad55a2b4f Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Thu, 27 Apr 2023 15:57:52 +0200 Subject: [PATCH 005/258] Set group id of contrib --- contribs/railsim/pom.xml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/contribs/railsim/pom.xml b/contribs/railsim/pom.xml index 5093ee41910..959ebfe9106 100644 --- a/contribs/railsim/pom.xml +++ b/contribs/railsim/pom.xml @@ -7,9 +7,21 @@ org.matsim 16.0-SNAPSHOT - 4.0.0 - org.matsim.contrib + ch.sbb.matsim.contrib railsim - + + + org.matsim.contrib + otfvis + 16.0-SNAPSHOT + compile + + + org.matsim.contrib + signals + 16.0-SNAPSHOT + compile + + From 3681802a5c3cbb01e9b636fce8ad1a0217b0898e Mon Sep 17 00:00:00 2001 From: Ihab Kaddoura Date: Thu, 27 Apr 2023 16:15:17 +0200 Subject: [PATCH 006/258] Add railsim prototype - Core: Config group, railsim events and handlers, train signal and spatial train dimension logic. - Prepare: Split transit links and adjust network to schedule before sim. - Analysis: Convert train events to default events. - Demo: Visualize test results with OTFVis. --- .../AdaptiveTrainSignalsControler.java | 385 ++++++ .../prototype/LinkUsageVisualizer.java | 153 +++ .../railsim/prototype/RailsimConfigGroup.java | 192 +++ .../prototype/RailsimLinkSpeedCalculator.java | 12 + .../RailsimLinkSpeedCalculatorImpl.java | 198 +++ .../prototype/RailsimSignalsQSimModule.java | 35 + .../railsim/prototype/RailsimUtils.java | 138 +++ .../contrib/railsim/prototype/RunRailsim.java | 158 +++ .../contrib/railsim/prototype/SignalInfo.java | 134 +++ .../prototype/SpatialTrainDimension.java | 257 ++++ .../railsim/prototype/TrainEntersLink.java | 56 + .../TrainEntersLinkEventHandler.java | 11 + .../railsim/prototype/TrainLeavesLink.java | 55 + .../TrainLeavesLinkEventHandler.java | 10 + .../prototype/TrainPathEntersLink.java | 56 + .../TrainPathEntersLinkEventHandler.java | 10 + .../railsim/prototype/TrainStatistics.java | 195 +++ .../ConvertTrainEventsToDefaultEvents.java | 47 + .../analysis/TrainEventsHandler.java | 81 ++ .../prototype/analysis/TrainEventsReader.java | 112 ++ .../analysis/TransitEventHandler.java | 100 ++ .../railsim/prototype/demo/RunDemo.java | 72 ++ .../prepare/AdjustNetworkToSchedule.java | 286 +++++ .../prepare/DistributeCapacities.java | 99 ++ .../prototype/prepare/SplitTransitLinks.java | 250 ++++ .../QRailsimSignalsNetworkFactory.java | 64 + .../RunRailsimAdvancedCorridorTest.java | 106 ++ .../railsim/prototype/RunRailsimTest.java | 1066 +++++++++++++++++ .../prepare/AdjustNetworkToScheduleTest.java | 91 ++ .../prepare/DistributeCapacitiesTest.java | 58 + .../prepare/SplitTransitLinksTest.java | 89 ++ .../test0/config.xml | 40 + .../test0/network_trainCorridor.xml | 329 +++++ .../test0/network_trainCorridor.xml.gz | Bin 0 -> 4838 bytes .../test0/transitSchedule.xml | 153 +++ .../test0/transitVehicles.xml | 42 + .../prototype/RunRailsimTest/test0/config.xml | 39 + .../RunRailsimTest/test0/trainNetwork.xml | 57 + .../RunRailsimTest/test0/transitSchedule.xml | 45 + .../RunRailsimTest/test0/transitVehicles.xml | 35 + .../prototype/RunRailsimTest/test1/config.xml | 39 + .../RunRailsimTest/test1/trainNetwork.xml | 73 ++ .../RunRailsimTest/test1/transitSchedule.xml | 58 + .../RunRailsimTest/test1/transitVehicles.xml | 35 + .../RunRailsimTest/test10/config.xml | 38 + .../RunRailsimTest/test10/trainNetwork.xml | 84 ++ .../RunRailsimTest/test10/transitSchedule.xml | 52 + .../RunRailsimTest/test10/transitVehicles.xml | 29 + .../RunRailsimTest/test11/config.xml | 39 + .../RunRailsimTest/test11/trainNetwork.xml | 79 ++ .../RunRailsimTest/test11/transitSchedule.xml | 76 ++ .../RunRailsimTest/test11/transitVehicles.xml | 33 + .../RunRailsimTest/test12/config.xml | 39 + .../RunRailsimTest/test12/trainNetwork.xml | 79 ++ .../RunRailsimTest/test12/transitSchedule.xml | 76 ++ .../RunRailsimTest/test12/transitVehicles.xml | 33 + .../RunRailsimTest/test13/config.xml | 39 + .../RunRailsimTest/test13/trainNetwork.xml | 104 ++ .../RunRailsimTest/test13/transitSchedule.xml | 56 + .../RunRailsimTest/test13/transitVehicles.xml | 48 + .../RunRailsimTest/test14/config.xml | 39 + .../RunRailsimTest/test14/trainNetwork.xml | 85 ++ .../RunRailsimTest/test14/transitSchedule.xml | 46 + .../RunRailsimTest/test14/transitVehicles.xml | 28 + .../prototype/RunRailsimTest/test2/config.xml | 39 + .../RunRailsimTest/test2/trainNetwork.xml | 85 ++ .../RunRailsimTest/test2/transitSchedule.xml | 64 + .../RunRailsimTest/test2/transitVehicles.xml | 54 + .../prototype/RunRailsimTest/test3/config.xml | 39 + .../RunRailsimTest/test3/trainNetwork.xml | 123 ++ .../RunRailsimTest/test3/transitSchedule.xml | 67 ++ .../RunRailsimTest/test3/transitVehicles.xml | 35 + .../prototype/RunRailsimTest/test4/config.xml | 38 + .../RunRailsimTest/test4/trainNetwork.xml.gz | Bin 0 -> 159748 bytes .../test4/transitSchedule.xml.gz | Bin 0 -> 70575 bytes .../test4/transitVehicles.xml.gz | Bin 0 -> 1366 bytes .../prototype/RunRailsimTest/test5/config.xml | 39 + .../RunRailsimTest/test5/trainNetwork.xml | 96 ++ .../RunRailsimTest/test5/transitSchedule.xml | 62 + .../RunRailsimTest/test5/transitVehicles.xml | 36 + .../prototype/RunRailsimTest/test6/config.xml | 39 + .../RunRailsimTest/test6/trainNetwork.xml | 196 +++ .../RunRailsimTest/test6/transitSchedule.xml | 65 + .../RunRailsimTest/test6/transitVehicles.xml | 28 + .../prototype/RunRailsimTest/test7/config.xml | 39 + .../RunRailsimTest/test7/trainNetwork.xml | 178 +++ .../RunRailsimTest/test7/transitSchedule.xml | 47 + .../RunRailsimTest/test7/transitVehicles.xml | 50 + .../prototype/RunRailsimTest/test8/config.xml | 39 + .../RunRailsimTest/test8/trainNetwork.xml | 423 +++++++ .../RunRailsimTest/test8/transitSchedule.xml | 105 ++ .../RunRailsimTest/test8/transitVehicles.xml | 29 + .../prototype/RunRailsimTest/test9/config.xml | 39 + .../RunRailsimTest/test9/trainNetwork.xml | 423 +++++++ .../RunRailsimTest/test9/transitSchedule.xml | 104 ++ .../RunRailsimTest/test9/transitVehicles.xml | 29 + .../test0/config.xml | 44 + .../test0/transitNetwork.xml.gz | Bin 0 -> 97116 bytes .../test0/transitSchedule.xml.gz | Bin 0 -> 72093 bytes .../test0/transitVehicles.xml.gz | Bin 0 -> 640 bytes .../DistributeCapacitiesTest/test0/config.xml | 44 + .../test0/transitNetwork.xml.gz | Bin 0 -> 97116 bytes .../test0/transitSchedule.xml.gz | Bin 0 -> 72093 bytes .../test0/transitVehicles.xml.gz | Bin 0 -> 640 bytes .../SplitTransitLinksTest/test0/config.xml | 44 + .../test0/trainNetwork.xml | 57 + .../test0/transitSchedule.xml | 34 + .../test0/transitVehicles.xml | 35 + 108 files changed, 9491 insertions(+) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java create mode 100644 contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/trainNetwork.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitSchedule.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitVehicles.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitNetwork.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitSchedule.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitVehicles.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitNetwork.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitSchedule.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitVehicles.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java new file mode 100644 index 00000000000..72ae8a3751e --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java @@ -0,0 +1,385 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.LineSegment; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent; +import org.matsim.core.mobsim.framework.events.MobsimInitializedEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener; +import org.matsim.core.mobsim.framework.listeners.MobsimInitializedListener; +import org.matsim.core.mobsim.qsim.interfaces.Netsim; +import org.matsim.core.mobsim.qsim.interfaces.SignalGroupState; +import org.matsim.core.mobsim.qsim.interfaces.SignalizeableItem; +import org.matsim.core.utils.io.IOUtils; +import org.matsim.vehicles.Vehicle; + +import javax.inject.Inject; +import java.io.BufferedWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This class places a simple adaptive signal on each link and controls them. + *

+ * A signal state for a link-to-link direction is changed depending on the available capacities on the next link. + * The signal control also makes sure that trains are not able to enter the same link in the same time step. + * + * @author Ihab Kaddoura + */ +public class AdaptiveTrainSignalsControler implements IterationEndsListener, MobsimInitializedListener, MobsimBeforeSimStepListener, TrainPathEntersLinkEventHandler, TrainLeavesLinkEventHandler { + + private final boolean printOutputs = true; + + private static final Logger log = LogManager.getLogger(AdaptiveTrainSignalsControler.class); + + @Inject + private Scenario scenario; + + private final Map, HashSet>> linkId2touchingVehicles = new HashMap<>(); + private final HashMap, SignalInfo> toLink2signalinfo = new HashMap<>(); + private final HashMap, SignalizeableItem> linkId2signal = new HashMap<>(); + + private final List signalInfosToVisualize = new ArrayList<>(); + private Map> nextCrossingTime2nodes = new HashMap<>(); + + private boolean atLeastOneLinkWithOppositeDirectionLink = false; + private boolean atLeastOneLinkWithMinimumTrainHeadway = false; + + @Override + public void notifyMobsimInitialized(MobsimInitializedEvent e) { + + Netsim mobsim = (Netsim) e.getQueueSimulation(); + + for (Link link : this.scenario.getNetwork().getLinks().values()) { + + // place a signal on all links + SignalizeableItem signal = (SignalizeableItem) mobsim.getNetsimNetwork().getNetsimLink(link.getId()); + signal.setSignalized(true); + this.linkId2signal.put(link.getId(), signal); + + // initialize ingoing links for each link + SignalInfo signalInfo = new SignalInfo(link, this.scenario.getNetwork()); + this.toLink2signalinfo.put(link.getId(), signalInfo); + + // initialize link utilization + this.linkId2touchingVehicles.put(link.getId(), new HashSet<>()); + + if (RailsimUtils.getOppositeDirectionLink(link, this.scenario.getNetwork()) != null) { + atLeastOneLinkWithOppositeDirectionLink = true; + } + + if (RailsimUtils.getMinimumTrainHeadwayTime(link) > 0.) { + atLeastOneLinkWithMinimumTrainHeadway = true; + } + + } + } + + @Override + public void reset(int iteration) { + if (iteration > 0) throw new RuntimeException("Running more than 1 iteration. Aborting..."); + } + + @Override + public void handleEvent(TrainLeavesLink event) { + + Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); + Id vehicleId = event.getVehicleId(); + +// log.warn("-"); +// log.warn("Train " + vehicleId + " leaves link " + link.getId() + " (time: " + event.getTime() + ")"); + + // bookkeeping: this train is no longer 'on' the link + this.linkId2touchingVehicles.get(link.getId()).remove(vehicleId); + + // 1) capacity condition + + int trainCapacity = RailsimUtils.getTrainCapacity(link); + + int vehCount = 0; + if (this.linkId2touchingVehicles.get(link.getId()) != null) { + vehCount = this.linkId2touchingVehicles.get(link.getId()).size(); + } + + if (vehCount == trainCapacity - 1) { + // There is capacity available on the link, update the signal condition for all inLinks... + this.toLink2signalinfo.get(link.getId()).changeCondition(SignalInfo.SignalCondition.linkCapacity, SignalGroupState.GREEN); + + // also make sure the from node is green for all other links + for (Link linkWithSameFromNode : link.getFromNode().getOutLinks().values()) { + if (linkWithSameFromNode != link) { + // not the same link + if (!isBlocked(linkWithSameFromNode)) { + this.toLink2signalinfo.get(linkWithSameFromNode.getId()).changeCondition(SignalInfo.SignalCondition.nodeCapacity, SignalGroupState.GREEN); + } + } + } + + } else { + // do nothing + } + + // 2) opposite direction + + if (vehCount == 0 && RailsimUtils.getOppositeDirectionLink(link, this.scenario.getNetwork()) != null) { + Id oppositeDirectionLink = this.toLink2signalinfo.get(link.getId()).getOppositeLink().getId(); + this.toLink2signalinfo.get(oppositeDirectionLink).changeCondition(SignalInfo.SignalCondition.oppositeDirection, SignalGroupState.GREEN); + } + + // 3) minimal train headway (for the toNode of the link) + + double minimumTime = RailsimUtils.getMinimumTrainHeadwayTime(link); + double earliestLeaveTime = event.getTime() + minimumTime; + + if (earliestLeaveTime > event.getTime()) { + for (Link toLink : link.getToNode().getOutLinks().values()) { + this.toLink2signalinfo.get(toLink.getId()).changeCondition(SignalInfo.SignalCondition.nodeMinimumHeadway, SignalGroupState.RED); + } + + if (this.nextCrossingTime2nodes.get(earliestLeaveTime) == null) { + Set nodes = new HashSet<>(); + nodes.add(link.getToNode()); + this.nextCrossingTime2nodes.put(earliestLeaveTime, nodes); + } else { + this.nextCrossingTime2nodes.get(earliestLeaveTime).add(link.getToNode()); + } + +// log.warn("Node " + link.getToNode().getId() + " blocked until " + earliestLeaveTime); + } + + } + + @Override + public void handleEvent(TrainPathEntersLink event) { + + Id vehicleId = event.getVehicleId(); + Id linkId = event.getLinkId(); + Link link = this.scenario.getNetwork().getLinks().get(linkId); + +// log.warn("-------"); +// log.warn("Fahrweg of " + vehicleId + " enters link " + linkId); + + // bookkeeping: register the train 'on' the link + this.linkId2touchingVehicles.get(linkId).add(vehicleId); + + // 1) capacity condition + + if (isBlocked(link)) { + this.toLink2signalinfo.get(link.getId()).changeCondition(SignalInfo.SignalCondition.linkCapacity, SignalGroupState.RED); + + // also make sure the from node is blocked for all other links + for (Link linkWithSameFromNode : link.getFromNode().getOutLinks().values()) { + if (linkWithSameFromNode != link) { + // not the same link + this.toLink2signalinfo.get(linkWithSameFromNode.getId()).changeCondition(SignalInfo.SignalCondition.nodeCapacity, SignalGroupState.RED); + + } + } + } + + // 2) opposite direction + + if (RailsimUtils.getOppositeDirectionLink(link, this.scenario.getNetwork()) != null) { + Id oppositeDirectionLink = this.toLink2signalinfo.get(link.getId()).getOppositeLink().getId(); + this.toLink2signalinfo.get(oppositeDirectionLink).changeCondition(SignalInfo.SignalCondition.oppositeDirection, SignalGroupState.RED); + } + } + + /** + * @param link + * @return + */ + private boolean isBlocked(Link link) { + int trainCapacity = RailsimUtils.getTrainCapacity(link); + + int vehCount = 0; + if (this.linkId2touchingVehicles.get(link.getId()) != null) { + vehCount = this.linkId2touchingVehicles.get(link.getId()).size(); + } + if (vehCount == trainCapacity) { + return true; + } else if (vehCount < trainCapacity) { + return false; + } else { + log.warn("+++++++++++++++++++++++++++++++++++++++++++++++++++++"); + log.warn("Link: " + link.getId()); + log.warn("Vehicles touching this link: " + this.linkId2touchingVehicles.get(link.getId())); + log.warn("trainCapacity: " + trainCapacity); + log.warn("attributes: " + link.getAttributes().toString()); + log.warn("+++++++++++++++++++++++++++++++++++++++++++++++++++++"); + throw new RuntimeException("More vehicles than allowed on link " + link.getId()); + } + } + + private void storeSignalStateInfo(double simulationTime, Link fromLink, Link toLink, SignalGroupState state) { + + // I want the signals to be visualized right before the end of the from link... + LineSegment ls = new LineSegment(fromLink.getFromNode().getCoord().getX(), fromLink.getFromNode().getCoord().getY(), fromLink.getToNode().getCoord().getX(), fromLink.getToNode().getCoord().getY()); + Coordinate point = ls.pointAlong(0.8); + + this.signalInfosToVisualize.add(simulationTime + ";" + point.getX() + ";" + point.getY() + ";" + toLink.getCoord().getX() + ";" + toLink.getCoord().getY() + ";" + state.toString()); + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + if (printOutputs) { + printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visSignalStates.csv", this.signalInfosToVisualize); + } + } + + private void printSignalInfos(String outputFile, List strings) { + + BufferedWriter writer = IOUtils.getBufferedWriter(outputFile); + + try { + writer.write("time;X;Y;toLinkX;toLinkY;state"); + writer.newLine(); + for (String line : strings) { + writer.write(line); + writer.newLine(); + } + writer.close(); + + log.info("Text info written to file."); + } catch (Exception e) { + log.warn("Text info not written to file."); + } + } + + @Override + public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) { + + double time = e.getSimulationTime(); + + // see if enough time has passed and we can update the headway condition + if (atLeastOneLinkWithMinimumTrainHeadway) updateHeadwaySignalCondition(time); + + // update conflicts between toLinks + if (atLeastOneLinkWithOppositeDirectionLink) updateConflicts(); + + // update all signal states based on the current conditions + updateSignalStates(time); + } + + /** + * if there is a conflicting toLink, set the conflictingOppositeLink condition to Red for one of the conflicting toLinks + */ + private void updateConflicts() { + // update conflicting opposite links + this.toLink2signalinfo.values().stream().forEach(signalInfo -> { + if (signalInfo.getOppositeLink() != null) { + if (signalInfo.isConsiderInNextTimeStep()) { + signalInfo.changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.GREEN); + this.toLink2signalinfo.get(signalInfo.getOppositeLink().getId()).changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.RED); + // change for next time step + signalInfo.setConsiderInNextTimeStep(false); + + } else { + signalInfo.changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.RED); + this.toLink2signalinfo.get(signalInfo.getOppositeLink().getId()).changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.GREEN); + + // change for next time step + signalInfo.setConsiderInNextTimeStep(true); + } + } + }); + } + + /** + * Iterate through all signals and see where all conditions are green... + *

+ * - if there is only one inLink, directly switch to green (there are no potential conflicts between any inLinks) + * - if there is more than one inLink, make sure there is only from link green (otherwise vehicles may enter a link in the same time step...) + * + * @param time + */ + private void updateSignalStates(double time) { + + // TODO: improve performance + // with parallel() I get some weird test failures... + // parallel().forEachOrdered() runs without any problems but does not improve performance + + this.toLink2signalinfo.values().stream().forEach(signalInfo -> { + + boolean demandOnAFromLink = false; + for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { + if (this.linkId2touchingVehicles.get(fromLinkInfo.getId()).size() > 0) { + demandOnAFromLink = true; + break; + } + } + + if (demandOnAFromLink) { + if (signalInfo.allConditionsGreen()) { + if (signalInfo.getFromLink2fromLinkInfo().size() <= 1) { + // no potential conflict, set for all ingoing links to green + for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { + switchSignalState(time, fromLinkInfo, signalInfo.getToLink(), SignalGroupState.GREEN); + } + } else { + // potential conflict, only set to green for one inlink + Link fromLink = signalInfo.getNextFromLink(); + switchSignalState(time, fromLink, signalInfo.getToLink(), SignalGroupState.GREEN); + + // and switch all other fromLinks to red. + for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { + if (fromLinkInfo != fromLink) { + switchSignalState(time, fromLinkInfo, signalInfo.getToLink(), SignalGroupState.RED); + } + } + } + } else { +// log.info("At least one Red condition. Setting to red: from link: " + fromLinkInfo.getFromLink().getId() + " --- to link: " + signalInfo.getToLink().getId()); + for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { + switchSignalState(time, fromLinkInfo, signalInfo.getToLink(), SignalGroupState.RED); + } + } + } + + }); + } + + /** + * @param time + */ + private void updateHeadwaySignalCondition(double time) { + if (this.nextCrossingTime2nodes.get(time) != null) { + for (Node node : this.nextCrossingTime2nodes.get(time)) { + for (Link toLink : node.getOutLinks().values()) { + this.toLink2signalinfo.get(toLink.getId()).changeCondition(SignalInfo.SignalCondition.nodeMinimumHeadway, SignalGroupState.GREEN); + } + } + this.nextCrossingTime2nodes.remove(time); + } + } + + private void switchSignalState(double time, Link fromLink, Link toLink, SignalGroupState state) { + boolean previousSignalStateIsGreen = this.linkId2signal.get(fromLink.getId()).hasGreenForToLink(toLink.getId()); + + if (previousSignalStateIsGreen && state == SignalGroupState.GREEN) { + // was already green, nothing to do! +// log.warn("Signal was already green and was then requested to be changed to green again..." + fromLink.getId() + " -> " + toLink.getId()); + } else if (!previousSignalStateIsGreen && state == SignalGroupState.RED) { + // was already red, nothing to do! +// log.warn("Signal was already red and was then requested to be changed to red again..." + fromLink.getId() + " -> " + toLink.getId()); + } else { + // the signal state changes from green to red or from red to green +// log.info("change signal state: " + fromLink.getId() + " --> " + toLink.getId() + ":" + state.toString()); + this.linkId2signal.get(fromLink.getId()).setSignalStateForTurningMove(state, toLink.getId()); + if (printOutputs) this.storeSignalStateInfo(time, fromLink, toLink, state); + } + + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java new file mode 100644 index 00000000000..39451ba305a --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java @@ -0,0 +1,153 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.mobsim.qsim.interfaces.SignalGroupState; +import org.matsim.core.utils.io.IOUtils; +import org.matsim.vehicles.Vehicle; + +import javax.inject.Inject; +import java.io.BufferedWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * This class writes out data to visualize which link is touched or even blocked by trains. + * + * @author Ihab Kaddoura + */ +public class LinkUsageVisualizer implements IterationEndsListener, TrainPathEntersLinkEventHandler, TrainLeavesLinkEventHandler, TrainEntersLinkEventHandler { + + private static final Logger log = LogManager.getLogger(LinkUsageVisualizer.class); + + @Inject + private Scenario scenario; + + private final Map, HashSet>> linkId2touchingTrainPaths = new HashMap<>(); + private final Map, HashSet>> linkId2touchingTrains = new HashMap<>(); + + private final List blockedLinksInfosToVisualize = new ArrayList<>(); + private final List touchedLinksByTrainInfosToVisualize = new ArrayList<>(); + private final List touchedLinksCountToVisualize = new ArrayList<>(); + private final List touchedLinksToVisualize = new ArrayList<>(); + + @Override + public void handleEvent(TrainPathEntersLink event) { + + HashSet> touchingTrainPaths = this.linkId2touchingTrainPaths.getOrDefault(event.getLinkId(), new HashSet<>()); + touchingTrainPaths.add(event.getVehicleId()); + this.linkId2touchingTrainPaths.put(event.getLinkId(), touchingTrainPaths); + + Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); + + this.touchedLinksCountToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + touchingTrainPaths.size()); + + if (touchingTrainPaths.size() == 1) { + this.touchedLinksToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.RED.toString()); + } + + if (isBlocked(link)) { + // the link is blocked + this.blockedLinksInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.RED.toString()); + + } + } + + @Override + public void handleEvent(TrainEntersLink event) { + + HashSet> touchingTrains = this.linkId2touchingTrains.getOrDefault(event.getLinkId(), new HashSet<>()); + touchingTrains.add(event.getVehicleId()); + this.linkId2touchingTrains.put(event.getLinkId(), touchingTrains); + + Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); + + if (touchingTrains.size() == 1) { + this.touchedLinksByTrainInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.RED.toString()); + } + } + + @Override + public void handleEvent(TrainLeavesLink event) { + + Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); + + HashSet> touchingTrainPaths = this.linkId2touchingTrainPaths.getOrDefault(event.getLinkId(), new HashSet<>()); + touchingTrainPaths.remove(event.getVehicleId()); + this.linkId2touchingTrainPaths.put(event.getLinkId(), touchingTrainPaths); + + this.touchedLinksCountToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + touchingTrainPaths.size()); + + int trainCapacity = RailsimUtils.getTrainCapacity(link); + + if (touchingTrainPaths.size() == trainCapacity - 1) { + // the link is no longer blocked + this.blockedLinksInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.GREEN.toString()); + } + + if (touchingTrainPaths.size() == 0) { + this.touchedLinksToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.GREEN.toString()); + } + + HashSet> touchingTrains = this.linkId2touchingTrains.getOrDefault(event.getLinkId(), new HashSet<>()); + touchingTrains.remove(event.getVehicleId()); + this.linkId2touchingTrains.put(event.getLinkId(), touchingTrains); + + if (touchingTrains.size() == 0) { + this.touchedLinksByTrainInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.GREEN.toString()); + } + } + + private boolean isBlocked(Link link) { + int trainCapacity = RailsimUtils.getTrainCapacity(link); + + int vehCount = 0; + if (this.linkId2touchingTrainPaths.get(link.getId()) != null) { + vehCount = this.linkId2touchingTrainPaths.get(link.getId()).size(); + } + if (vehCount == trainCapacity) { + return true; + } else if (vehCount < trainCapacity) { + return false; + } else { + throw new RuntimeException("More vehicles than allowed on link " + link.getId()); + } + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visTouchedLinks.csv", this.touchedLinksToVisualize); + printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visBlockedLinks.csv", this.blockedLinksInfosToVisualize); + printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visTouchedLinksByTrain.csv", this.touchedLinksByTrainInfosToVisualize); + printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visTouchedLinksCount.csv", this.touchedLinksCountToVisualize); + + } + + private void printSignalInfos(String outputFile, List strings) { + + BufferedWriter writer = IOUtils.getBufferedWriter(outputFile); + + try { + writer.write("time;fromX;fromY;toX;toY;state"); + writer.newLine(); + for (String line : strings) { + writer.write(line); + writer.newLine(); + } + writer.close(); + + log.info("Text info written to file."); + } catch (Exception e) { + log.warn("Text info not written to file."); + } + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java new file mode 100644 index 00000000000..90e3acb175f --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java @@ -0,0 +1,192 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.config.Config; +import org.matsim.core.config.ReflectiveConfigGroup; + +import java.util.Arrays; +import java.util.Map; + + +/** + * Provides the parameters for railsim. + * + * @author Ihab Kaddoura + */ +public final class RailsimConfigGroup extends ReflectiveConfigGroup { + public static final String GROUP_NAME = "railsim"; + private static final String REACTION_TIME = "reactionTime"; + private static final String GRAVITY = "gravity"; + private static final String DECELERATION = "deceleration"; + private static final String ACCELERATION = "acceleration"; + private static final String GRADE = "grade"; + private static final String ADJUST_NETWORK_TO_SCHEDULE = "adjustNetworkToSchedule"; + private static final String ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT = "abortIfVehicleMaxVelocityIsViolated"; + private static final String SPLIT_LINKS = "splitLinks"; + private static final String SPLIT_LINKS_LENGTH = "splitLinksLength"; + private static final String LINK_FREESPEED_APPROACH = "linkFreespeedApproach"; + private static final String TRAIN_ACCELERATION_APPROACH = "trainAccelerationApproach"; + + public enum TrainSpeedApproach {constantValue, fromLinkAttributesForEachVehicleType, fromLinkAttributesForEachLine, fromLinkAttributesForEachLineAndRoute} + + public enum TrainAccelerationApproach {without, euclideanDistanceBetweenStops, speedOnPreviousLink} + + public RailsimConfigGroup() { + super(GROUP_NAME); + } + + private static final Logger log = LogManager.getLogger(RailsimConfigGroup.class); + + private double reactionTime = 1.5; // seconds + private double gravity = 9.81; // meters per second squared + private double decelerationGlobalDefault = 0.5; // meters per second squared + private double accelerationGlobalDefault = 0.5; // meters per second squared + private double gradeGlobalDefault = 0.0; // percent + + private boolean adjustNetworkToSchedule = false; + private boolean abortIfVehicleMaxVelocityIsViolated = true; + + private boolean splitLinks = false; + private double splitLinksLength = 100.; + private TrainSpeedApproach trainSpeedApproach = TrainSpeedApproach.constantValue; + private TrainAccelerationApproach trainAccelerationApproach = TrainAccelerationApproach.without; + + @Override + public Map getComments() { + Map comments = super.getComments(); + comments.put(REACTION_TIME, "Reaction time in seconds which is used to compute the reserved train path."); + comments.put(GRAVITY, "Gravity in meters per second^2 which is used to compute the reserved train path."); + comments.put(DECELERATION, "Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link."); + comments.put(ACCELERATION, "Global acceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_ACCELERATION + ");" + " used to compute the train velocity per link."); + comments.put(GRADE, "Global grade in percentage which is used if there is no value provided in the link attributes (" + RailsimUtils.LINK_ATTRIBUTE_GRADE + ");" + " used to compute the reserved train path."); + comments.put(ADJUST_NETWORK_TO_SCHEDULE, "Set to 'true' to adjust the network to the travel times provided in the transit schedule. " + "There is no guarantee that all delays are eliminiated."); + comments.put(ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT, "Set to 'true' to throw a runtime exception if the schedule requires a speed which exceeds the vehicle maximum velocity. " + "Set to 'false' to continue anyway and only print a warning. For 'false', there is no guarantee that all delays are eliminiated."); + comments.put(SPLIT_LINKS, "Set to 'true' to split links into smaller link segments."); + comments.put(SPLIT_LINKS_LENGTH, "The (approximate) maximum length of link segments. Links with a higher distance will be split into smaller links."); + comments.put(LINK_FREESPEED_APPROACH, "The freespeed calculation approach: " + Arrays.toString(TrainSpeedApproach.values()) + ". " + TrainSpeedApproach.constantValue + " is the matsim default approach: a constant freespeed value for all vehicles. " + TrainSpeedApproach.fromLinkAttributesForEachVehicleType + " uses vehicle type-specific freespeed values provided in the link attributes. " + TrainSpeedApproach.fromLinkAttributesForEachLine + " uses transit line-specific freespeed values provided in the link attributes. " + TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute + " uses transit line- and route-specific freespeed values provided in the link attributes. "); + comments.put(TRAIN_ACCELERATION_APPROACH, "The acceleration calculation approach: " + Arrays.toString(TrainAccelerationApproach.values()) + ". " + TrainAccelerationApproach.without + " is the matsim default (The acceleration and deceleration is infinite). " + TrainAccelerationApproach.euclideanDistanceBetweenStops + " accounts for the euclidean distance between the train and the previous stop (acceleration) and the train and the next stop (decelearion). " + TrainAccelerationApproach.speedOnPreviousLink + " EXPERIMENTAL: accounts for the speed on the previous link and computes the acceleration based on the distance of the current link."); + return comments; + } + + // ######################################################################################################## + + @Override + protected void checkConsistency(Config config) { + + log.info("Checking consistency in train config group..."); + // TODO: check parameter consistency. + } + + @StringGetter(REACTION_TIME) + public double getReactionTime() { + return reactionTime; + } + + @StringSetter(REACTION_TIME) + public void setReactionTime(double reactionTime) { + this.reactionTime = reactionTime; + } + + @StringGetter(GRAVITY) + public double getGravity() { + return gravity; + } + + @StringSetter(GRAVITY) + public void setGravity(double gravity) { + this.gravity = gravity; + } + + @StringGetter(DECELERATION) + public double getDecelerationGlobalDefault() { + return decelerationGlobalDefault; + } + + @StringSetter(DECELERATION) + public void setDecelerationGlobalDefault(double deceleration) { + this.decelerationGlobalDefault = deceleration; + } + + @StringGetter(GRADE) + public double getGradeGlobalDefault() { + return gradeGlobalDefault; + } + + @StringSetter(GRADE) + public void setGradeGlobalDefault(double grade) { + this.gradeGlobalDefault = grade; + } + + @StringGetter(ADJUST_NETWORK_TO_SCHEDULE) + public boolean isAdjustNetworkToSchedule() { + return adjustNetworkToSchedule; + } + + @StringSetter(ADJUST_NETWORK_TO_SCHEDULE) + public void setAdjustNetworkToSchedule(boolean adjustNetworkToSchedule) { + this.adjustNetworkToSchedule = adjustNetworkToSchedule; + } + + @StringGetter(SPLIT_LINKS) + public boolean isSplitLinks() { + return splitLinks; + } + + @StringSetter(SPLIT_LINKS) + public void setSplitLinks(boolean splitLinks) { + this.splitLinks = splitLinks; + } + + @StringGetter(SPLIT_LINKS_LENGTH) + public double getSplitLinksLength() { + return splitLinksLength; + } + + @StringSetter(SPLIT_LINKS_LENGTH) + public void setSplitLinksLength(double splitLinksLength) { + this.splitLinksLength = splitLinksLength; + } + + @StringGetter(ACCELERATION) + public double getAccelerationGlobalDefault() { + return accelerationGlobalDefault; + } + + @StringSetter(ACCELERATION) + public void setAccelerationGlobalDefault(double accelerationGlobalDefault) { + this.accelerationGlobalDefault = accelerationGlobalDefault; + } + + @StringGetter(LINK_FREESPEED_APPROACH) + public TrainSpeedApproach getTrainSpeedApproach() { + return trainSpeedApproach; + } + + @StringSetter(LINK_FREESPEED_APPROACH) + public void setTrainSpeedApproach(TrainSpeedApproach trainSpeedApproach) { + this.trainSpeedApproach = trainSpeedApproach; + } + + @StringGetter(ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT) + public boolean isAbortIfVehicleMaxVelocityIsViolated() { + return abortIfVehicleMaxVelocityIsViolated; + } + + @StringSetter(ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT) + public void setAbortIfVehicleMaxVelocityIsViolated(boolean abortIfVehicleMaxVelocityIsViolated) { + this.abortIfVehicleMaxVelocityIsViolated = abortIfVehicleMaxVelocityIsViolated; + } + + @StringGetter(TRAIN_ACCELERATION_APPROACH) + public TrainAccelerationApproach getTrainAccelerationApproach() { + return trainAccelerationApproach; + } + + @StringSetter(TRAIN_ACCELERATION_APPROACH) + public void setTrainAccelerationApproach(TrainAccelerationApproach trainAccelerationApproach) { + this.trainAccelerationApproach = trainAccelerationApproach; + } + + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java new file mode 100644 index 00000000000..42ee94d92e0 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java @@ -0,0 +1,12 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.mobsim.qsim.qnetsimengine.linkspeedcalculator.LinkSpeedCalculator; +import org.matsim.vehicles.Vehicle; + +/** + * interface so that this can be injected + */ +public interface RailsimLinkSpeedCalculator extends LinkSpeedCalculator { + double getRailsimMaximumVelocity(Vehicle vehicle, Link link, double time); +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java new file mode 100644 index 00000000000..6d24e9f4bce --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java @@ -0,0 +1,198 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.events.TransitDriverStartsEvent; +import org.matsim.api.core.v01.events.handler.TransitDriverStartsEventHandler; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.mobsim.qsim.qnetsimengine.QVehicle; +import org.matsim.core.mobsim.qsim.qnetsimengine.linkspeedcalculator.DefaultLinkSpeedCalculator; +import org.matsim.core.network.NetworkUtils; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.matsim.vehicles.Vehicle; + +import java.util.HashSet; +import java.util.Set; + +/** + * A simple link speed calculator which accounts for the acceleration and deceleration of vehicles. + * + * @author Ihab Kaddoura + */ +public class RailsimLinkSpeedCalculatorImpl implements TransitDriverStartsEventHandler, RailsimLinkSpeedCalculator { + private static final Logger log = LogManager.getLogger(RailsimLinkSpeedCalculatorImpl.class); + + DefaultLinkSpeedCalculator defaultLinkSpeedCalculator = new DefaultLinkSpeedCalculator(); + Set> transitVehicles = new HashSet<>(); + + @Inject + Scenario scenario; + + @Inject + TrainStatistics statistics; + + @Override + public double getMaximumVelocity(QVehicle vehicle, Link link, double time) { + if (isTrain(vehicle)) { + return getRailsimMaximumVelocity(vehicle.getVehicle(), link, time); + } else { + return defaultLinkSpeedCalculator.getMaximumVelocity(vehicle, link, time); + } + } + + @Override + public double getRailsimMaximumVelocity(Vehicle vehicle, Link link, double time) { + + RailsimConfigGroup railSimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); + + double freespeed = Double.MIN_VALUE; + + { + final double defaultFreespeed = Math.min(vehicle.getType().getMaximumVelocity(), link.getFreespeed(time)); + if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.constantValue) { + freespeed = defaultFreespeed; + + } else if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachVehicleType) { + freespeed = RailsimUtils.getLinkFreespeedForVehicleType(vehicle.getType().getId(), link); + + } else if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine) { + Id line = statistics.getVehicleId2currentTransitLine().get(vehicle.getId()); + freespeed = RailsimUtils.getLinkFreespeedForTransitLine(line, link); + + } else if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute) { + Id line = statistics.getVehicleId2currentTransitLine().get(vehicle.getId()); + Id route = statistics.getVehicleId2currentTransitRoute().get(vehicle.getId()); + + freespeed = RailsimUtils.getLinkFreespeedForTransitLineAndTransitRoute(line, route, link); + + } else { + throw new RuntimeException("Unknown train speed approach. Aborting..."); + } + + if (freespeed <= 0) { + // use the default if no information is provided in the link attributes... + freespeed = defaultFreespeed; + } + } + + // now, account for acceleration and so on + + if (railSimConfigGroup.getTrainAccelerationApproach() == RailsimConfigGroup.TrainAccelerationApproach.without) { + return freespeed; + + } else if (railSimConfigGroup.getTrainAccelerationApproach() == RailsimConfigGroup.TrainAccelerationApproach.euclideanDistanceBetweenStops) { + + final double beelineDistanceFactor = 1.3; + + Id lastStopId = statistics.getVehicle2lastStop().get(vehicle.getId()); + TransitStopFacility lastStop = scenario.getTransitSchedule().getFacilities().get(lastStopId); + Coord lastStopCoord = scenario.getNetwork().getLinks().get(lastStop.getLinkId()).getCoord(); + + final double minimumDistanceNonZero = 1.; + double distanceFromLastStop = Math.max(minimumDistanceNonZero, NetworkUtils.getEuclideanDistance(lastStopCoord, link.getCoord()) * beelineDistanceFactor); + double speedWithConsiderationOfAcceleration = getSpeedAfterAccelerating(distanceFromLastStop, vehicle, railSimConfigGroup); + + Id nextStopId = statistics.getVehicle2nextStop().get(vehicle.getId()); + double speedWithConsiderationOfDeceleration = Double.MAX_VALUE; + if (nextStopId == null) { + // train at final stop of transit line, the vehicle may be required for the next cycle in a different transit route... + } else { + TransitStopFacility nextStop = scenario.getTransitSchedule().getFacilities().get(nextStopId); + Coord nextStopCoord = scenario.getNetwork().getLinks().get(nextStop.getLinkId()).getCoord(); + double distanceToNextStop = Math.max(minimumDistanceNonZero, NetworkUtils.getEuclideanDistance(link.getCoord(), nextStopCoord) * beelineDistanceFactor); + speedWithConsiderationOfDeceleration = getSpeedBeforeDecelerating(distanceToNextStop, vehicle, railSimConfigGroup); + } + double maxVehicleVelocityWithAccelerationAndDeceleration = Math.min(speedWithConsiderationOfAcceleration, speedWithConsiderationOfDeceleration); + + if (freespeed == 0. || maxVehicleVelocityWithAccelerationAndDeceleration == 0.) { + throw new RuntimeException("Velocity is 0. Aborting..."); + } + // make sure the infrastructure limitations are taken into consideration + double velocity = Math.min(freespeed, maxVehicleVelocityWithAccelerationAndDeceleration); +// log.info("vehicle: " + vehicle.getId() + " / link: " + link.getId() + "/ velocity: " + velocity); + return velocity; + + } else if (railSimConfigGroup.getTrainAccelerationApproach() == RailsimConfigGroup.TrainAccelerationApproach.speedOnPreviousLink) { + + // TODO: very experimental, needs tests etc. + // The idea is to start with the speed on the previous link (or: when entering the current link). + // The previous speed is then increased based on the acceleration and length of the current link. + // Unclear: What do we do about the deceleration? + // Unclear: The speed calculation is done for the front of the train path... and the front of the train is computed based on the + double speedOnPreviousLink = statistics.getSpeedOnPreviousLink(vehicle.getId()); + double speedWithConsiderationOfAcceleration = getSpeedAfterAcceleratingFromPreviousSpeed(speedOnPreviousLink, link.getLength(), vehicle, railSimConfigGroup); + + if (freespeed == 0. || speedWithConsiderationOfAcceleration == 0.) { + throw new RuntimeException("Velocity is 0. Aborting..."); + } + + // make sure the infrastructure limitations are taken into consideration + double velocity = Math.min(freespeed, speedWithConsiderationOfAcceleration); +// log.info("vehicle: " + vehicle.getId() + " / link: " + link.getId() + "/ velocity: " + velocity); + return velocity; + + } else { + throw new RuntimeException("Unknown train acceleration approach. Aborting..."); + } + + } + + /** + * @param distanceToNextStop + * @param vehicle + * @param railSimConfigGroup + * @return + */ + private double getSpeedBeforeDecelerating(double distanceToNextStop, Vehicle vehicle, RailsimConfigGroup railSimConfigGroup) { + double deceleration = RailsimUtils.getTrainDeceleration(vehicle, railSimConfigGroup); + double v = Math.sqrt(distanceToNextStop * 2 * deceleration); + double maxVelocityVehicle = vehicle.getType().getMaximumVelocity(); + return Math.min(v, maxVelocityVehicle); + } + + /** + * @param distanceFromLastStop + * @param vehicle + * @param railSimConfigGroup s = a/2 * t^2 + * a = v/t + * --> v = sqrt(s * 2 * a) + * @return + */ + private double getSpeedAfterAccelerating(double distanceFromLastStop, Vehicle vehicle, RailsimConfigGroup railSimConfigGroup) { + double acceleration = RailsimUtils.getTrainAcceleration(vehicle, railSimConfigGroup); + double v = Math.sqrt(distanceFromLastStop * 2 * acceleration); + double maxVelocityVehicle = vehicle.getType().getMaximumVelocity(); + return Math.min(v, maxVelocityVehicle); + } + + /** + * s = a/2 * t^2 + * a = (v1 - v0) / (t1 - t0) + * --> v1 = v0 + sqrt(s * 2 * a) + * + * @return + */ + private double getSpeedAfterAcceleratingFromPreviousSpeed(double previousSpeed, double distance, Vehicle vehicle, RailsimConfigGroup railSimConfigGroup) { + double acceleration = RailsimUtils.getTrainAcceleration(vehicle, railSimConfigGroup); + double v1 = previousSpeed + Math.sqrt(distance * 2 * acceleration); + double maxVelocityVehicle = vehicle.getType().getMaximumVelocity(); + return Math.min(v1, maxVelocityVehicle); + } + + private boolean isTrain(QVehicle vehicle) { + return this.transitVehicles.contains(vehicle.getVehicle().getId()); + } + + @Override + public void handleEvent(TransitDriverStartsEvent event) { + this.transitVehicles.add(event.getVehicleId()); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java new file mode 100644 index 00000000000..5f65dceb358 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java @@ -0,0 +1,35 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import org.matsim.core.mobsim.qsim.qnetsimengine.QNetworkFactory; +import org.matsim.core.mobsim.qsim.qnetsimengine.QRailsimSignalsNetworkFactory; + +class RailsimSignalsQSimModule extends AbstractQSimModule { + @Override + protected void configureQSim() { + this.bind(QNetworkFactory.class).toProvider(QNetworkFactoryProvider.class); + } + + static class QNetworkFactoryProvider implements Provider { + + @Inject + private Scenario scenario; + + @Inject + private EventsManager events; + + @Inject + private RailsimLinkSpeedCalculator calculator; + + @Override + public QNetworkFactory get() { + final QRailsimSignalsNetworkFactory factory = new QRailsimSignalsNetworkFactory(scenario, events); + factory.setLinkSpeedCalculator(calculator); + return factory; + } + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java new file mode 100644 index 00000000000..511c2b80cb1 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java @@ -0,0 +1,138 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; + +/** + * @author Ihab Kaddoura + */ +public final class RailsimUtils { + + // link + public static final String LINK_ATTRIBUTE_GRADE = "grade"; + public static final String LINK_ATTRIBUTE_OPPOSITE_DIRECTION = "trainOppositeDirectionLink"; + public static final String LINK_ATTRIBUTE_CAPACITY = "trainCapacity"; + public static final String LINK_ATTRIBUTE_MAX_SPEED = "maxSpeed"; + public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "minimumTime"; + // vehicle + public static final String VEHICLE_ATTRIBUTE_MAX_DECELERATION = "maxDeceleration"; + public static final String VEHICLE_ATTRIBUTE_MAX_ACCELERATION = "maxAcceleration"; + + private RailsimUtils() { + } + + /** + * @param link + * @return the train capacity for this link, if no link attribute is provided the default is 1. + */ + public static int getTrainCapacity(Link link) { + int trainCapacity = 1; + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY) != null) { + trainCapacity = (Integer) link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY); + } + return trainCapacity; + } + + /** + * @param link + * @return the minimum time for the switch at the end of the link (toNode); if no link attribute is provided the default is 0. + */ + public static double getMinimumTrainHeadwayTime(Link link) { + double minimumTime = 0.; + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME) != null) { + minimumTime = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME); + } + return minimumTime; + } + + /** + * @return the default deceleration time or the vehicle-specific value + */ + public static double getTrainDeceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { + double deceleration = railsimConfigGroup.getDecelerationGlobalDefault(); + if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION) != null) { + deceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION); + } + return deceleration; + } + + /** + * @return the default acceleration time or the vehicle-specific value + */ + public static double getTrainAcceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { + double acceleration = railsimConfigGroup.getAccelerationGlobalDefault(); + if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION) != null) { + acceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION); + } + return acceleration; + } + + /** + * @return the default acceleration time or the vehicle-specific value + */ + public static double getGrade(Link link, RailsimConfigGroup railsimConfigGroup) { + double grade = railsimConfigGroup.getGradeGlobalDefault(); + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE) != null) { + grade = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE); + } + return grade; + } + + /** + * @param type + * @param link + * @return the vehicle-specific freespeed or 0 if there is no vehicle-specific freespeed provided in the link attributes + */ + public static double getLinkFreespeedForVehicleType(Id type, Link link) { + Object attribute = link.getAttributes().getAttribute(type.toString()); + if (attribute == null) { + return 0.; + } else { + return (double) attribute; + } + } + + /** + * @param line + * @param link + * @return the line-specific freespeed or 0 if there is no line-specific freespeed provided in the link attributes + */ + public static double getLinkFreespeedForTransitLine(Id line, Link link) { + Object attribute = link.getAttributes().getAttribute(line.toString()); + if (attribute == null) { + return 0.; + } else { + return (double) attribute; + } + } + + /** + * @param line + * @param route + * @param link + * @return the line- and route-specific freespeed or 0 if there is no line- and route-specific freespeed provided in the link attributes + */ + public static double getLinkFreespeedForTransitLineAndTransitRoute(Id line, Id route, Link link) { + Object attribute = link.getAttributes().getAttribute(line.toString() + "+++" + route.toString()); + if (attribute == null) { + return 0.; + } else { + return (double) attribute; + } + } + + public static Id getOppositeDirectionLink(Link link, Network network) { + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION) == null) { + return null; + } else { + String oppositeLink = (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION); + return Id.createLinkId(oppositeLink); + } + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java new file mode 100644 index 00000000000..0d1c191a787 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java @@ -0,0 +1,158 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import ch.sbb.matsim.contrib.railsim.prototype.prepare.AdjustNetworkToSchedule; +import ch.sbb.matsim.contrib.railsim.prototype.prepare.SplitTransitLinks; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.QSimConfigGroup.LinkDynamics; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.vehicles.VehicleType; + +/** + * @author Ihab Kaddoura + */ +public final class RunRailsim { + private static final Logger log = LogManager.getLogger(RunRailsim.class); + + private RunRailsim() { + } + + public static void main(String[] args) { + log.info("Arguments:"); + for (String arg : args) { + log.info(arg); + } + log.info("---"); + + Config config = prepareConfig(args); + Scenario scenario = prepareScenario(config); + Controler controler = prepareControler(scenario); + + controler.run(); + } + + public static Config prepareConfig(String[] args) { + Config config = ConfigUtils.loadConfig(args); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + config.qsim().setUsingFastCapacityUpdate(false); + config.qsim().setTimeStepSize(1.); + config.qsim().setLinkDynamics(LinkDynamics.PassingQ); + config.qsim().setNumberOfThreads(1); + + // Storage capacities are handled via signals. + // To avoid any additional restrictions, the storage capacity is set to a very large value. + if (config.qsim().getStorageCapFactor() < 9999999.) { + log.warn("Storage capacities are handled via signals. To avoid any additional restrictions, the storage capacity factor is set to 9999999."); + config.qsim().setStorageCapFactor(9999999.); + } + + if (config.qsim().getFlowCapFactor() != 1.0) { + log.warn("The signals need at least one time step to react to the movements of transit vehicles. To enforce this, the flow capacity factor needs to be set to 1.0."); + config.qsim().setFlowCapFactor(1.0); + } + + return config; + } + + public static Scenario prepareScenario(Config config) { + Scenario scenario = ScenarioUtils.loadScenario(config); + double timeStepSize = config.qsim().getTimeStepSize(); + + // check links + for (Link link : scenario.getNetwork().getLinks().values()) { + + if (link.getCapacity() > 3600 / timeStepSize) { + log.warn("The signals need at least one time step to react to the movements of transit vehicles. Link capacities should be set to a maximum of 3600 vehicles per hour."); + link.setCapacity(3600. / timeStepSize); + } + + if (RailsimUtils.getOppositeDirectionLink(link, scenario.getNetwork()) != null && RailsimUtils.getTrainCapacity(link) > 1) { + + throw new RuntimeException("In the current version, one direction tracks only work for capacities = 1. " + "For capacities larger than 1, we have to think about the train disposition strategies to avoid deadlocks."); + } + + for (Link outLink : link.getFromNode().getOutLinks().values()) { + if (outLink == link) { + // same link + } else { + // another link + if (outLink.getFromNode() == link.getFromNode() && outLink.getToNode() == link.getToNode()) { + // overlaying link + throw new RuntimeException("Overlaying links with identical from and to node: " + outLink.getId() + " and " + link.getId() + ". " + "Please use separate nodes if you want to run trains on separate links, otherwise we have many potentially conflicting links " + "which increases the computation time. Aborting..."); + } + } + } + + } + + for (VehicleType vehicleType : scenario.getTransitVehicles().getVehicleTypes().values()) { + if (vehicleType.getPcuEquivalents() != 1.0) { + log.warn("The transit vehicle types are required to have a pcu equivalent of 1.0."); + vehicleType.setPcuEquivalents(1.0); + } + } + + return scenario; + } + + public static Controler prepareControler(Scenario scenario) { + + RailsimConfigGroup railsimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); + + if (railsimConfigGroup.isAdjustNetworkToSchedule()) { + AdjustNetworkToSchedule adjust = new AdjustNetworkToSchedule(scenario); + adjust.run(); + } + + if (railsimConfigGroup.isSplitLinks()) { + SplitTransitLinks splitTransitLinks = new SplitTransitLinks(scenario); + splitTransitLinks.run(railsimConfigGroup.getSplitLinksLength()); + } + + Controler controler = new Controler(scenario); + + controler.addOverridingModule(new AbstractModule() { + @Override + public void install() { + + // train statistics used in different classes + bind(TrainStatistics.class).asEagerSingleton(); + addEventHandlerBinding().to(TrainStatistics.class); + + // train expansion along several links + bind(SpatialTrainDimension.class).asEagerSingleton(); + addEventHandlerBinding().to(SpatialTrainDimension.class); + + // adaptive signal controler + bind(AdaptiveTrainSignalsControler.class).asEagerSingleton(); + addMobsimListenerBinding().to(AdaptiveTrainSignalsControler.class); + addEventHandlerBinding().to(AdaptiveTrainSignalsControler.class); + addControlerListenerBinding().to(AdaptiveTrainSignalsControler.class); + + // visualization output + bind(LinkUsageVisualizer.class).asEagerSingleton(); + addEventHandlerBinding().to(LinkUsageVisualizer.class); + addControlerListenerBinding().to(LinkUsageVisualizer.class); + + // for train acceleration / deceleration dynamics + bind(RailsimLinkSpeedCalculatorImpl.class).asEagerSingleton(); + bind(RailsimLinkSpeedCalculator.class).to(RailsimLinkSpeedCalculatorImpl.class); + addEventHandlerBinding().to(RailsimLinkSpeedCalculatorImpl.class); + + } + }); + + controler.addOverridingQSimModule(new RailsimSignalsQSimModule()); + + return controler; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java new file mode 100644 index 00000000000..4c34be32f0b --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java @@ -0,0 +1,134 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + + +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.mobsim.qsim.interfaces.SignalGroupState; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * @author Ihab Kaddoura + */ +public class SignalInfo { + + private final Link toLink; + private final Link oppositeLink; + private final List fromLink2fromLinkInfo; + private final Map signalCondition2state; + + public enum SignalCondition {linkCapacity, nodeCapacity, nodeMinimumHeadway, oppositeDirection, conflictingOppositeLink} + + private Iterator iterator; + private boolean considerInNextTimeStep = true; + + /** + * @param considerInNextTimeStep the considerInNextTimeStep to set + */ + public void setConsiderInNextTimeStep(boolean considerInNextTimeStep) { + this.considerInNextTimeStep = considerInNextTimeStep; + } + + public SignalInfo(Link toLink, Network network) { + this.toLink = toLink; + + this.fromLink2fromLinkInfo = new ArrayList<>(); + + for (Link inLink : toLink.getFromNode().getInLinks().values()) { + if (inLink.getFromNode() == toLink.getToNode() && inLink.getToNode() == toLink.getFromNode()) { + // skip the inverse link (assuming that turning around at nodes is not possible) + } else { + fromLink2fromLinkInfo.add(inLink); + } + } + + if (RailsimUtils.getOppositeDirectionLink(toLink, network) != null) { + oppositeLink = network.getLinks().get(RailsimUtils.getOppositeDirectionLink(toLink, network)); + } else { + oppositeLink = null; + } + + iterator = this.fromLink2fromLinkInfo.iterator(); + + this.signalCondition2state = new HashMap<>(); + + // set initial conditions to green + this.signalCondition2state.put(SignalCondition.linkCapacity, SignalGroupState.GREEN); + this.signalCondition2state.put(SignalCondition.nodeCapacity, SignalGroupState.GREEN); + this.signalCondition2state.put(SignalCondition.nodeMinimumHeadway, SignalGroupState.GREEN); + this.signalCondition2state.put(SignalCondition.oppositeDirection, SignalGroupState.GREEN); + } + + /** + * Changes the state (GREEN/RED) of a signal condition (e.g. capacity, minimum time) + * + * @param condition + * @param state + */ + public void changeCondition(SignalCondition condition, SignalGroupState state) { + this.signalCondition2state.put(condition, state); + } + + /** + * Returns true if all signal conditions are green, otherwise false. + */ + public boolean allConditionsGreen() { + for (SignalGroupState state : this.signalCondition2state.values()) { + if (state == SignalGroupState.RED) { + return false; + } + } + return true; + } + + /** + * @return the signalCondition2state + */ + public Map getSignalCondition2state() { + return signalCondition2state; + } + + /** + * @return the oppositeLink + */ + public Link getOppositeLink() { + return oppositeLink; + } + + /** + * @return the toLink + */ + public Link getToLink() { + return toLink; + } + + /** + * @return the fromLink2fromLinkInfo + */ + public List getFromLink2fromLinkInfo() { + return fromLink2fromLinkInfo; + } + + /** + * @return the next fromLink using an iterator which always starts from the beginning + */ + public Link getNextFromLink() { + if (!iterator.hasNext()) { + // set the iterator to the beginning + iterator = this.fromLink2fromLinkInfo.iterator(); + } + return iterator.next(); + } + + /** + * @return the considerInNextTimeStep + */ + public boolean isConsiderInNextTimeStep() { + return considerInNextTimeStep; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java new file mode 100644 index 00000000000..83d99eaaa0b --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java @@ -0,0 +1,257 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +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.VehicleEntersTrafficEvent; +import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; +import org.matsim.api.core.v01.events.handler.VehicleEntersTrafficEventHandler; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.ConfigUtils; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitRouteStop; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; + +import javax.inject.Inject; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +/** + * This class computes the spatial dimension of trains along several links. + *

+ * Processes default MATSim events (e.g. link enter events), accounts for the length of the train plus the reserved train path + * and then throws train enters and train leaves events. + * + * @author Ihab Kaddoura + */ +public class SpatialTrainDimension implements LinkEnterEventHandler, VehicleEntersTrafficEventHandler { + private static final Logger log = LogManager.getLogger(SpatialTrainDimension.class); + + @Inject + private Scenario scenario; + + @Inject + private EventsManager events; + + @Inject + private TrainStatistics statistics; + + @Inject + private RailsimLinkSpeedCalculator linkSpeedCalculator; + + private final HashMap, LinkedList>> vehicleId2currentlyTouchedLinksTotalSorted = new HashMap<>(); + + // in the following data container we don't need the actual queue or order of vehicles + private final HashMap, Set>> vehicleId2currentlyTouchedLinksByTrain = new HashMap<>(); + + private int warnCnt = 0; + + @Override + public void reset(int iteration) { + if (iteration > 0) throw new RuntimeException("Running more than 1 iteration. Aborting..."); + } + + @Override + public void handleEvent(LinkEnterEvent event) { + + // matsim vehicle is the tip of the train path (=fahrweg) + this.events.processEvent(new TrainPathEntersLink(event.getTime(), event.getLinkId(), event.getVehicleId())); + + // update the information about which links are touched and blocked (by the train and/or by the fahrweg) + updateBlockLinks(event.getLinkId(), event.getVehicleId(), event.getTime()); + } + + /** + * @param linkId + * @param vehicleId + * @param time + */ + private void updateBlockLinks(Id linkId, Id vehicleId, double time) { + + final LinkedList> touchedLinksSorted = this.vehicleId2currentlyTouchedLinksTotalSorted.computeIfAbsent(vehicleId, k -> new LinkedList<>()); + // add the just entered linkId to the 'queue' + touchedLinksSorted.addFirst(linkId); + + final Set> touchedLinksByTrain = this.vehicleId2currentlyTouchedLinksByTrain.computeIfAbsent(vehicleId, k -> new HashSet<>()); + + VehicleType vehicleType = this.scenario.getTransitVehicles().getVehicles().get(vehicleId).getType(); + final double vehicleLength = vehicleType.getLength(); + final double fahrwegLength = getFahrwegLength(vehicleId, vehicleType, linkId, time); + final double trainAndFahrwegLength = vehicleLength + fahrwegLength; + + // iterate through the list of touched links (start from where the transit vehicle currently is) + + // IIIIIIIIIIIIIIIIIIIII---------------------------- >>> + // | train | fahrweg 'vehicle' + + LinkedList> touchedLinksSortedUpdated = new LinkedList<>(); + + LinkedList linksNoLongerTouched = new LinkedList<>(); + + Link latestTouchedLink = this.scenario.getNetwork().getLinks().get(linkId); + if (latestTouchedLink.getLength() > trainAndFahrwegLength) { + // This link is long enough --> This link is touched by both the path and the train itself + + // update the queue + touchedLinksSortedUpdated.add(linkId); + + // all other links are no longer touched, update the list of no longer touched links + for (Id touchedLinkId : touchedLinksSorted) { + if (touchedLinkId.toString().equals(linkId.toString())) { + // this link is still touched + } else { + Link touchedLink = this.scenario.getNetwork().getLinks().get(touchedLinkId); + linksNoLongerTouched.addFirst(touchedLink); + } + } + + // The link is also touched by the train -> throw train enters link event + this.events.processEvent(new TrainEntersLink(time, linkId, vehicleId)); + touchedLinksByTrain.add(linkId); + + } else { + double lengthCumulated = 0.; + for (Id touchedLinkId : touchedLinksSorted) { + Link touchedLink = this.scenario.getNetwork().getLinks().get(touchedLinkId); + + if (lengthCumulated <= trainAndFahrwegLength) { + // The link is touched by either the fahrweg or the train itself. + + touchedLinksSortedUpdated.add(touchedLinkId); + + // compute the train position (for visualization purposes only) + if (lengthCumulated < fahrwegLength) { + // this link is touched by the reserved fahrweg (not by the train itself) + } else { + // this link is touched by the train itself + + if (touchedLinksByTrain.contains(touchedLinkId)) { + // This link has already been touched by the train. + } else { + // This link is touched by the train for the first time. + this.events.processEvent(new TrainEntersLink(time, touchedLinkId, vehicleId)); + touchedLinksByTrain.add(touchedLinkId); + } + } + + // increase the cumulated length + lengthCumulated += touchedLink.getLength(); + + } else { + // collect the links which are no longer touched by the train or fahrweg + linksNoLongerTouched.addFirst(touchedLink); + } + } + } + + // throw 'train enters link' events for all links no longer touched by the train path + // otherwise these events are missing and event-based processing may not work correctly + for (Link linkNoLongerTouched : linksNoLongerTouched) { + if (touchedLinksByTrain.contains(linkNoLongerTouched.getId())) { + // a 'train enters link' event has already been thrown for this link + } else { + if (warnCnt < 5) { + log.warn("'train entered link' event is thrown in the same time step as the 'train left link' event. " + "This may be prevented by reducing the link length or reducing the physical extension of the train (train length or train path length)."); + log.warn("link: " + linkId + " / vehicle: " + vehicleId); + warnCnt++; + } else if (warnCnt == 5) { + log.warn("Further warnings of this type will not be printed out."); + warnCnt++; + } + this.events.processEvent(new TrainEntersLink(time, linkNoLongerTouched.getId(), vehicleId)); + touchedLinksByTrain.add(linkNoLongerTouched.getId()); + } + } + + // throw 'train leaves link' events for all links no longer touched by the train (or train path) + for (Link linkNoLongerTouched : linksNoLongerTouched) { + this.events.processEvent(new TrainLeavesLink(time, linkNoLongerTouched.getId(), vehicleId)); + + // link is also no longer touched by train + touchedLinksByTrain.remove(linkNoLongerTouched.getId()); + } + + // add the updated lists of blocked link IDs + this.vehicleId2currentlyTouchedLinksTotalSorted.put(vehicleId, touchedLinksSortedUpdated); + } + + /** + * @param vehicleId + * @param vehicleType + * @param linkId + * @return + */ + private double getFahrwegLength(Id vehicleId, VehicleType vehicleType, Id linkId, double time) { + + RailsimConfigGroup railsimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); + + // Set the reserved train path to 0 if the train is on a link at which the train stops. + TransitStopFacility stopFacility = this.statistics.getLink2stop().get(linkId); + if (stopFacility != null) { + // this link is a stop + Id currentLineId = this.statistics.getVehicleId2currentTransitLine().get(vehicleId); + Id currentRouteId = this.statistics.getVehicleId2currentTransitRoute().get(vehicleId); + TransitRouteStop routeStop = this.scenario.getTransitSchedule().getTransitLines().get(currentLineId).getRoutes().get(currentRouteId).getStop(stopFacility); + if (routeStop != null) { + // The train is about to stop at this station and the speed will be reduced to 0. + // Setting the length of the reserved train path to 0. + return 0.; + } else { + // The train will not stop at this station and the speed will not be reduced. + // The reserved train path will not be reduced to 0. + } + } + + // start with the theoretical maximum speed + // the following does not account for the acceleration or deceleration... +// final double maxSpeedOnTheCurrentLink = Math.min(vehicleType.getMaximumVelocity(), +// this.scenario.getNetwork().getLinks().get(linkId).getFreespeed()); + + Vehicle vehicle = scenario.getTransitVehicles().getVehicles().get(vehicleId); + Link link = scenario.getNetwork().getLinks().get(linkId); + + final double maxSpeedOnTheCurrentLink = linkSpeedCalculator.getRailsimMaximumVelocity(vehicle, this.scenario.getNetwork().getLinks().get(linkId), time); + + // account for congestion effects, e.g. a fast train behind a slower train + double speedWhenEnteringTheLink = this.statistics.getSpeedOnPreviousLink(vehicleId); + final double currentSpeed = Math.min(maxSpeedOnTheCurrentLink, speedWhenEnteringTheLink); + + final double reactionTime = railsimConfigGroup.getReactionTime(); + final double gravity = railsimConfigGroup.getGravity(); + + double deceleration = RailsimUtils.getTrainDeceleration(vehicle, railsimConfigGroup); + double grade = RailsimUtils.getGrade(link, railsimConfigGroup); + + // calculate the fahrwegLength with given formula + double fahrwegLength = (currentSpeed * reactionTime) + (Math.pow(currentSpeed, 2) / (2 * (deceleration + gravity * grade))) + ((currentSpeed / 2) + 20); + + return fahrwegLength; + } + + @Override + public void handleEvent(VehicleEntersTrafficEvent event) { + + // manually process the relevant events on the initial link + this.events.processEvent(new TrainPathEntersLink(event.getTime(), event.getLinkId(), event.getVehicleId())); + this.events.processEvent(new TrainEntersLink(event.getTime(), event.getLinkId(), event.getVehicleId())); + + // also add the initial link to the relevant data containers + + Set> touchedLinksByTrainUpdated = new HashSet<>(); + touchedLinksByTrainUpdated.add(event.getLinkId()); + this.vehicleId2currentlyTouchedLinksByTrain.put(event.getVehicleId(), touchedLinksByTrainUpdated); + + LinkedList> touchedLinksSorted = new LinkedList<>(); + touchedLinksSorted.add(event.getLinkId()); + this.vehicleId2currentlyTouchedLinksTotalSorted.put(event.getVehicleId(), touchedLinksSorted); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java new file mode 100644 index 00000000000..9e63006e9d6 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java @@ -0,0 +1,56 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasLinkId; +import org.matsim.api.core.v01.events.HasVehicleId; +import org.matsim.api.core.v01.network.Link; +import org.matsim.vehicles.Vehicle; + +import java.util.Map; + +/** + * @author Ihab Kaddoura + */ +public class TrainEntersLink extends Event implements HasLinkId, HasVehicleId { + + public static final String EVENT_TYPE = "train entered link"; + public static final String ATTRIBUTE_VEHICLE = "vehicle"; + public static final String ATTRIBUTE_LINK = "link"; + + private final Id linkId; + private final Id vehicleId; + + public TrainEntersLink(double time, Id linkId, Id vehicleId) { + super(time); + this.linkId = linkId; + this.vehicleId = vehicleId; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getLinkId() { + return linkId; + } + + /** + * @return the vehicleId + */ + @Override + public Id getVehicleId() { + return vehicleId; + } + + @Override + public Map getAttributes() { + Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); + attr.put(ATTRIBUTE_LINK, this.linkId.toString()); + return attr; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java new file mode 100644 index 00000000000..2e60fb2349e --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java @@ -0,0 +1,11 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + + +import org.matsim.core.events.handler.EventHandler; + +/** + * @author Ihab Kaddoura + */ +public interface TrainEntersLinkEventHandler extends EventHandler { + void handleEvent(TrainEntersLink event); +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java new file mode 100644 index 00000000000..761e7139ef3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java @@ -0,0 +1,55 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasLinkId; +import org.matsim.api.core.v01.events.HasVehicleId; +import org.matsim.api.core.v01.network.Link; +import org.matsim.vehicles.Vehicle; + +import java.util.Map; + +/** + * @author Ihab Kaddoura + */ +public class TrainLeavesLink extends Event implements HasLinkId, HasVehicleId { + + public static final String EVENT_TYPE = "train left link"; + public static final String ATTRIBUTE_VEHICLE = "vehicle"; + public static final String ATTRIBUTE_LINK = "link"; + + private final Id linkId; + private final Id vehicleId; + + public TrainLeavesLink(double time, Id linkId, Id vehicleId) { + super(time); + this.linkId = linkId; + this.vehicleId = vehicleId; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getLinkId() { + return linkId; + } + + /** + * @return the vehicleId + */ + @Override + public Id getVehicleId() { + return vehicleId; + } + + @Override + public Map getAttributes() { + Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); + attr.put(ATTRIBUTE_LINK, this.linkId.toString()); + return attr; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java new file mode 100644 index 00000000000..5578a42b4f4 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java @@ -0,0 +1,10 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.matsim.core.events.handler.EventHandler; + +/** + * @author Ihab Kaddoura + */ +public interface TrainLeavesLinkEventHandler extends EventHandler { + void handleEvent(TrainLeavesLink event); +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java new file mode 100644 index 00000000000..1ae6c05fbaf --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java @@ -0,0 +1,56 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasLinkId; +import org.matsim.api.core.v01.events.HasVehicleId; +import org.matsim.api.core.v01.network.Link; +import org.matsim.vehicles.Vehicle; + +import java.util.Map; + +/** + * @author Ihab Kaddoura + */ +public class TrainPathEntersLink extends Event implements HasLinkId, HasVehicleId { + + public static final String EVENT_TYPE = "train path entered link"; + public static final String ATTRIBUTE_VEHICLE = "vehicle"; + public static final String ATTRIBUTE_LINK = "link"; + + private final Id linkId; + private final Id vehicleId; + + public TrainPathEntersLink(double time, Id linkId, Id vehicleId) { + super(time); + this.linkId = linkId; + this.vehicleId = vehicleId; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getLinkId() { + return this.linkId; + } + + /** + * @return the vehicleId + */ + @Override + public Id getVehicleId() { + return vehicleId; + } + + @Override + public Map getAttributes() { + Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); + attr.put(ATTRIBUTE_LINK, this.linkId.toString()); + return attr; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java new file mode 100644 index 00000000000..52fb7ee4a1b --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java @@ -0,0 +1,10 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.matsim.core.events.handler.EventHandler; + +/** + * @author Ihab Kaddoura + */ +public interface TrainPathEntersLinkEventHandler extends EventHandler { + public void handleEvent(TrainPathEntersLink event); +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java new file mode 100644 index 00000000000..a5f1e3a860d --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java @@ -0,0 +1,195 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +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.LinkLeaveEvent; +import org.matsim.api.core.v01.events.TransitDriverStartsEvent; +import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; +import org.matsim.api.core.v01.events.handler.LinkLeaveEventHandler; +import org.matsim.api.core.v01.events.handler.TransitDriverStartsEventHandler; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.api.experimental.events.VehicleArrivesAtFacilityEvent; +import org.matsim.core.api.experimental.events.handler.VehicleArrivesAtFacilityEventHandler; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitRouteStop; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.matsim.vehicles.Vehicle; + +import javax.inject.Inject; +import java.util.HashMap; +import java.util.List; + +/** + * Note: These statistics refer to the MATSim transit vehicles, which in our current implementation is interpreted as the front of the train path. + * + * @author Ihab Kaddoura + */ +public class TrainStatistics implements LinkEnterEventHandler, LinkLeaveEventHandler, TransitDriverStartsEventHandler, VehicleArrivesAtFacilityEventHandler { + private static final Logger log = LogManager.getLogger(TrainStatistics.class); + + @Inject + private Scenario scenario; + + // the following maps are required to compute the length of the reserved train path + private final HashMap, Double> vehicleId2speedOnPreviousLink = new HashMap<>(); + private final HashMap, Double> vehicleId2lastLinkEnterTime = new HashMap<>(); + private final HashMap, Id> vehicleId2previousLink = new HashMap<>(); + + private final HashMap, Id> vehicleId2currentTransitLine = new HashMap<>(); + private final HashMap, Id> vehicleId2currentTransitRoute = new HashMap<>(); + private final HashMap, TransitStopFacility> link2stop = new HashMap<>(); + private final HashMap, Id> stop2link = new HashMap<>(); + + private final HashMap, Id> vehicle2lastStop = new HashMap<>(); + private final HashMap, Id> vehicle2nextStop = new HashMap<>(); + + private final HashMap, Id> vehicle2lastStopLink = new HashMap<>(); + + @Override + public void reset(int iteration) { + + for (TransitStopFacility facility : this.scenario.getTransitSchedule().getFacilities().values()) { + if (this.link2stop.containsKey(facility.getLinkId())) { + log.warn("transitStopFacility: " + facility.getId() + " 1st link: " + link2stop.get(facility.getLinkId())); + log.warn("transitStopFacility: " + facility.getId() + " 2nd link: " + facility.getLinkId()); + throw new RuntimeException("A transit stop facility is located on more than one link. Aborting..."); + } + this.link2stop.put(facility.getLinkId(), facility); + this.stop2link.put(facility.getId(), facility.getLinkId()); + } + + if (iteration > 0) throw new RuntimeException("Running more than 1 iteration. Aborting..."); + } + + @Override + public void handleEvent(LinkEnterEvent event) { + this.vehicleId2lastLinkEnterTime.put(event.getVehicleId(), event.getTime()); + } + + @Override + public void handleEvent(LinkLeaveEvent event) { + if (vehicleId2lastLinkEnterTime.get(event.getVehicleId()) == null) { + // At the very beginning vehicles are 'put' on the link without entering... + this.vehicleId2speedOnPreviousLink.put(event.getVehicleId(), null); + + } else { + double t = event.getTime() - this.vehicleId2lastLinkEnterTime.get(event.getVehicleId()); + double s = this.scenario.getNetwork().getLinks().get(event.getLinkId()).getLength(); + double v = s / t; + this.vehicleId2speedOnPreviousLink.put(event.getVehicleId(), v); + } + + this.vehicleId2previousLink.put(event.getVehicleId(), event.getLinkId()); + } + + @Override + public void handleEvent(TransitDriverStartsEvent event) { + this.vehicleId2currentTransitLine.put(event.getVehicleId(), event.getTransitLineId()); + this.vehicleId2currentTransitRoute.put(event.getVehicleId(), event.getTransitRouteId()); + } + + /** + * @return the speed on the previous link. + * If there is no information about the previous link or if the vehicle was stopping at the previous link, the speed on the previous link is 0. + */ + public double getSpeedOnPreviousLink(Id vehicleId) { + + if (vehicleId2speedOnPreviousLink.get(vehicleId) == null) { + // There is no speed stored for the vehicle, probably the beginning of the transit route. + return 0.; + } else { + + if (this.vehicle2lastStop.get(vehicleId) == null) { + throw new RuntimeException("A vehicle should always have a last stop. Aborting..."); + } + + if (this.vehicle2lastStopLink.get(vehicleId).toString().equals(this.vehicleId2previousLink.get(vehicleId).toString())) { + // the vehicle was stopping at the previous link + return 0.; + } + + return vehicleId2speedOnPreviousLink.get(vehicleId); + + } + } + + /** + * @return the vehicleId2lastLinkEnterTime + */ + public HashMap, Double> getVehicleId2lastLinkEnterTime() { + return vehicleId2lastLinkEnterTime; + } + + /** + * @return the vehicleId2currentTransitLine + */ + public HashMap, Id> getVehicleId2currentTransitLine() { + return vehicleId2currentTransitLine; + } + + /** + * @return the vehicleId2currentTransitRoute + */ + public HashMap, Id> getVehicleId2currentTransitRoute() { + return vehicleId2currentTransitRoute; + } + + /** + * @return the link2stop + */ + public HashMap, TransitStopFacility> getLink2stop() { + return link2stop; + } + + @Override + public void handleEvent(VehicleArrivesAtFacilityEvent event) { + this.vehicle2lastStop.put(event.getVehicleId(), event.getFacilityId()); + this.vehicle2lastStopLink.put(event.getVehicleId(), this.stop2link.get(event.getFacilityId())); + + // find next stop + Id lineId = this.vehicleId2currentTransitLine.get(event.getVehicleId()); + Id routeId = this.vehicleId2currentTransitRoute.get(event.getVehicleId()); + + List stops = this.scenario.getTransitSchedule().getTransitLines().get(lineId).getRoutes().get(routeId).getStops(); + int index = 0; + for (TransitRouteStop stop : stops) { + if (stop.getStopFacility().getId().toString().equals(event.getFacilityId().toString())) { + break; + } + index++; + } + + int indexNextStop = index + 1; + TransitRouteStop nextStop = null; + if (stops.size() == indexNextStop) { + // final stop + } else { + nextStop = stops.get(indexNextStop); + } + if (nextStop == null) { + this.vehicle2nextStop.put(event.getVehicleId(), null); + } else { + this.vehicle2nextStop.put(event.getVehicleId(), nextStop.getStopFacility().getId()); + } + } + + /** + * @return the vehicle2lastStop + */ + public HashMap, Id> getVehicle2lastStop() { + return vehicle2lastStop; + } + + /** + * @return the vehicle2nextStop + */ + public HashMap, Id> getVehicle2nextStop() { + return vehicle2nextStop; + } + + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java new file mode 100644 index 00000000000..3ac0c959727 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java @@ -0,0 +1,47 @@ +package ch.sbb.matsim.contrib.railsim.prototype.analysis; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.events.algorithms.EventWriterXML; + +/** + * @author Ihab Kaddoura + */ +public class ConvertTrainEventsToDefaultEvents { + private static final Logger log = LogManager.getLogger(ConvertTrainEventsToDefaultEvents.class); + + public static void main(String[] args) { + + String outputDirectory = "test/output/ch/sbb/railsim/RunRailsimTest/test4/"; + String runId = "test"; + + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(runId, outputDirectory); + } + + public void run(String runId, String outputDirectory) { + + if (!outputDirectory.endsWith("/")) outputDirectory = outputDirectory.concat("/"); + + String eventsFile = outputDirectory + runId + ".output_events.xml.gz"; + + // read events + EventsManager events = EventsUtils.createEventsManager(); + EventWriterXML eventWriter = new EventWriterXML(outputDirectory + runId + ".output_events_trainVisualization.xml.gz"); + + TrainEventsHandler trainEventHandler = new TrainEventsHandler(events); + events.addHandler(eventWriter); + events.addHandler(trainEventHandler); + events.initProcessing(); + + log.info("Reading the events..."); + new TrainEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + log.info("Reading the events... Done."); + + eventWriter.closeFile(); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java new file mode 100644 index 00000000000..d94aff44826 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java @@ -0,0 +1,81 @@ +package ch.sbb.matsim.contrib.railsim.prototype.analysis; + +import ch.sbb.matsim.contrib.railsim.prototype.TrainEntersLink; +import ch.sbb.matsim.contrib.railsim.prototype.TrainEntersLinkEventHandler; +import ch.sbb.matsim.contrib.railsim.prototype.TrainLeavesLink; +import ch.sbb.matsim.contrib.railsim.prototype.TrainLeavesLinkEventHandler; +import ch.sbb.matsim.contrib.railsim.prototype.TrainPathEntersLink; +import ch.sbb.matsim.contrib.railsim.prototype.TrainPathEntersLinkEventHandler; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.LinkEnterEvent; +import org.matsim.api.core.v01.events.LinkLeaveEvent; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.vehicles.Vehicle; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Ihab Kaddoura + */ +public class TrainEventsHandler implements TrainEntersLinkEventHandler, TrainLeavesLinkEventHandler, TrainPathEntersLinkEventHandler { + + private EventsManager events; + + Map, Double> vehId2lastTimeStep = new HashMap<>(); + + public TrainEventsHandler(EventsManager events) { + this.events = events; + } + + @Override + public void handleEvent(TrainPathEntersLink event) { + Id pathId = Id.createVehicleId(event.getVehicleId() + "_viz_path"); + + if (isNewTimeStep(event.getTime(), pathId)) { + this.events.processEvent(new LinkEnterEvent(event.getTime(), pathId, event.getLinkId())); + this.vehId2lastTimeStep.put(pathId, event.getTime()); + } + + } + + @Override + public void handleEvent(TrainLeavesLink event) { + + Id trainId = Id.createVehicleId(event.getVehicleId() + "_viz_train"); + Id pathId = Id.createVehicleId(event.getVehicleId() + "_viz_path"); + + if (isNewTimeStep(event.getTime(), trainId)) { + this.events.processEvent(new LinkLeaveEvent(event.getTime(), trainId, event.getLinkId())); + this.vehId2lastTimeStep.put(trainId, event.getTime()); + } + + if (isNewTimeStep(event.getTime(), pathId)) { + this.events.processEvent(new LinkLeaveEvent(event.getTime(), pathId, event.getLinkId())); + this.vehId2lastTimeStep.put(pathId, event.getTime()); + + } + + } + + private boolean isNewTimeStep(double time, Id vehId) { + if (this.vehId2lastTimeStep.get(vehId) == null) { + return true; + } else if (this.vehId2lastTimeStep.get(vehId) == time) { + return false; + } else { + return true; + } + } + + @Override + public void handleEvent(TrainEntersLink event) { + Id trainId = Id.createVehicleId(event.getVehicleId() + "_viz_train"); + + if (isNewTimeStep(event.getTime(), trainId)) { + this.events.processEvent(new LinkEnterEvent(event.getTime(), trainId, event.getLinkId())); + this.vehId2lastTimeStep.put(trainId, event.getTime()); + } + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java new file mode 100644 index 00000000000..21bbfa72893 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java @@ -0,0 +1,112 @@ +package ch.sbb.matsim.contrib.railsim.prototype.analysis; + +import ch.sbb.matsim.contrib.railsim.prototype.TrainEntersLink; +import ch.sbb.matsim.contrib.railsim.prototype.TrainLeavesLink; +import ch.sbb.matsim.contrib.railsim.prototype.TrainPathEntersLink; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.utils.io.MatsimXmlParser; +import org.matsim.vehicles.Vehicle; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import java.util.Stack; + +/** + * @author ihab + */ +public class TrainEventsReader extends MatsimXmlParser { + + private static final String EVENT = "event"; + + private final EventsManager eventsManager; + + public TrainEventsReader(EventsManager events) { + super(); + this.eventsManager = events; + setValidating(false); // events-files have no DTD, thus they cannot validate + } + + @Override + public void startTag(String name, Attributes atts, Stack context) { + if (EVENT.equals(name)) { + startEvent(atts); + } + } + + @Override + public void endTag(String name, String content, Stack context) { + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + // ignore characters to prevent OutOfMemoryExceptions + /* the events-file only contains empty tags with attributes, + * but without the dtd or schema, all whitespace between tags is handled + * by characters and added up by super.characters, consuming huge + * amount of memory when large events-files are read in. + */ + } + + private void startEvent(final Attributes attributes) { + + String eventType = attributes.getValue("type"); + + Double time = 0.0; + Id linkId = null; + Id vehicleId = null; + + if (TrainEntersLink.EVENT_TYPE.equals(eventType)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (attributes.getQName(i).equals("time")) { + time = Double.parseDouble(attributes.getValue(i)); + } else if (attributes.getQName(i).equals("type")) { + eventType = attributes.getValue(i); + } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_LINK)) { + linkId = Id.create((attributes.getValue(i)), Link.class); + } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_VEHICLE)) { + vehicleId = Id.create((attributes.getValue(i)), Vehicle.class); + } else { + throw new RuntimeException("Unknown event attribute. Aborting..."); + } + } + this.eventsManager.processEvent(new TrainEntersLink(time, linkId, vehicleId)); + + } else if (TrainLeavesLink.EVENT_TYPE.equals(eventType)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (attributes.getQName(i).equals("time")) { + time = Double.parseDouble(attributes.getValue(i)); + } else if (attributes.getQName(i).equals("type")) { + eventType = attributes.getValue(i); + } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_LINK)) { + linkId = Id.create((attributes.getValue(i)), Link.class); + } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_VEHICLE)) { + vehicleId = Id.create((attributes.getValue(i)), Vehicle.class); + } else { + throw new RuntimeException("Unknown event attribute. Aborting..."); + } + } + this.eventsManager.processEvent(new TrainLeavesLink(time, linkId, vehicleId)); + + } else if (TrainPathEntersLink.EVENT_TYPE.equals(eventType)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (attributes.getQName(i).equals("time")) { + time = Double.parseDouble(attributes.getValue(i)); + } else if (attributes.getQName(i).equals("type")) { + eventType = attributes.getValue(i); + } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_LINK)) { + linkId = Id.create((attributes.getValue(i)), Link.class); + } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_VEHICLE)) { + vehicleId = Id.create((attributes.getValue(i)), Vehicle.class); + } else { + throw new RuntimeException("Unknown event attribute. Aborting..."); + } + } + this.eventsManager.processEvent(new TrainPathEntersLink(time, linkId, vehicleId)); + + } else { + // do not process other event types + } + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java new file mode 100644 index 00000000000..b27e56e98ce --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java @@ -0,0 +1,100 @@ +package ch.sbb.matsim.contrib.railsim.prototype.analysis; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Id; +import org.matsim.core.api.experimental.events.VehicleArrivesAtFacilityEvent; +import org.matsim.core.api.experimental.events.VehicleDepartsAtFacilityEvent; +import org.matsim.core.api.experimental.events.handler.VehicleArrivesAtFacilityEventHandler; +import org.matsim.core.api.experimental.events.handler.VehicleDepartsAtFacilityEventHandler; +import org.matsim.core.utils.collections.Tuple; +import org.matsim.core.utils.io.IOUtils; +import org.matsim.vehicles.Vehicle; + +import java.io.BufferedWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Ihab Kaddoura + */ +public class TransitEventHandler implements VehicleArrivesAtFacilityEventHandler, VehicleDepartsAtFacilityEventHandler { + private static final Logger log = LogManager.getLogger(TransitEventHandler.class); + + private final Map> vehicleFacilityDeparture2time2delay = new HashMap<>(); + private final Map> vehicleFacilityArrival2time2delay = new HashMap<>(); + private final Map, Double> vehicle2totalTraveltime = new HashMap<>(); + private final Map, Double> vehicle2previousDepartureTime = new HashMap<>(); + + @Override + public void handleEvent(VehicleDepartsAtFacilityEvent event) { + String vehicleFacility = event.getVehicleId().toString() + "---" + event.getFacilityId().toString(); + this.vehicleFacilityDeparture2time2delay.put(vehicleFacility, new Tuple<>(event.getTime(), event.getDelay())); + this.vehicle2previousDepartureTime.put(event.getVehicleId(), event.getTime()); + } + + @Override + public void handleEvent(VehicleArrivesAtFacilityEvent event) { + String vehicleFacility = event.getVehicleId().toString() + "---" + event.getFacilityId().toString(); + this.vehicleFacilityArrival2time2delay.put(vehicleFacility, new Tuple<>(event.getTime(), event.getDelay())); + + double tt = event.getTime() - this.vehicle2previousDepartureTime.getOrDefault(event.getVehicleId(), 0.); + double ttSoFar = this.vehicle2totalTraveltime.getOrDefault(event.getVehicleId(), 0.); + this.vehicle2totalTraveltime.put(event.getVehicleId(), ttSoFar + tt); + } + + /** + * @return the vehicleFacilityDeparture2time2delay + */ + public Map> getVehicleFacilityDeparture2time2delay() { + return vehicleFacilityDeparture2time2delay; + } + + /** + * @return the vehicleFacilityArrival2time2delay + */ + public Map> getVehicleFacilityArrival2time2delay() { + return vehicleFacilityArrival2time2delay; + } + + /** + * @param outputDirectory + * @param runId + */ + public void printResults(String outputDirectory, String runId) { + final String name = "transitAnalysis"; + + if (!outputDirectory.endsWith("/")) outputDirectory = outputDirectory + "/"; + + String outputFile = outputDirectory + runId + "." + name + ".csv"; + + BufferedWriter writer = IOUtils.getBufferedWriter(outputFile); + + try { + writer.write("vehicle;facility;type;time;delay"); + writer.newLine(); + + for (String vehicleFacility : vehicleFacilityDeparture2time2delay.keySet()) { + writer.write(vehicleFacility.split("---")[0] + ";" + vehicleFacility.split("---")[1] + ";" + "departure;" + vehicleFacilityDeparture2time2delay.get(vehicleFacility).getFirst() + ";" + vehicleFacilityDeparture2time2delay.get(vehicleFacility).getSecond()); + writer.newLine(); + } + for (String vehicleFacility : vehicleFacilityArrival2time2delay.keySet()) { + writer.write(vehicleFacility.split("---")[0] + ";" + vehicleFacility.split("---")[1] + ";" + "arrival;" + vehicleFacilityArrival2time2delay.get(vehicleFacility).getFirst() + ";" + vehicleFacilityArrival2time2delay.get(vehicleFacility).getSecond()); + writer.newLine(); + } + writer.close(); + + log.info("Text info written to file."); + } catch (Exception e) { + log.warn("Text info not written to file."); + } + } + + /** + * @return the vehicle2totalTraveltime + */ + public Map, Double> getVehicle2totalTraveltime() { + return vehicle2totalTraveltime; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java new file mode 100644 index 00000000000..651b7d8b5f8 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java @@ -0,0 +1,72 @@ +package ch.sbb.matsim.contrib.railsim.prototype.demo; + +import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.TransitEventHandler; +import org.matsim.api.core.v01.Scenario; +import org.matsim.contrib.otfvis.OTFVisLiveModule; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.vis.otfvis.OTFVisConfigGroup; + +public class RunDemo { + + public static void main(String[] args) { + + System.setProperty("matsim.preferLocalDtds", "true"); +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml"}; // one direction +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml"}; // two directions +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml"}; // two directions, several trains + String[] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml"}; // T shaped network +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml"}; // Genf-Bern +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml"}; // very short links +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml"}; // crossing +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml"}; // one corridor, very short links +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml"}; // advanced crossing, same links +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml"}; // advanced crossing, crossing... +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml"}; // simple crossing, different links +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml"}; // simplified GE-BN situation, capacity = 2 +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml"}; // simplified GE-BN situation, capacity = 1 +// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml"}; // one direction, passing queue + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory("contribs/railsim/test/output/ch/sbb/matsim/contrib/railsim/RunRailsimTest/demo/output"); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + OTFVisConfigGroup otfvisCfg = ConfigUtils.addOrGetModule(config, OTFVisConfigGroup.class); + otfvisCfg.setDrawNonMovingItems(false); + otfvisCfg.setDrawTime(true); + otfvisCfg.setLinkWidth(20); + otfvisCfg.setLinkWidthIsProportionalTo("capacity"); + otfvisCfg.setColoringScheme(OTFVisConfigGroup.ColoringScheme.standard); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.addOverridingModule(new OTFVisLiveModule()); + controler.run(); + + // read events + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), config.controler().getOutputDirectory()); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java new file mode 100644 index 00000000000..dff6c450f06 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java @@ -0,0 +1,286 @@ +package ch.sbb.matsim.contrib.railsim.prototype.prepare; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.network.io.NetworkWriter; +import org.matsim.pt.transitSchedule.api.Departure; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitRouteStop; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Ihab Kaddoura + */ +public class AdjustNetworkToSchedule { + private static final Logger log = LogManager.getLogger(AdjustNetworkToSchedule.class); + + private final Scenario scenario; + private final Set> stopLinkIds = new HashSet<>(); + private final RailsimConfigGroup railsimConfigGroup; + + private boolean throwExceptionIfMaximumVehicleVelocityIsViolated = false; + + /** + * @param scenario + */ + public AdjustNetworkToSchedule(Scenario scenario) { + this.scenario = scenario; + this.railsimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); + } + + public void run() { + + log.info("Adjust network speed levels to the transit schedule..."); + + // first store the information which link is a stop link + for (TransitStopFacility facility : scenario.getTransitSchedule().getFacilities().values()) { + stopLinkIds.add(facility.getLinkId()); + } + + // now adjust the travel times for each transit line, transit route, stop to stop segment... + for (TransitLine line : scenario.getTransitSchedule().getTransitLines().values()) { + for (TransitRoute route : line.getRoutes().values()) { + + log.debug("+++++++ Transit route: " + route.getId().toString() + " ++++++++"); + + // identify vehicle types + VehicleType vehicleType = null; + for (Departure departure : route.getDepartures().values()) { + Vehicle vehicle = this.scenario.getTransitVehicles().getVehicles().get(departure.getVehicleId()); + if (vehicleType == null) { + vehicleType = vehicle.getType(); + } else { + if (vehicleType == vehicle.getType()) { + // same vehicle type + } else { + throw new RuntimeException("Different vehicle types detected within a single transit route: " + route.getId()); + } + } + } + + Double previousDeparture = null; + Id previousStopLink = null; + for (TransitRouteStop stop : route.getStops()) { + if (previousDeparture == null) { + previousDeparture = stop.getDepartureOffset().seconds(); + previousStopLink = stop.getStopFacility().getLinkId(); + + } else { + double ttSchedule = stop.getArrivalOffset().seconds() - previousDeparture; + + if (ttSchedule <= 0) { + log.warn("Implausible travel time in schedule: " + ttSchedule + " / arrival: " + stop.getArrivalOffset().seconds() + " / previous departure: " + previousDeparture); + log.warn("Line: " + line.getId() + " Route: " + route.getId() + " Stop: " + stop.getStopFacility().getId()); + + if (ttSchedule < 0) throw new RuntimeException("Invalid travel time. Aborting..."); + // TODO: do not throw a runtime exception in the case of ttSchedule == 0; otherwise a test fails... + } + + List> routeLinks = getRouteLinks(route, previousStopLink, stop.getStopFacility().getLinkId()); + adjustLinks(route.getId(), line.getId(), routeLinks, ttSchedule, vehicleType); + previousStopLink = stop.getStopFacility().getLinkId(); + + if (stop.getDepartureOffset().isDefined()) { + previousDeparture = stop.getDepartureOffset().seconds(); + } else { + previousDeparture = Double.MAX_VALUE; + } + } + } + } + } + + new NetworkWriter(scenario.getNetwork()).write(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTrainNetwork.xml"); + } + + private void adjustLinks(Id routeId, Id lineId, List> routeLinks, double ttSchedule, VehicleType vehicleType) { + log.info("++++++++++++++++++"); + log.info("Line: " + lineId); + log.info("Route: " + routeId); + log.info("Vehicle type: " + vehicleType.getId()); + log.info("Route links: " + routeLinks.toString()); + + final double ttNetwork = computeRouteTravelTime(routeLinks, lineId, routeId, vehicleType.getId()); + + if (isSame(ttNetwork, ttSchedule)) { + // no delays and no early arrivals, nothing to do. + + } else { + + // Start with the easiest implementation: set the speed equally for each link + // In a later step and depending on the available data, we may come up with + // a more elaborated approach. + // If we already have several smaller links, we may account for the acceleration and deceleration + // and apply a function, e.g. lower speeds behind/before stops and so on... + double requiredSpeed = this.computeRouteTravelDistance(routeLinks) / ttSchedule; + + if (requiredSpeed > vehicleType.getMaximumVelocity()) { + log.warn("The required speed is larger than the maximum velocity of the vehicle."); + log.warn("ttNetwork: " + ttNetwork + " ttSchedule: " + ttSchedule); + log.warn("Required speed is above vehicle maximum velocity."); + log.warn("required speed: " + requiredSpeed); + log.warn("vehicle maximum velocity: " + vehicleType.getMaximumVelocity()); + + if (this.throwExceptionIfMaximumVehicleVelocityIsViolated) { + throw new RuntimeException("Aborting..."); + } else { + log.warn("Continuing anyway..."); + } + } + + Network network = this.scenario.getNetwork(); + + for (Id linkId : routeLinks) { + Link link = network.getLinks().get(linkId); + + if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine) { + if (link.getAttributes().getAttribute(lineId.toString()) == null) { + link.getAttributes().putAttribute(lineId.toString(), requiredSpeed); + } else { + double previousValue = (double) link.getAttributes().getAttribute(lineId.toString()); + if (previousValue == requiredSpeed) { + // ok + } else { + log.warn("link: " + linkId); + log.warn("previous value: " + previousValue); + log.warn("required speed: " + requiredSpeed); + throw new RuntimeException("Link attribute already set for line " + lineId + ". Maybe try " + RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute + " instead! Aborting..."); + } + } + } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute) { + if (link.getAttributes().getAttribute(lineId.toString() + "+++" + routeId.toString()) == null) { + link.getAttributes().putAttribute(lineId.toString() + "+++" + routeId.toString(), requiredSpeed); + } else { + double previousValue = (double) link.getAttributes().getAttribute(lineId.toString() + "+++" + routeId.toString()); + if (previousValue == requiredSpeed) { + // ok + } else { + log.warn("previous value: " + previousValue); + log.warn("required speed: " + requiredSpeed); + throw new RuntimeException("Link attribute already set for line " + lineId + " and route " + routeId + ". Aborting..."); + } + } + } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachVehicleType) { + if (link.getAttributes().getAttribute(vehicleType.getId().toString()) == null) { + link.getAttributes().putAttribute(vehicleType.getId().toString(), requiredSpeed); + } else { + double previousValue = (double) link.getAttributes().getAttribute(vehicleType.getId().toString()); + if (previousValue == requiredSpeed) { + // ok + } else { + log.warn("previous value: " + previousValue); + log.warn("required speed: " + requiredSpeed); + throw new RuntimeException("Link attribute already set for vehicleType " + vehicleType.getId() + ". Maybe try " + RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine + " instead! Aborting..."); + } + } + } else { + throw new RuntimeException("AdjustNetworkToSchedule does not work for trainSpeedApproach " + railsimConfigGroup.getTrainSpeedApproach().toString() + " Aborting..."); + } + } + + // re-compute the network travel time... + double ttNetworkAfterNetworkAdjustment = computeRouteTravelTime(routeLinks, lineId, routeId, vehicleType.getId()); + if (isSame(ttNetworkAfterNetworkAdjustment, ttSchedule)) { + // network adjustment successful + + } else { + log.warn("ttNetwork (before): " + ttNetwork + " / ttNetwork (after): " + ttNetworkAfterNetworkAdjustment + " / ttSchedule: " + ttSchedule); + throw new RuntimeException("Network travel time is still different from schedule travel time. Aborting..."); + } + } + } + + /** + * @param ttNetwork + * @param ttSchedule + * @return + */ + private boolean isSame(double ttNetwork, double ttSchedule) { + return Math.abs(ttNetwork - ttSchedule) <= 1.; + } + + /** + * @param route + * @param fromStopLinkId + * @param toStopLinkId + * @return a snippet of the transit route which contains all links between the provided from and to link (excluding the from stop and including the to stop) + */ + private List> getRouteLinks(TransitRoute route, Id fromStopLinkId, Id toStopLinkId) { + List> routeSnippet = new ArrayList<>(); + + List> transitRouteInclFirstAndLastLink = new ArrayList<>(); + transitRouteInclFirstAndLastLink.add(route.getRoute().getStartLinkId()); + transitRouteInclFirstAndLastLink.addAll(route.getRoute().getLinkIds()); + transitRouteInclFirstAndLastLink.add(route.getRoute().getEndLinkId()); + + boolean putInList = false; + for (Id linkId : transitRouteInclFirstAndLastLink) { + + if (putInList) { + routeSnippet.add(linkId); + } + + if (linkId.toString().equals(fromStopLinkId.toString())) { + putInList = true; + } + + if (linkId.toString().equals(toStopLinkId.toString())) { + break; + } + } + + if (routeSnippet.size() < 2) { + log.warn("fromLink: " + fromStopLinkId + " --> toLink: " + toStopLinkId); + log.warn("Route: " + route.getRoute().toString()); + throw new RuntimeException("Route snippet should at least have two links. Aborting... " + routeSnippet.toString()); + } + + return routeSnippet; + } + + private double computeRouteTravelTime(List> routeLinks, Id lineId, Id routeId, Id vehicleType) { + double ttNetwork = 0.; + for (Id linkId : routeLinks) { + Link link = this.scenario.getNetwork().getLinks().get(linkId); + double linkFreespeed = Double.NEGATIVE_INFINITY; + if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine) { + linkFreespeed = RailsimUtils.getLinkFreespeedForTransitLine(lineId, link); + } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachVehicleType) { + linkFreespeed = RailsimUtils.getLinkFreespeedForVehicleType(vehicleType, link); + } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute) { + linkFreespeed = RailsimUtils.getLinkFreespeedForTransitLineAndTransitRoute(lineId, routeId, link); + } else { + linkFreespeed = link.getFreespeed(); + } + double ttLink = link.getLength() / linkFreespeed; + ttNetwork += ttLink; + } + return ttNetwork; + } + + private double computeRouteTravelDistance(List> routeLinks) { + double distance = 0.; + for (Id linkId : routeLinks) { + Link link = this.scenario.getNetwork().getLinks().get(linkId); + double distanceLink = link.getLength(); + distance += distanceLink; + } + return distance; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java new file mode 100644 index 00000000000..a2521cc8ed3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java @@ -0,0 +1,99 @@ +package ch.sbb.matsim.contrib.railsim.prototype.prepare; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.network.io.NetworkWriter; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Ihab Kaddoura + */ +public class DistributeCapacities { + private static final Logger log = LogManager.getLogger(DistributeCapacities.class); + + private final Scenario scenario; + private final Set> stopLinkIds = new HashSet<>(); + + /** + * @param scenario + */ + public DistributeCapacities(Scenario scenario) { + this.scenario = scenario; + } + + public void run() { + + log.info("Adjust network speed levels to the transit schedule..."); + + // first store the information which link is a stop link + for (TransitStopFacility facility : scenario.getTransitSchedule().getFacilities().values()) { + stopLinkIds.add(facility.getLinkId()); + } + + for (Link link : scenario.getNetwork().getLinks().values()) { + if (stopLinkIds.contains(link.getId())) { + // bhf + + // inLinks + int linkCapacity = RailsimUtils.getTrainCapacity(link); + + reduceCapacityOnAllInLinks(link, linkCapacity); + reduceCapacityOnAllOutLinks(link, linkCapacity); + + } + } + + new NetworkWriter(scenario.getNetwork()).write(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTrainNetwork_capacities.xml.gz"); + } + + /** + * @param link + * @param capacity + */ + private void reduceCapacityOnAllOutLinks(Link link, int capacity) { + if (capacity == 1) { + // can't further decrease the capacity + } else { + for (Link outLink : link.getToNode().getOutLinks().values()) { + int outLinkCapacity = RailsimUtils.getTrainCapacity(outLink); + + if (outLinkCapacity > capacity || stopLinkIds.contains(outLink.getId())) { + // stop + } else { + // in all other cases: reduce capacity by one. + int updatedCapacity = capacity - 1; + outLink.getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_CAPACITY, updatedCapacity); + reduceCapacityOnAllOutLinks(outLink, updatedCapacity); + } + } + } + + } + + private void reduceCapacityOnAllInLinks(Link link, int capacity) { + if (capacity == 1) { + // can't further decrease the capacity + } else { + for (Link inLink : link.getFromNode().getInLinks().values()) { + int inLinkCapacity = RailsimUtils.getTrainCapacity(inLink); + + if (inLinkCapacity > capacity || stopLinkIds.contains(inLink.getId())) { + // stop + } else { + // in all other cases: reduce capacity by one. + int updatedCapacity = capacity - 1; + inLink.getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_CAPACITY, updatedCapacity); + reduceCapacityOnAllInLinks(inLink, updatedCapacity); + } + } + } + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java new file mode 100644 index 00000000000..f3b9ad1d1f1 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java @@ -0,0 +1,250 @@ +package ch.sbb.matsim.contrib.railsim.prototype.prepare; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.LineSegment; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.matsim.utils.objectattributes.attributable.Attributes; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Ihab Kaddoura + */ +public class SplitTransitLinks { + private static final Logger log = LogManager.getLogger(SplitTransitLinks.class); + + private final HashMap>> connectedLinks = new HashMap<>(); + private final Scenario scenario; + + /** + * @param scenario + */ + public SplitTransitLinks(Scenario scenario) { + this.scenario = scenario; + } + + /** + * Splits all links in the network into smaller link segments and adjusts the network routes in the transit schedule. + * Network links which have a transit stop facility are skipped. + * + * @param maximumLinkLength + */ + public void run(double maximumLinkLength) { + Set> stopLinkIds = new HashSet<>(); + for (TransitStopFacility facility : scenario.getTransitSchedule().getFacilities().values()) { + stopLinkIds.add(facility.getLinkId()); + } + + Map, List>> link2splitLinks = new HashMap<>(); + + List linksFromNetwork = new ArrayList<>(); + for (Link link : scenario.getNetwork().getLinks().values()) { + linksFromNetwork.add(link); + } + + for (Link link : linksFromNetwork) { + if (stopLinkIds.contains(link.getId())) { + log.info("Skipping link " + link.getId() + " (transit stop)"); + + } else if (RailsimUtils.getOppositeDirectionLink(link, scenario.getNetwork()) != null) { + log.info("Skipping link " + link.getId() + " (one direction track)"); + // TODO: Once we have the use case, allow for splitting these links (and make sure the one direction logic is correctly transferred to all link segments...) + + } else { + if (link.getLength() > maximumLinkLength) { + log.info("Splitting link " + link.getId()); + List> links = connectNodes(link.getId().toString(), link.getFromNode(), link.getToNode(), link.getFreespeed(), link.getLength(), maximumLinkLength, link.getAttributes()); + link2splitLinks.put(link.getId(), links); + } else { + log.info("Skipping link " + link.getId() + " (below maximumLength)"); + } + } + } + + for (Id linkId : link2splitLinks.keySet()) { + // remove old link from the scenario + this.scenario.getNetwork().removeLink(linkId); + + // replace old link in all transit routes + for (TransitLine transitLine : this.scenario.getTransitSchedule().getTransitLines().values()) { + for (TransitRoute transitRoute : transitLine.getRoutes().values()) { + + + NetworkRoute networkRoute = transitRoute.getRoute(); + + List> replacedLinkIds = new ArrayList<>(); + + for (Id linkIdInNetworkRoute : networkRoute.getLinkIds()) { + if (linkIdInNetworkRoute.toString().equals(linkId.toString())) { + replacedLinkIds.addAll(link2splitLinks.get(linkIdInNetworkRoute)); + } else { + replacedLinkIds.add(linkIdInNetworkRoute); + } + } + + networkRoute.setLinkIds(networkRoute.getStartLinkId(), replacedLinkIds, networkRoute.getEndLinkId()); + } + } + } + +// new NetworkWriter(scenario.getNetwork()).write(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTrainNetwork.xml"); +// new TransitScheduleWriter(scenario.getTransitSchedule()).writeFile(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTransitSchedule.xml"); +// new MatsimVehicleWriter(scenario.getTransitVehicles()).writeFile(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTransitVehicles.xml"); + } + + /** + * Connects two nodes with link(s) + *

+ * Creates segmented links between nodes. Each segment is itself a link with maximum length (minimumLinkLength / euclideanDistance). + * + * @param name the name of the link. + * @param fromNode the source node. + * @param toNode the target node. + * @param speedLevel the maximum speed level allowed on the link. + * @param originalLinkLength the original link length (set to <= 0 to use the euclidean distance) + * @param linkSegmentLength the length of the link segments. + * @param attributes + * @return A list holding the link ids between the connected nodes. + */ + public List> connectNodes(String name, Node fromNode, Node toNode, double speedLevel, double originalLinkLength, double linkSegmentLength, Attributes attributes) { + + // lookup if link already exists + String nodeIds = fromNode.getId().toString() + "_" + toNode.getId().toString(); + List> links = connectedLinks.get(nodeIds); + if (links != null) { + log.info("Link already exists, skipping " + nodeIds); + return links; + } + + // create links + log.info("Create link " + nodeIds); + + List nodes = new ArrayList<>(); + double distance = originalLinkLength; + if (originalLinkLength <= 0.) { + distance = NetworkUtils.getEuclideanDistance(fromNode.getCoord(), toNode.getCoord()); + } + if (distance <= linkSegmentLength) { + nodes.add(fromNode); + nodes.add(toNode); + } else { + // add first node + nodes.add(fromNode); + + // add additional nodes in between... + + int nodeCounter = 0; + for (double fraction = linkSegmentLength / distance; fraction < 1.; ) { + LineSegment ls = new LineSegment(fromNode.getCoord().getX(), fromNode.getCoord().getY(), toNode.getCoord().getX(), toNode.getCoord().getY()); + Coordinate point = ls.pointAlong(fraction); + Coord coord = new Coord(point.x, point.y); + String idBetweenNode = fromNode.getId().toString() + "-" + toNode.getId().toString() + "-" + nodeCounter; +// log.info("Adding 'between node' " + idBetweenNode); + Node betweenNode = addNode(idBetweenNode, coord); + nodes.add(betweenNode); + + fraction = fraction + linkSegmentLength / distance; + nodeCounter++; + } + + // add last node + nodes.add(toNode); + } + + links = connectStopsAndGetRailLinks(name, nodes, speedLevel, originalLinkLength, attributes); + connectedLinks.put(nodeIds, links); + return links; + } + + private List> connectStopsAndGetRailLinks(String name, List nodes, double speedLevel, double originalLinkLength, Attributes attributes) { + + if (nodes.size() < 2) throw new RuntimeException("At least two route stops required. Aborting..."); + + List> railLinks = new ArrayList<>(); + + log.info("Connecting nodes..."); + + double length = originalLinkLength / (nodes.size() - 1.); + length = Math.round(length); + if (length <= 0.) { + log.warn("An euclidean distance of " + length + " is not accepted."); + length = 5.; + log.warn("... the length will be set to " + length); + } + + int linkCounter = 0; + + Node previousNode = null; + for (Node node : nodes) { + + if (previousNode == null) { + // first node + } else { + // not the first terminal + // add connecting link + Link connectingLink = getOrCreateLink(name, linkCounter, previousNode, node, speedLevel, length, attributes); + linkCounter++; + + railLinks.add(connectingLink.getId()); + + } + previousNode = node; + } + return railLinks; + } + + public Node addNode(String name, Coord coord) { + + Id id = Id.createNodeId(name); + + if (this.scenario.getNetwork().getNodes().get(id) != null) { + return this.scenario.getNetwork().getNodes().get(id); + } + + Node node = this.scenario.getNetwork().getFactory().createNode(id, coord); + this.scenario.getNetwork().addNode(node); + return node; + } + + private Link getOrCreateLink(String name, int linkCounter, Node fromNode, Node toNode, double freespeed, double length, Attributes attributes) { + + if (length <= 0.) throw new RuntimeException("Length " + length + " not accepted. Aborting..."); + + Id linkId = Id.create(name + "_" + linkCounter, Link.class); + + if (this.scenario.getNetwork().getLinks().get(linkId) != null) { + return this.scenario.getNetwork().getLinks().get(linkId); + } + + Link link = this.scenario.getNetwork().getFactory().createLink(linkId, fromNode, toNode); + link.setAllowedModes(new HashSet<>(List.of("rail"))); + link.setLength(length); + link.setFreespeed(freespeed); + link.setCapacity(3600.); + link.setNumberOfLanes(1.); + for (String attribute : attributes.getAsMap().keySet()) { + link.getAttributes().putAttribute(attribute, attributes.getAttribute(attribute)); + } + this.scenario.getNetwork().addLink(link); + + return link; + } + +} diff --git a/contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java b/contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java new file mode 100644 index 00000000000..1f2c61a93bd --- /dev/null +++ b/contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java @@ -0,0 +1,64 @@ +package org.matsim.core.mobsim.qsim.qnetsimengine; + +import com.google.inject.Inject; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Node; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimLinkSpeedCalculator; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.mobsim.framework.MobsimTimer; +import org.matsim.core.mobsim.qsim.interfaces.AgentCounter; +import org.matsim.core.mobsim.qsim.qnetsimengine.QNetsimEngineI.NetsimInternalInterface; +import org.matsim.vis.snapshotwriters.SnapshotLinkWidthCalculator; + +public final class QRailsimSignalsNetworkFactory implements QNetworkFactory { + private final QNetworkFactory delegate; + private RailsimLinkSpeedCalculator linkSpeedCalculator; + private final Scenario scenario; + private final EventsManager events; + private NetsimEngineContext context; + private NetsimInternalInterface netsimEngine; + + @Inject + public QRailsimSignalsNetworkFactory(Scenario scenario, EventsManager events) { + this.scenario = scenario; + this.events = events; + + // TODO throw runtime exception if lanes is switched on... + this.delegate = new QSignalsNetworkFactory(scenario, events); + } + + @Override + public void initializeFactory(AgentCounter agentCounter, MobsimTimer mobsimTimer, NetsimInternalInterface simEngine1) { + SnapshotLinkWidthCalculator linkWidthCalculator = new SnapshotLinkWidthCalculator(); + linkWidthCalculator.setLinkWidthForVis(scenario.getConfig().qsim().getLinkWidthForVis()); + linkWidthCalculator.setLaneWidth(scenario.getNetwork().getEffectiveLaneWidth()); + + AbstractAgentSnapshotInfoBuilder agentSnapshotInfoBuilder = AbstractQNetsimEngine.createAgentSnapshotInfoBuilder(scenario, linkWidthCalculator); + + this.netsimEngine = simEngine1; + this.context = new NetsimEngineContext(events, scenario.getNetwork().getEffectiveCellSize(), agentCounter, agentSnapshotInfoBuilder, scenario.getConfig().qsim(), mobsimTimer, linkWidthCalculator); + + delegate.initializeFactory(agentCounter, mobsimTimer, simEngine1); + } + + @Override + public QNodeI createNetsimNode(Node node) { + return delegate.createNetsimNode(node); + } + + @Override + public QLinkI createNetsimLink(Link link, QNodeI queueNode) { + QLinkImpl.Builder linkBuilder = new QLinkImpl.Builder(context, netsimEngine); + linkBuilder.setLinkSpeedCalculator(this.linkSpeedCalculator); + return linkBuilder.build(link, queueNode); + } + + /** + * @param calculator + */ + public void setLinkSpeedCalculator(RailsimLinkSpeedCalculator calculator) { + this.linkSpeedCalculator = calculator; + } + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java new file mode 100644 index 00000000000..386075f3b7e --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java @@ -0,0 +1,106 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; +import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; +import org.apache.logging.log4j.LogManager; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainAccelerationApproach; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainSpeedApproach; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.testcases.MatsimTestUtils; + +/** + * @author Ihab Kaddoura + */ +public class RunRailsimAdvancedCorridorTest { + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public final void test0() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // one direction + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setSplitLinks(true); + rscfg.setSplitLinksLength(200.); + rscfg.setTrainAccelerationApproach(TrainAccelerationApproach.speedOnPreviousLink); + rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachVehicleType); + + Scenario scenario = RunRailsim.prepareScenario(config); + + // set minimum time for one link + scenario.getNetwork().getLinks().get(Id.createLinkId("15")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 30 * 60.); + + // set different maximum speed levels for different train types + for (Link link : scenario.getNetwork().getLinks().values()) { + link.getAttributes().putAttribute("Express", 160. / 3.6); + link.getAttributes().putAttribute("Regio", 160. / 3.6); + link.getAttributes().putAttribute("Cargo", 80. / 3.6); + } + +// List linksToAdd = new ArrayList<>(); +// for (Link link : scenario.getNetwork().getLinks().values()) { +// Link linkR = scenario.getNetwork().getFactory().createLink(Id.createLinkId(link.getId().toString() + "_r"), link.getToNode(), link.getFromNode()); +// linkR.setAllowedModes(link.getAllowedModes()); +// linkR.setCapacity(link.getCapacity()); +// linkR.setLength(link.getLength()); +// linkR.setFreespeed(link.getFreespeed()); +// linkR.setNumberOfLanes(link.getNumberOfLanes()); +// for (String attribute : link.getAttributes().getAsMap().keySet()) { +// linkR.getAttributes().putAttribute(attribute, link.getAttributes().getAttribute(attribute)); +// } +// +// linksToAdd.add(linkR); +// } +// for (Link linkR : linksToAdd) { +// scenario.getNetwork().addLink(linkR); +// } + + // add some one direction links +// scenario.getNetwork().getLinks().get(Id.createLinkId("20")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "20_r"); +// scenario.getNetwork().getLinks().get(Id.createLinkId("20_r")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "20"); + +// scenario.getNetwork().getLinks().get(Id.createLinkId("36")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "36_r"); +// scenario.getNetwork().getLinks().get(Id.createLinkId("36_r")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "36"); + +// scenario.getNetwork().getLinks().get(Id.createLinkId("DUG")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "DUG_r"); +// scenario.getNetwork().getLinks().get(Id.createLinkId("DUG_r")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "DUG"); + + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + } catch (Exception ee) { + ee.printStackTrace(); + LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java new file mode 100644 index 00000000000..330860e5ea6 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java @@ -0,0 +1,1066 @@ +package ch.sbb.matsim.contrib.railsim.prototype; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainAccelerationApproach; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainSpeedApproach; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.TransitEventHandler; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.testcases.MatsimTestUtils; + +import java.util.stream.Collectors; + +/** + * @author Ihab Kaddoura + */ +public class RunRailsimTest { + + private static final Logger log = LogManager.getLogger(RunRailsimTest.class); + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public final void test0() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // one direction + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.qsim().setNumberOfThreads(1); + config.global().setNumberOfThreads(1); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + // double totalTT = 0.; + // for (Double tt : transitEventHandler.getVehicle2totalTraveltime().values()) { + // totalTT += tt; + // } + + // Ist immer noch nicht deterministisch... :-( + + // Assert.assertEquals("Total travel time has changed.", 433780., totalTT, MatsimTestUtils.EPSILON); + // + // Assert.assertEquals("Arrival time has changed.", 43337., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train10---t2").getFirst(), MatsimTestUtils.EPSILON); + // Assert.assertEquals("Arrival time has changed.", 50610., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train10---t3").getFirst(), MatsimTestUtils.EPSILON); + // Assert.assertEquals("Arrival time has changed.", 43337., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train10---t2").getFirst(), MatsimTestUtils.EPSILON); + + } catch (Exception ee) { + ee.printStackTrace(); + LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test1() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // two directions, two trains in same time step + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setSplitLinks(true); + rscfg.setSplitLinksLength(1000.); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 32473., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t2_A-B").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 32620., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t2_B-A").getFirst(), MatsimTestUtils.EPSILON); + + // also make sure ethe vehicles arrive at the end of the route + Assert.assertEquals("Arrival time has changed.", 36146., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); + Assert.assertEquals("Arrival time has changed.", 28800., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t3_B-A").getFirst(), MatsimTestUtils.EPSILON); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test2() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // two directions, several trains in different time steps + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setSplitLinks(true); + rscfg.setSplitLinksLength(1000.); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + Assert.assertEquals("Arrival time has changed.", 36147., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); + Assert.assertEquals("Arrival time has changed.", 36269., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); + Assert.assertEquals("Arrival time has changed.", 36389., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); + Assert.assertEquals("Arrival time has changed.", 36509., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train4---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); + Assert.assertEquals("Arrival time has changed.", 36629., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train5---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test3() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // T shaped network + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setAccelerationGlobalDefault(Double.MAX_VALUE); + rscfg.setDecelerationGlobalDefault(Double.MAX_VALUE); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 32474., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t2_A-B").getFirst(), 1.); + Assert.assertEquals("Arrival time has changed.", 32473., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t2_A-B").getFirst(), 1.); + Assert.assertEquals("Arrival time has changed.", 32547., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---t2_A-B").getFirst(), 1.); + Assert.assertEquals("Arrival time has changed.", 32547., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---t2_A-B").getFirst(), 1.); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test4() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // Genf-Bern + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + System.out.println(transitEventHandler.getVehicleFacilityArrival2time2delay().keySet().stream().sorted().collect(Collectors.toList())); + Assert.assertEquals("Arrival time has changed.", 4121., transitEventHandler.getVehicleFacilityArrival2time2delay().get("Expresszug_GE_BE_train_0---lausanne").getFirst(), 8.0); + Assert.assertEquals("Arrival time has changed.", 48614., transitEventHandler.getVehicleFacilityArrival2time2delay().get("Expresszug_BE_GE_train_8---genf").getFirst(), 8.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test5() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // short links, long train blocking beyond several links + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setAccelerationGlobalDefault(Double.MAX_VALUE); + rscfg.setDecelerationGlobalDefault(Double.MAX_VALUE); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 36257., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t3_A-B").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test6() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // short links, long train blocking beyond several links + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 30086., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---19-20").getFirst(), MatsimTestUtils.EPSILON); + Assert.assertEquals("Arrival time has changed.", 29977., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---12-13").getFirst(), MatsimTestUtils.EPSILON); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test7() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simple corridor, small links + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29547., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29593., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + // with very slow acceleration + @Test + public final void test7a() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getClassInputDirectory() + "test7/config.xml"}; // simple corridor, small links + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setAccelerationGlobalDefault(0.1); + rscfg.setDecelerationGlobalDefault(0.1); + rscfg.setTrainAccelerationApproach(TrainAccelerationApproach.euclideanDistanceBetweenStops); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 31187., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 5.0); + Assert.assertEquals("Arrival time has changed.", 31290., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 5.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + // with vehicle-specific values provided in the link attributes + @Test + public final void test7b() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getClassInputDirectory() + "test7/config.xml"}; // simple corridor, small links + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachVehicleType); + + Scenario scenario = RunRailsim.prepareScenario(config); + + scenario.getNetwork().getLinks().get(Id.createLinkId("10-11")).getAttributes().putAttribute("trainType1", 1.2345); + scenario.getNetwork().getLinks().get(Id.createLinkId("10-11")).getAttributes().putAttribute("trainType2", 9.8765); + + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29626., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29671., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + // with vehicle-specific values provided in the link attributes + @Test + public final void test7c() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getClassInputDirectory() + "test7/config.xml"}; // simple corridor, small links + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachLine); + + Scenario scenario = RunRailsim.prepareScenario(config); + + scenario.getNetwork().getLinks().get(Id.createLinkId("10-11")).getAttributes().putAttribute("line1", 1.111); + + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29635., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29761., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test8() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // complex intersection + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 30600., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29258., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103224_9").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + /** + * Similar to test8 but with modification of the network: adding a link with a limited zugfolgezeit... + */ + @Test + public final void test8a() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getClassInputDirectory() + "test8/config.xml"}; // complex intersection + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + scenario.getNetwork().getLinks().get(Id.createLinkId("-103251_14")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); + scenario.getNetwork().getLinks().get(Id.createLinkId("-103224_8")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); + + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 30600., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 2.0); + Assert.assertEquals("Arrival time has changed.", 29325., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103224_9").getFirst(), 2.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test9() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // complex intersection, kreuzende Fahrwege ohne gleiche Kanten + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29638., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2----103224_19").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 30852., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 2.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + /** + * Similar to test9 but with modified network: adding Zugfolgezeit attribute. + */ + @Test + public final void test9a() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getClassInputDirectory() + "test9/config.xml"}; // complex intersection, kreuzende Fahrwege ohne gleiche Kanten + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + scenario.getNetwork().getLinks().get(Id.createLinkId("-103251_14")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); + scenario.getNetwork().getLinks().get(Id.createLinkId("-103224_8")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29638., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2----103224_19").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 31035., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test10() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simple intersection, kreuzende Fahrwege ohne gleiche Kanten + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29601., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---B0").getFirst(), MatsimTestUtils.EPSILON); + Assert.assertEquals("Arrival time has changed.", 28801., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---C0").getFirst(), MatsimTestUtils.EPSILON); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test11() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simplified BN-GE situation + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29100., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---B").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29348., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---X").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29468., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---A").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29366., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---A").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test12() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simplified BN-GE situation + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 29100., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---B").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29402., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---X").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29468., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---A").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 29950., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---A").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test13() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // passingQ + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + Assert.assertEquals("Arrival time has changed.", 39827., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---7-8").getFirst(), 1.0); + Assert.assertEquals("Arrival time has changed.", 35825., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---7-8").getFirst(), 1.0); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + @Test + public final void test14() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // Strecke mti Depot + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + // Assert.assertEquals("Arrival time has changed.", 29100., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---B").getFirst(), MatsimTestUtils.EPSILON); + // Assert.assertEquals("Arrival time has changed.", 29584., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---X").getFirst(), MatsimTestUtils.EPSILON); + // Assert.assertEquals("Arrival time has changed.", 29468., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---A").getFirst(), MatsimTestUtils.EPSILON); + // Assert.assertEquals("Arrival time has changed.", 29650., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---A").getFirst(), MatsimTestUtils.EPSILON); + + } catch (Exception ee) { + ee.printStackTrace(); + log.fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java new file mode 100644 index 00000000000..23f4cb92689 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java @@ -0,0 +1,91 @@ +package ch.sbb.matsim.contrib.railsim.prototype.prepare; + +import ch.sbb.matsim.contrib.railsim.prototype.prepare.AdjustNetworkToSchedule; +import org.apache.logging.log4j.LogManager; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Scenario; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainSpeedApproach; +import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.TransitEventHandler; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.testcases.MatsimTestUtils; + +/** + * @author Ihab Kaddoura + */ +public class AdjustNetworkToScheduleTest { + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public final void test0() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.network().setInputFile("transitNetwork.xml.gz"); + config.transit().setTransitScheduleFile("transitSchedule.xml.gz"); + config.transit().setVehiclesFile("transitVehicles.xml.gz"); + + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.qsim().setNumberOfThreads(1); + config.global().setNumberOfThreads(1); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); + rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute); + + Scenario scenario = RunRailsim.prepareScenario(config); + + AdjustNetworkToSchedule adjust = new AdjustNetworkToSchedule(scenario); + adjust.run(); + + Controler controler = RunRailsim.prepareControler(scenario); + + controler.run(); + + // read events + String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; + + EventsManager events = EventsUtils.createEventsManager(); + TransitEventHandler transitEventHandler = new TransitEventHandler(); + events.addHandler(transitEventHandler); + events.initProcessing(); + new MatsimEventsReader(events).readFile(eventsFile); + events.finishProcessing(); + + transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); + + // visualize trains in addition to train path + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); + + } catch (Exception ee) { + ee.printStackTrace(); + LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java new file mode 100644 index 00000000000..239867b7670 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java @@ -0,0 +1,58 @@ +package ch.sbb.matsim.contrib.railsim.prototype.prepare; + +import ch.sbb.matsim.contrib.railsim.prototype.prepare.DistributeCapacities; +import org.apache.logging.log4j.LogManager; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Scenario; +import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; +import org.matsim.core.config.Config; +import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.testcases.MatsimTestUtils; + +/** + * @author Ihab Kaddoura + */ +public class DistributeCapacitiesTest { + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public final void test0() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.network().setInputFile("transitNetwork.xml.gz"); + config.transit().setTransitScheduleFile("transitSchedule.xml.gz"); + config.transit().setVehiclesFile("transitVehicles.xml.gz"); + + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.qsim().setNumberOfThreads(1); + config.global().setNumberOfThreads(1); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = RunRailsim.prepareScenario(config); + + DistributeCapacities adjust = new DistributeCapacities(scenario); + adjust.run(); + + } catch (Exception ee) { + ee.printStackTrace(); + LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java new file mode 100644 index 00000000000..84c922def99 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java @@ -0,0 +1,89 @@ +package ch.sbb.matsim.contrib.railsim.prototype.prepare; + +import ch.sbb.matsim.contrib.railsim.prototype.prepare.SplitTransitLinks; +import org.apache.logging.log4j.LogManager; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; +import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; +import org.matsim.core.config.Config; +import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; +import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.testcases.MatsimTestUtils; + +import static org.junit.Assert.assertEquals; + +/** + * @author Ihab Kaddoura + */ +public class SplitTransitLinksTest { + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public final void test0() { + + try { + + System.setProperty("matsim.preferLocalDtds", "true"); + + String[] args0 = {utils.getInputDirectory() + "config.xml"}; // one direction + + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.network().setInputFile("trainNetwork.xml"); + config.transit().setTransitScheduleFile("transitSchedule.xml"); + config.transit().setVehiclesFile("transitVehicles.xml"); + + config.qsim().setSnapshotStyle(SnapshotStyle.queue); + config.qsim().setNumberOfThreads(1); + config.global().setNumberOfThreads(1); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + + Scenario scenario = ScenarioUtils.loadScenario(config); + + assertEquals(6, scenario.getNetwork().getNodes().size()); + assertEquals(5, scenario.getNetwork().getLinks().size()); + + double distanceBefore = 0.; + for (Link link : scenario.getNetwork().getLinks().values()) { + distanceBefore += link.getLength(); + + if (link.getId().toString().startsWith("t2_OUT-t3_IN")) { + link.getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 120.); + } + } + + SplitTransitLinks splitTransitLinks = new SplitTransitLinks(scenario); + splitTransitLinks.run(1000.); + + double distanceAfter = 0.; + for (Link link : scenario.getNetwork().getLinks().values()) { + distanceAfter += link.getLength(); + + if (link.getId().toString().startsWith("t2_OUT-t3_IN")) { + double time = RailsimUtils.getMinimumTrainHeadwayTime(link); + assertEquals("minimum time attribute has not been passed to split links", time, 120., MatsimTestUtils.EPSILON); + } + } + assertEquals("distance has changed after splitting the links", distanceBefore, distanceAfter, MatsimTestUtils.EPSILON); + + assertEquals(104, scenario.getNetwork().getNodes().size()); + assertEquals(103, scenario.getNetwork().getLinks().size()); + + } catch (Exception ee) { + ee.printStackTrace(); + LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); + + // if one catches an exception, then one needs to explicitly fail the test: + Assert.fail(); + } + } + +} diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml new file mode 100644 index 00000000000..21e8e2c86eb --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml new file mode 100644 index 00000000000..163fdf58571 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..471c58c8ec947f89a288eda607014fcb47ce7380 GIT binary patch literal 4838 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 M0D%P=-7^OP0Aw + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml new file mode 100644 index 00000000000..0aa82636212 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml @@ -0,0 +1,42 @@ + + + + + + + 0.7 + 0.7 + + + + + + + + + + 0.7 + 0.7 + + + + + + + + + + 0.1 + 0.1 + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml new file mode 100644 index 00000000000..89d2e617067 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml @@ -0,0 +1,57 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + 2 + + + + + 999 + + + + + 5 + + + + + 999 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml new file mode 100644 index 00000000000..be9b4aedcc4 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml new file mode 100644 index 00000000000..790b6b25dae --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml @@ -0,0 +1,35 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml new file mode 100644 index 00000000000..53a93be2cb1 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml @@ -0,0 +1,73 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + t2_B-t2_A + + + + + 1 + t2_A-t2_B + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml new file mode 100644 index 00000000000..cc3851daffe --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml new file mode 100644 index 00000000000..2a9ef4bcb89 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml @@ -0,0 +1,35 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml new file mode 100644 index 00000000000..192e9e16abc --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml new file mode 100644 index 00000000000..8435acece8d --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml @@ -0,0 +1,84 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + 2 + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml new file mode 100644 index 00000000000..d1c6aa9a2fc --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml new file mode 100644 index 00000000000..c86eff16d18 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml @@ -0,0 +1,29 @@ + + + + + + + 5.0 + serial + 5.0 + 10.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml new file mode 100644 index 00000000000..81b97846d66 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml @@ -0,0 +1,79 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml new file mode 100644 index 00000000000..5f1443a7b3a --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml new file mode 100644 index 00000000000..103d0251dad --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml @@ -0,0 +1,33 @@ + + + + + + + 5.0 + serial + 5.0 + 1.0 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml new file mode 100644 index 00000000000..41b33e143d5 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml @@ -0,0 +1,79 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml new file mode 100644 index 00000000000..5f1443a7b3a --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml new file mode 100644 index 00000000000..103d0251dad --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml @@ -0,0 +1,33 @@ + + + + + + + 5.0 + serial + 5.0 + 1.0 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml new file mode 100644 index 00000000000..23df0c093d0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml new file mode 100644 index 00000000000..224e09e269a --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml @@ -0,0 +1,104 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + + 1 + + + + + 1 + + + + + + 999 + + + + + + 1 + + + + + 1 + + + + + + 3 + + + + + + 1 + + + + + + 999 + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml new file mode 100644 index 00000000000..fd97728b28d --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml new file mode 100644 index 00000000000..a2056088fd3 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml @@ -0,0 +1,48 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml new file mode 100644 index 00000000000..5075a48d790 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml new file mode 100644 index 00000000000..17d4fe0d429 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml new file mode 100644 index 00000000000..de64a5a9958 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml @@ -0,0 +1,28 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml new file mode 100644 index 00000000000..56fa13496fd --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml @@ -0,0 +1,85 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 5 + + + + + + 1 + t2_B-t2_A + + + + + 1 + t2_A-t2_B + + + + + + 5 + + + + + 5 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml new file mode 100644 index 00000000000..5d12af79465 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml new file mode 100644 index 00000000000..af93d6d5830 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml @@ -0,0 +1,54 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml new file mode 100644 index 00000000000..23df0c093d0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml new file mode 100644 index 00000000000..9bd8fcb2a63 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml @@ -0,0 +1,123 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + 999 + + + + + + 1 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + 2 + + + + + 1 + + + + + + 999 + + + + + 999 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml new file mode 100644 index 00000000000..92e5e3b25ae --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml new file mode 100644 index 00000000000..2a9ef4bcb89 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml @@ -0,0 +1,35 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml new file mode 100644 index 00000000000..0de681bb7b0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/trainNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/trainNetwork.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..22bff2fe04fd3fea4f9b7b5530299185c0586463 GIT binary patch literal 159748 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 RrfNpm`0>3wD>p^H{|9O_)Z_pF literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitSchedule.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitSchedule.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..5989dd29a2da50987ae36f330d55f9c789b06a11 GIT binary patch literal 70575 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 literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitVehicles.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..25ad72a9773194a1f7bddab3c9f2e74ec47735fd GIT binary patch literal 1366 zcmb2|=3sz;w{vp+Z+nQe+8WtG7dpIZW5%5%)s>QGW>C=PbCIYUO zdzF_P_0{bETqO9|`u3}+*ELD2T<6)W`}C|%vA4j;q}!+E zgYR%^3!OZ(%WL|5rOdchTs&6(aaFH9XQl1eO$mE}_=I=@xmwKLV{l0C@VFdwh!Iq@unhnJPBXC++B+j#S0X3oFwq)0W%p<5J&!SUPB1 zd-6uU$Eh{ii?`SmS~|>4+U@Rqd7JL<2VWO(U)r<7Z&{|;^s5zfPJUke>VZ>k`>e@l z>O^n8c%NFKenq7FP6SI)y;7%6Smw9u%hnvbciq`o;O5~8nN!xeUCxwf;rjIZZJqSx zPnXYSUbuO9fsmNITUU=*HwdPzNi+7oZGCiqkIVP{CtS{N|I-})|9R7*z5ZJQwglKX z8b;}x&CICZm~nE2Yuoe4d;d=V=RBB^FR3e`E76}2Q@<}aUsP96SCHLh%lD3H9sdml zRe8_c7h~@Bbn`Hz-=r&gA!%e-32ajz?NRBvg<@Ui14-z5@40J3O?1eKW#AJB5`Px{0 z7RK;!>$S0pA8zVSJjj}B2xA`I@E|GTz?%*2i3bmFc#s(J!9b@%Ld+&$DTL(s}CTxASvq6n{7Vk`(d0<>A`t0U} z@Wgq%l`9|KoM7`Y$74Z-nM$CJv`@~9b-d@<4l~`|`tW8##Dd*sHqt!YCD+^1n-d$~ z-TIJYpp)>*w>-DPOhOE-a*H{Xfusd!%tFG(l+A#ccOh1K9&a1#uI$5`8AG{Hj5%`+ zN%G)^22Wl!lRIbL*gjnP{>B@7rZC65*~{|Eigr)DmARx$dgWfXto;*zt(my%zDMo3 zu=nk;v!{hO*Inq(1S!f5(O$hZaP69FZd*4+Nnbscg(?v0^!HDuMr~ZYjdAGJfgz?wodiST^b$z@@m+xty9ytPW^l9RPnS=Q>A~u^E|oO%lFSJt=IEI sg!gv=nOa(=A6Kuqa%BCYAXI_;coEQo;t0MYE9%K!iX literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml new file mode 100644 index 00000000000..4fbe6d6c452 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml @@ -0,0 +1,96 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml new file mode 100644 index 00000000000..09d5c02e458 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml new file mode 100644 index 00000000000..a5f1849ff76 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml @@ -0,0 +1,36 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml new file mode 100644 index 00000000000..72cf067be18 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml @@ -0,0 +1,196 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml new file mode 100644 index 00000000000..0d6c5472cca --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml new file mode 100644 index 00000000000..05c114f2309 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml @@ -0,0 +1,28 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml new file mode 100644 index 00000000000..bd8d5c71223 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml @@ -0,0 +1,178 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml new file mode 100644 index 00000000000..33a88a1cc16 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml new file mode 100644 index 00000000000..c900e79b69c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml @@ -0,0 +1,50 @@ + + + + + + + 5.0 + serial + 5.0 + 5.0 + + + + + + + + + + + + + + + + + + 5.0 + serial + 5.0 + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml new file mode 100644 index 00000000000..a5c7658af5c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml @@ -0,0 +1,423 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml new file mode 100644 index 00000000000..9cc7ea08496 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml new file mode 100644 index 00000000000..2642a38d9e0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml @@ -0,0 +1,29 @@ + + + + + + + 5.0 + serial + 5.0 + 2.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml new file mode 100644 index 00000000000..c5050d6d5f7 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml new file mode 100644 index 00000000000..df9461ed7f6 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml @@ -0,0 +1,423 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml new file mode 100644 index 00000000000..1feafed0f84 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml new file mode 100644 index 00000000000..2642a38d9e0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml @@ -0,0 +1,29 @@ + + + + + + + 5.0 + serial + 5.0 + 2.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml new file mode 100644 index 00000000000..f45e23b0f3b --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitNetwork.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..199db02001776e43822aca5df1a186f84e459bda GIT binary patch literal 97116 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�=h^wQ;ka84!G$4%MBl4Q0x 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 Y9p%pw`71Qjb-VMT-yTzbcbC@9$I*g!=EWtCx( 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
mhEG`e=+m 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 LVbSAW+?@Xbe3Dc6 literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitVehicles.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..3156247920716188746f17cfea00a1f98f67f97a GIT binary patch literal 640 zcmV-`0)PDGqCiB}Y?Gnn{u2IEXL;L1oUwD8oN)&h`)S zaer^GH-#~y98RHNf*A{IWIct>@YtB@C=Bbm4oYG)D+8%UVZk`)FgH1V@%Sa5frSKW=Gl$o=ZxMOK-zeXoEd`y@Y#($RYAU-zVhr>L=nz`Z=4lI>fh9+(RJIR>Nim>V&Alp9z~1o21dGqR>o4fT^YWJ>Ln`_?=-& z)B-8EtOrHGD6^t|I3v_Zg$nnziqs1m$Nn7|3{O6e4h}xO4!TiyCnY4iE1}Z~I?dMo zUzBh)_&7>9{Papl({v{##8G!wCA8Z?`&s+2N@!k-?(AjJSo`XpuIJ=nGf+wow#9_)b! ad+5P_UCXxYYuuVwp#ERAeq+uA4gdffHa9o` literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml new file mode 100644 index 00000000000..f45e23b0f3b --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitNetwork.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..199db02001776e43822aca5df1a186f84e459bda GIT binary patch literal 97116 zcmV*CKyAMtiwFP!000000Ia=h4{gm-o%h-Nir4S=)2Htj3$WswWC?Q+;3#}TQO=2j zVju!!oPVG3^xCUxzRSs?8wG)ZZFcRs=IpAj%cwDa|6l+2zy7a3{kPx!-QWM!-~P=X z{G3Ak`KRCg&Hwhdzx}Jf`7eL)^MC&H|M08%^S}PX-~0W4`R{)7Z~y!+{^Z~P^f$lz zhkyLrzxzLb`m?|Ivp@g$|LLcn|Brw8hyUwe{rcDc_>cb>{`!CYhrj=;zYc%*t^TZ~fd< zN1rpaIDh_W|E*v1*Z#-9|C|5(=RXYOnA1Pcz_0$(Z~jyd@~h~U)(E4I9MfFg?2WQQ zDrH9f;X1aO%xal6#Mo*qW2J}Lxx#3xr?K>ZacDm!sw?L>TY1}^xwcT2e!=XW zf9}+-J#fmkhcZXadDP{0R}V3b5l1fkCo;K^_H8b3*j&SXxWu3cxZg`ksee^K(g-T7?-*8(x z!$Y4m>4RRp;c0efi$T6IOOMIVTGyYJHOq)KwF}=^W_Ohy@+#Zs>M+A2Dx&84^11Uj zY-c#u5kuBth^^0IFG6oz53JX$<0k(&w`Kn7&|H#gFi6HKZngskxj7pKQLYXDwmqxfO_~o!}K? zm1pRwOTGze*<3j{T|2AC+LD~a?Y0*58)lWSuZtIKo}7E`A?b2xisI1?r>O8=V%I-+ z6V$S~avmuMord{~54TcNbZ@=Nzb|f$yxgpUq9WIJI6G)7N5%G;x4(_^_iN`&@3HGR zq!~w>4?k95j!9nF<{0Nz-HoR%`;~O%OeGS-AGZzph5oS_Yd`;N#r=4Vq;sku9tpx` zSRYYgqf8=OyLe)Aee?8W)u=0D zwic^A@BAt%=E!o@rQ3eLhnC!~lA)@zo65S0XvG{^H?bTeDugSA@-HWUkYDI*%e(W% z3pPjQp4VkBn=#ew4yQ{q=%p_u05=h>3^z=oJ0l5e@iVN9Qn6hJqQ+U$#TzzHE^Sm@ z_A;SJ=|@cutD{X^o-tm$VRPl8gjARPz~ywF&G1Ny?MmG6nRWh(?L@?0vqEr;%ACeD zyJJDtN6Pv1dQRGwIG) zN%lva=O3=RFY7Ps)=7OTQuzB-te0!3;Anc{^CY60FW1QB5J^Im{&|d0ITNCiRXN{H zRIAkuFP+soDPZFCH>|TJ-%zY5T`}m~usO4S?o@d1S$BqIQ%yJV>w_l6=ZohxUsiC? zC(1fdE4fi67E5`S()v~}UavVa>AtRd?y|L7GsC+0U7X99+!d1a5 z6*t@!+ehwY)r-2Qnj=>|kFNUl3x8{R0XltaZg)FBzV^-H$TKX321W5vo4YY*L|;|tDG%NCDl?S=`-|Y- zQ)!qIEs%@%Yo1J#LwCKddO5QDThF>rBS~w%=*YC`W>sf%XDTAp=`UDkL2f9&St=1O zUa&c`T&e{e4v(w;z={nzzuovGt9$`+UZpsR8tePV?PE>44 znN(2t!wy{*Q|XYBv~vE{y18*{ZR%Q3sV&EP90$b;`GtOp;>UTI?B>OK$QrPimI922 z2bC5q05J z{4ieBaNYfw>OB(|DQ4|~b!94(*V!ZIIXCNGyygnK=Rj&*-Rx9cFC>Jk+@ebWn{ei-HmI0S7( zuPs!v=q>OAbZ4BK^*(JJMK3ooacRTbo{E@ZsK#gHk`HdSKmE-g{c@BeBcalAQkLpP zw@k{eE<4q3eH=XR54%3h*%ibn`umUOnF>NB@I?92%1K-KZT9`VY0)iF;MI>MV=}`^ zs1)eqR8n2jfe*h8Co>$YQq)Br`e<{}l32Xoqz`*;_3ab-Sat3m%+mL>rrap|qR2%# z)aVB;@wQvc?rbT@(bA~BJW7DlT9ETa-SH_l?TF@@U%^g>-qLZhpf1 zx#4Dpd!wtI>F*S8Y3P0)Duq-RD~Gsv!QJdC=LkjRq{3zO7o1gn_aOy3@QW$&Ba_$^9%E4KOstW~LrNDlKQ%xc3 z8qrZanx?<)nlq0{9;Vz+C#o%X=A<`VE93T>PV%U?Ve@8XNvg7nj_YXM-!O@Z9#wHz zhwVI8+9)UK751G+=~nCR8?h@(ZFHM=O?PV6d|3ti3d$(ub*20Jl?Mm%e`T-vq)G6$ zYrd?)TIr->jS9=Y9?zTUmng&3x4Zam=E|xoNI1oJ3cWu84kh_ZF@Gl@%IoB)y<~@xbQAO7kh^nbbFL+IQ8NK?#pO-N_X4ZPk3Z zD{h6AmEgqc<5=ZBiLsnAh`wmJBmkcHbWxNM?&SfVe{hNC|f$! zUv?+fZC69tGl-^HR z8BQY24NE~nrEK)i)^*D_E;a>@i??9kt$}h3l%JgkN}J#$n)0Dn zFh;7iJw6cGl@2mAj29J=&2VTP_CQ!uo&9j+Hk1ykYMES|7az_XxdE~&Q{|@0Lw~_- zgXuDujdD>F*$^k`4RW6+y3#rMjnM!MO$AXMnTrEqe%vUklJ0mM&e3fh5E}5P?QrLz z(mvek8N?9@bbUatRDmh5%PDjOW4*9zo=ks|>PHn-%#Y_){;-&v(ULh&tZj-zcT1Q0 zQ1DXi?H{hH6wUl=s-Z7xEgRy{pD+5wtM2Q^?dqnliD9Z@=6L|NA8P?Xorg-BYGs{>y*&Cx8BzZ=kpG=*R_RO`Sy}Jbdj5{4ntw!+NV`pZ7Kl zYyLm}^>Ogm?VtaD`3Z-A|5yLvPygeyJxRDy4wY49$Vwdkb5zj*+y+tctr+^H*<@xX zz#S&=%4@P)ExI!0dTHvTeu=yvR{d#d1t>pV)ZEF;>OT5$#mBGp_NQ61n`W#kdWuqr zY;%^S^Tr9QKy1HdlAmVnE$FT4W>gsLxG&yEPG& zRw-4d^(imO-!hUOJDWLJ6i+)&uBLR8tj$iMIPrKdiQoA)}Wr{536iQQ6;Lpn5!1#8r zecBynSGNIka%RtYxZ%2#01=D{qF*kcPs7s;D|ZM0zIB^h$|t*^ZrV_8R#y9RQGD86 zW;YJtdUS|7W~w8UlIG>nL1!aOCJ;If=MU@AflMY$eSARd z9?J?Km|BwltbFZneHu<~IO=BOYAeN`SJfx;$Dz7F_x{BLn;XYTshO%WGl%}lZfs%0 ztqGDBuh-l-${~URnKCmyvcCxVA#E=`^7y(N@M+i_IfA^@bt@NJ-7X)UXTW|#iMooJ zix**j9NWg)D+qvbaJx!>K=XTAHquf6z=uB>8^W!Wx3ks8Uu$yx6BFv9tro^Iz%P)w- z+i+yslD5ZP+!%I(l`rebbjhozazLB@SaDhR9Pp4Gu21c{FDtiIX`%bAZb$s_yeiwk z$BacWb8gl>8JMSTCXx_a7Fo0tiE@f)HGbP3`n2nwte+X$9!b~b?_sYva96O&@T~>@ zY548RdXaerxd&4m>v^u+Isls;kiH_TKkZhtI~5C5c(W;Em4)o;XUO4d$zy%1SU&AG zvpbl+fvr(>-TXz+Iv5N}vQFZ~i|A&UZv@0fVeMh}H6a)vrqab#WU~v|WdhwXw;UTW&+%VE~OS5<#syohCnE9*FWknDGcDq3r zO=egA5GEaEDi?pf`t^ZRTov-xc?7oU#ayhD6=2oVH#isTb~^)5`nN8wqT-IM7@#D& zb-fJhuCKI#W32HFy`%F$S=^B$*s7QdZCYz`EpB@yC#o67SGdNfU328gfQh}anS6FA5U64#DEBN#qB2LJQIvpso#2ipLWfUr;-{)m=}oMQ|mO^U=cz0 z^o<|e^n!IJMzKV&W;@8b=NF?`1v^kq7k15!Cs5q3aQ5`KUDqwbFSXryhF0B;^&aDD z8?|L&9Y|jwl5s4~U#^WVCS!j2Yu+E-Gi!S8tm^49N$b2GvdP7qtlR_skIOqzi_J4tS}W@0DB*16s4 zc7eP0)xgBrXOlN(8fsnTOufjWsynevu(*+xi%oaptV@&j?_~Ah;yt(%Q>w8v4(Pf2 zdjMn8TI73P)blpE(CHdEC}*bw{xHi5O)t7ZV*NTZ{j_UNJQOB@nbkIGb0=25$qvoy z`a2JjHo1`9sPL`a;puHDsz_30u$}EZC9z^iAyzSVuyz}pr#cgOf&LrQg>~^3Y-}+Z zjs<3|3y1Z`jkM~~EHcZhlc&y|og_^qo_;sT9Y1h1Xw5}(crO@y< zFOGAASX6U@J3o&)Hnx~5uSx(Tg4372rls~0!gYHke=Eb_p zYslaxqBkFylCrAx^0xEiYrO|WgdAA4QR3@)nZA}1RB$_yzR|ha=uZpYa8Xna+d9_c zX1yDw4}+%P`P;SKlev*832eEp2lryg3Z}l7{`Bjv?WbMy;?8KI>zg-raQyu$Z3wFj z(&>ItTG;N%?6i+n$>~Zc+1)tv+Ov{K)r0lC0-5T>7 zVEfZl*uESM>mJ6XihpqD5f4ClkDH zV05lXIo*vT!AQ@lg7KdkyZyqhxp4>L%Wlln$FsOY%Sd;>ennN{(=Sf2`!RU}9`26F%c+lp6;O5+{yK!8DB&_UZbbHzf1yLcXiv8Q> z)Tdo{W94KjAriahzIsicPO&}>5~zG}3)%LG-X7aBI=j6`1O3WwiEI=`#ifhO!gfz) zgQ#Vy=HWW3X0{LhGy;jL7c(c@J<(?yi7%8@u|JPPXR^a$8pDl?_h4?^z*VWzRwC7H zrl)GTk-;kqdcpcn&2~2%XHXp5o1p%y`(a)zm(oQ>0?ahm%ob8BFh=EKz%a~(TLGy;GuoYP!kYz9<>hPVFTuL%vyw8h zWzF`s-^I-A!LaA`eGT-}t~qgIRVc9FmSOn$QN>(5Z*sdaf<4Wx55twqY-IVi zDq%|(Q;X>?tREfHtTfP@yD+mu(2`S;{$dB^PPKGAFLJ zt55~8qQcN_V&*j}63b$Sbun33=ERki_Pvs+95}n(!Y)eOq%HB>i&u8Lk?CdL#ERM9 zuIkpH%SXlbSK6UZyXM7I*`cy%oT72eum>k!$z_$+*ToIA+=~-Ci7Kt2HZv~Q6TF%|iGpp) z{Y^=`?lx{`+-)6uu}w-BF_JyUt`}iV9eG&~s*DyOg5!7KLBm)`0(n%J;?-{y9iMhRIHbdlt(YbYBu3=WtHHOtfE=4d*8(C^@I=Z|an4Di!IH8IpI(Z{;VfSOJ=jYe3 zVG6R0CfFlnTf-lh1ZWMG7{0C&e%duhRyo}^A(_sTZ)1^5C^usa)nhup-LfHTHn0=k zuue0?9a)#DQYfe!&mnTz#{3gVUjT+%$bro&_;=W0^c(6oriV|v)$Jx_HA=}<#`cH1 zF)xGri*)_wfz6E-Bf*IFO?>yTTlemmOwaStLbm4Y=I9A<3Ib7fFIKIjx`55dlD|RV zKkb?qv(O%N_StkR!=G1gR`s0zB#(7*VI+6s1VpLAL^ll>#%#LN!6$Y5Mk(@X)}1)T z940NOY;Y)`JCik(m$RLlbyOHJaxsP;#WqWsIVr7*8?ax_TTnT>6YIT(0yw$kWGiWN z@51uFE0e%Lg7pT$NY~8YL!Xp$P=D!6A19L8-ou1yAV6&ep=oimg>|PvXo%M$1`n5)+r8$s$7Q3R zgDA2i!bnI8<*H7xBf?po13 zRRyI4Z9^j8b`!KdT@^;|=S_#~fH3x~Lu9l)AZj$jnVqqy0^tT3mmLtM*OzD#4n}%* z6?eU2*6#~a0v9EV1HvNv@N4K=TXVV-Q-mj2Z7FFk-h+8@g#Nnfn<;8JJICyi7HBUD z>FIo6ksS~w_wA4~f?<3(am;LuMabL7%{enKj=gMx@?J}A=EdNx;0E7&@)wn+1HvL( zARudWoT^vb_+Nloc+;Am+chuVB$b^0hr2NyiJLA6Z#(bmSV&h01gIt_1~vT{Ln{dm zwBZ!i+Qn;dNEp-dK)Fd<-rQJ)2E*yT3Ipj2yUFcFZkt4FJ^TEME^^Ri2XcAN^Y`nR zFeo}@fuV3UOc+GOeJ5=2jGxc9vtz4Sm76n5Z8qWXii#tqATl?<{v*1r-{d>Wo+c$69B!wN9XP+_v0A+3wLrcyp% zGRTGs8w&(Yf-7}+7RCB@KoSUtJMU9l7PTlGLII2tmt|3}f*GxZs)%_$&&-AjQ;sxP zH`-D|)n{0nK(;A{!hL&j5g9CO?`VWLHZ00uVIU_Pa1fopc5`7EEDSRB98|7XcrVQjD1XyPYkZ8+~0aRk(xgK`2Ql6#wz-@GH)*U%c-H}~6MUT_3WSS8n67pfzyLb`q z$v_QRtL@`Om;5#ibq&m^%4E++4B3!j48WA~Cm@W~kYO~9VqjG!#&OYnFkl$g*n-Y@ zTw9hyz>%a{oY#}nIjGzU7ntitW zY@O=gfOPZRu(`5M!3=U(vL?fZ>3<)azhsP+F6vl=hV9MCP)Pga$lmm1m#95dQK>}yb{~XL>`QrE*IBY=Wt8#)8 z*%={cS-2r@Kqc&PA%|{mH^cN)3QB(G3nJOTVLA_GAe_el#5b8|0rfj{tWiol zzljVTrqZoJo~vSIIZFqsbYpAb4Zu<7fydBcJJk=HxDg&8vkNeaUE~s5xwtS4a0R6? zT5pK-Sf^{vO(uZkfdb}54QBw5(Y8oS|HbH71y~Nd^UHYi_}a8S7f5Po2Xxtw?#uFv zODKVYRnR}51z4X85`VT!V1r!QP&@P_^~Q#DD=;eSpQEWf23XAtO zn1|5TsHlbbTYzyz2?Q|hd43faud-mpOtHfq=4+3ety^JUrA)8qH*xWdkI4ma0>yCO zdQ`fG%7OkY>YqG+3wHl74hpm#B2oo^R3}WOb?YtVa{?=lk8_2^!(q}gd6){|Zqo(sK*t%ORuqz5GuBPPF ze1M=Jt(AD@9kC`#NZVi2{ zvAe9tX3cr=*}#l^C>isoRWXDb6xaH>lUZL&R^hXyes3=_`M24ue@tf<;^Xrwu)6ay zumFF-l-2GBnx|pGrCrlk;nEMY?z_w@3b_rl8aKN+Zj~*;>w9d~T$jfU8=WYLxbsN>wvX;$*(=S zL$l_(v?7(o0eD%y=LmOVD7tOdt2REgn%ycw4p3^Al$|=HcR0(@rbNH${3V$0(z^>R zY}c2j*xM{1&y{+ji+KJL%yntJLEX@GjC{!vUDdGbO}&HT7;c@cwe^$`X_~}ce+ej_ zbSPDr9UmWCYh}SL%;vk!?U)YyXQw0}wE#WEdEIA?tOadxXbUOw=w)AjjYMXy^Q&;o zhgI`jL|XwtHny@KPYaDCJw#-n&hLiizRQGS^Yc7>4EAY=TDFUw*Jk!+b-`}kTlOTK zOkTMo050Z@J(qLrNO#|js0irSF6i<6X<3k8O6iaoJ-*4TgSDWK407B-_Tj)XL><|P zKnv_5E?Nsqc7F`g=noe+t3TE2rbnI6;a}Fk;$(pmha))qZsRMogM(>MIvtxe=dHvl zAgg0M7;(3-F3H1QQ9Q5rtbeuY-Wg`YPH9+ovl)(|giK>PZ@^mnirxuS+%loE$)6Sm zkuIO!K<)FlVBQPgnsPWu71kUNyD9_+iq=YM&hH27Ua?Y@@&CbTwVPMJp&+KpEIXcK zzpQz+)epjkq7*Mr0pkbYtlQ0wUxK{@$hj!M29t1$V2j2eX8^YI^L0M!TY(-#c@x5l z*5l)8p)2jsn$3I;*cd+=UzRhf(QD$KToUpp&X9!b~6M*R8X6M`b z)4ez9Bo$@i>`b`#BD=V0N7DJ=&$bz%3$0M_S0+<-^(q`FMF)@6&)Gq2m2n}9#7sa3 zzIXTB7||(1^M0js#Idb1E((T#RA1Ywi*LJ%?E2RtI$h;r=4Y#ndzm#T*#v)Ks#M6= zK&!-3#w_Po&UDXZutDQ8rfi#x-10-7$G|Ut)l&E{>#oaOLq9n}if$k$UGs2k@evD+ z?qj>=yP5Gr-Mtx?*jsxIu`nBFlk3>YY>$!KrG&XQaLPNPN**A^ow4LT&uN2fjZs;~ zgeR#0!dlK$z_>DOY2=*Fi6Cr?QD${UsqddYR%!5e2s2avD%AE%t7a6GTi8s$FywgJ ztQZ+O15xJg$M3+F7?mso(#>))*1_y)t72uRMUK4Txmokx%+J-AfuY!T7#XV445%>t zpD&2o1|wW*s>B(~$I0d_f3k+lz}6!2V;VgXXj|a=NJ8BbBJ{cz)w7cU}F}urcxN z85fa=AuENP=sZbS?z+m0S)5`(kle*kf-5Khj`O0-78e)I6Er)^+L#l!s@qY8dj`>d zemU6QA|vUx_c+p%%)Ox+Hr5N+^8ao0-~Q%L{?ds0p@WAJ_3i&fK?=>-FX4p0{~Hqu z(9PHXc?N#<$A7`Eif(AZ#mU0lLt|JkpHzYdN&ZHL^-H74j1Dv#)==RWvweZR56<{` z^{@QiA4cs_*-=3>wb5gFpFy2zbR0?s0sf6W{lls~tE#KSX@P9lVv$^OCw?8cSw6R# z-6}0|)u>xT)2!;=*o|*ktzTFof*dKorZ=F@fp zP*GCqI=QUBmMb52o7*MJMbRl{+bz;^Jgg2XIc`!R6kgW5OTnLRuTPxAf%s{jt zLwk{Nf9qj<*md7+kPOZWZKgLNOWwje2~q5rFYLPaLQJ|DGJx%xS^B$0m%UEI*Zu4d zv+lm|(f2}%QvB_rFG7Q8vuB^jn(PkTAp%i4skiInT66^DDBzIucl_9gU3cIP$@1P? zDjpd#LUu(d97vf9v+lr({{cQkz1+viooNGY8}#lMX5E3u-hYefdBC`4! zdoL`D+u2%u#6}%rI4wAS+ZYIDAYa6qq|w!ZwdKwT|pOS&3zTY z;WJ_ptUb=ILVJgqUTN$6wr}`hwz*mQZ+snEnf|z)(P+sVEpcwPyIBT^YfC>oPVPWK zX}l8obF1dOP2oSlP>Yg}pRfsKZI1luMZRQ!lh`(8Xaz#s9+8pgg#rls>gyY~=ZD?p zc2`53d;6J1M!rUOWvJtyu!b{C8euJ2xPknCGd{;gnXjPR$xlO$=>#mCd1O>Cgl^xsN9$9(?FBRHjv+lZepU<(U zmL3=@R7`s5IlW}G4$Zpj?mEiwyNSaaE)JrUNM(VYti)^V_H?zkQPyMzwiOF;66 zU0vd#uxYwe-DA7%yM1jf6(F6DXbYuSU|R-;wF|rMyhFA4R%MUFqpc!EVxq4<)9riy zcHMcQ@?@9>N4Y;ODx-T}0{s5DS$AH@+dCxi){ADM0?`ae9>$f&M%{JM)q$ja^Ania zO^efde6f!on|0Tn6Aapd0q1eRUKpVW>({(6>%P0-C1GC^S~)X|N~#XrVsHMuQ`Ove z7xFIbAfd-E+eOtoDrwSrwL`Y4N+3ol zV}4aG{V;0|3_Hgbz}$rMZdQf+UT2A8U6@^Nwsv+Rwx@a0*ddW_kpALn$MI&k2lv5# z4IcM6V+na`qVVpxoSStI2F#LkNB`1;E)juw(tt$k;%VK3;Z;X;B&PT{x`+p|x5-TZ zI29f4z-Zn;_nsd-+B-XE_CDy9ejDC@n05bEunh&kdWtp)E?bI$y<Y;FBx3;WxbJhglz(ZJu|=JM4a% z)zlz0RpPix{I<^YY1SV%QkXz8#4PUp$@1n?Z*1bEd{XYb?fUbQh3tAyrIcYEGxI_| zCG11r=qf+$S`^dY*U^Wrsa1o4XKI}QKY;!$ofOn>yT$Dmn$}x6d>Xq0TDAaPP>M}o zg)~16TOiAmH@T?~Q?nJ|jFPKTXavt^{)Wl>G~C=U83SDey$*Z9jQ&_fi8L9$At^uY zS}fDo+DIfsGY!9=(W6uCr_yDma?-bX+Z}F~c@#`NP>R^6qk>RIhB70(2j_+@JY=YJ zlyhXj)XVHLwg#61#p_86?`?Ov-Mn`TIII?XWj7U8YF{3AaAE$i>k~BOSoV^3@3zfB z+evY!ZookQ#>V_<);u}W{L!PL3+Rd#NdBN0yu}Gm65hAnO5U&u4!rd2QF8%yD*VfSOk)q`F`Gyqjb;5pEhpX}9M90zyfOuEN1*Ycf? z3~Ll?17MHmNf!CG>u#J=L>WZUuyq@FL8G>io#iSi-&Ep04Z9mBhKUuGqUxIxnH2|R zlKKs-W}L?dAEiOrsYIdRZw9E#+(0y^U;cJ}(tMUyH+--Hc#6w!&IKrty;wN)&fl;P z(=sB5kQYJoZ7?;o>_GW50V-da3O^0ICo_~>dJk3UsBKkd(D*^mt#^D>w7u<`Cxa^v zjQdJnwmKuT5eoA{KmHq&_NQ5U!DuK2ne2e~EkM9brqq?~?XQUbPqWp{qChS0fV;p_ zsZ6{;q+U^f9YF%#hRusCf@RXJG4~TcWjf^SsjyS9AmOR%Y}kR;HsWz5*grSi0~G^W;Q3 zv&FeRF1y`W_D0!uxbx8CV>R>>bx`Y@*7=7g!)%~x8;+oH(&&BLjsCnv7UBj0cxKW8 zGBEw%mz*Gm-geEAlZqb2M0jX*BJ2pO{7TSV?M=Rm%hQKziJf>@igFsZ{zXE8&ZhD+ zecB6G_URf+Edg+M0x4v6TaQe!luCTvTl_TZt_)>fUCK$9l3jmZSle0S-B7P{yY9)l z9av#i4Od(V6$BhhM(k>&d{MplfGr`7F<<**wzVg_Y*lplrN{iXn(}GaT{&%^jmjw% z*4!|I6@ADAn&=y^%!gqgTQL2d>Km298r>SP$d%;r7Y+!>8)QpuLU#&l#rmKK75pLY$P*uf|TmwahIDV zg_dv7De$*IUL5zfLi!b)<*lJc+FThx}5K`W8ceY1EnEGHCiunK`T7+9*`Ou~2_3Bp#dfht>7D*)3Ck z@T#E{smhTn;LCUYwE56joD|`R!0UpxHwq&JipO>AjmLAtF0z<9ea4D-OS05ZCUs;W zUg%2jzYS174ZBz>NeT%dvBFjL^aYU!%DTtWw+rRdu4|=&I!E_Jj@KWmkc`7=L)P7H zIXCQLsg;sqO(@6NXAetnIUekNKboQw7?*ZQ<;WYfCp zR4lN5n4LiibtDonBv$&jp30|LfvF4j-G?#VE|&AgZL^5Z}haB_9StP;`Wjq9uA7%JFGD~X$N3XHY)6-GBeYMuu3ZjBbkYr44=DAbPs8FOuj1l7m>Rla8(7K%9LpNaDii(8CaY%uvwtuB{__XVeyqNv#np1!%P9h`UNdZY8Rl4KbeEHL` zc{01&kmh7Pad$$Y#6oL9pqL z-xy#?LxF*ztgpOqpLWfYS%nWfGf`w;{^fw9>6^l<@|7L?)9`Y`D9r0s;&R!K8emYB zV%y~|LWJ|0^XU_?g1B62kc{tzBLdxW{=6>aq9lrBf#s5E1J?E0oSW=+mT&WuPqXIA z(ENu&^A(1E|8Q-x&5c?``l?v*Y1mwuBv@alDbpP}`bnd!0-i|keO$x{S90M_M$jIj zLBHZ)y$C2%b_J1dt0$j!tJ|e92!_2jPa)D^X%^V-YF{-XKkd3F>w&|7L6pvl8-|Ub z5A;iJVx=p&Op<=x8SoS(Cm}>pc&8RDaDF4Z_%v*;tkePNRVBN{S%4S(2^*%U!;&vT zgiE;)ZZ{W61{ZEeO{vggJ-y@&+&wqE+_35@-R*eXaMshT>jJ*sm&=_WiKmi4MBP)) zJdM~6x>MF|$Aow28#~3PS##x;p;Q9RpOY=DzE;VB@3Izne#g0%3;KE>Hn(z8bF*uI z&B}7=H$uiwyXMJ@l}hxjs0BUru)-Va_VhR}LWFC%kl85KZi(OFB)GY3;7r!zzA%@k zhN~Oi0N|=?XRL?a0U4jlN1!7Y=fPZAnF)xxN$UHM63DxMyti>~>The1?&e59AyxlE z@mrbIw9T?ZE_5Cus(bPT_epnO4rjahd25kjsE+T|0cPM5BICTI`xU_wxJ+Y;eXnCERH5!lz zj1c|rOi$iDnU%`{u8z*%xn1J{(RV?`2fDx99%f7h=Q zC)(gTgmU!QFW!SufkayQHFBfJHT*ys~xNea@U%gL0;9nk7t^;HCJ=pz&$g z-I(nuOPwfz0^)JU9(zN@=9{!G^?IJKx^&b;P8=i)4BBMb_s$m`F^r$zPP=B_~WOLj@<4;q7(xpW1a= zAEh*5x0aQgdvuu45m0%J^cCOoY1e6e0FCSCZg2DO0NIaRW_#O3cyLuJwm|o4Ag0}m z5nM)peRH^;n>8<{6~&e?+@;e=-*mY8CR@!D-Qjb?=EVupo{h7}k6zbCXCpmB<$W>P zb2iL0p%PLarZmLx-3!42Rkw2QO=WU!*Zmj(M~O13+TMFzsD166n5^WYRp5M>u%GDD zvy@(kUz_e;RgjE`)X}JHe>QnO(?D$=D`n*^4jtB^J8W#bavQumH7j2 z?BZlWu3a>NogOpqg%xrpn6Vy&lLh^~p#);tLT#<91Cx1?r zSpdPsy+xn8Dj#S7O!LoAnzLofC6Gf@MN#*WdfR0WSno!q);Rxg&X!r~Zp*3& z8u7u|UfB23=}oBpoSSu~TjlGhtsuGXd}myxYpZ1FC&Y^g;e?s&5~vsk(mk7Zms>@U zp8t2H^bH;QY4|}@&2G_Tusgg}&{(%9Oc2SiFTR_5@?-!o_iDm=5MV=EPDf@kRX(@t zt_&Bz1{OdC;s>p>$<*0GyE(t^$dD$fM&1tRM9q}sF~;q0$AQNcJ~LAry@)!8Y1~Yf z+$azLx0dsVwJGvI{!4j@K08~D9vnK@v`%^ajSb<`sp(|o7*Yc zx!nhQJu|VsHH06kVhy>$I5J7*SKZrB!_Hr?YAR6uvDN1NYG#Rw7+aKbeM8=U8g>Tz ztm-H2Q04$x=1nkYHJZ2QXq#+ku@}X!-tAhf{ksYWy1**NnV)^*)%&#T`V*C@E_pw6 zjSszyzPpw{Bfjbwej0u#P~^R;u?bRLjYR2b7_9N)o|!;8u8giEZq5@+?9VF!?l)D*kQ7oOo76a)CcMhSz&H@hY7B`+LqeA|2d zFzkS_v3XKU&5t`?@ixH6y5PgUFzSAcZnIu)M#asAnhI*!I%@1%pMzp@az9?I;3e8e z{UM*y8As`8qnREr0))YBUUc|ydvpl5{XB3*FT~gK`pQA_Y1l=eAc13ny0(Y(PuqIr z?81(G5gu#~Nwx&9IG0=Tl%#h^968!5m(H;u*~TKk9#%k8X>$Oc#s z!Id$*Wz8828w`Pd#>I(nF(?+;^*%XM%X`aP9}ckI7Ue#V?T#3m8+$i_0%e!ft?Y?M z27eP3(Q~^l2!(bGd~&G0h?Xw&RKG61e%f_KM)wulXVmJhbb0F_^Cubkd&sz8x8gT{uO`Km+CcHMk&h z-$JiRCxV4)KfkIH0z8!L%wI$vS41W@(1x2-T&`BznVKL}IO(RG+kI%NuDti{GHeSE z)n1jd5Z5q~I3NCJ*F;9JJJ58Yf8;7N1H~jnEGitJ=O@Bdk-NTq*rmOGv_CLna$^8t z7~fXHKh3%;Lvf+=1ox2#G$8o}0gD?vUUp674(GEjjEpR+cdZ9AN|INd>2(nxo~p=8 zrTdr=Cfbg0O7jG3Rz0X04YP^ zZQpGs)rS=#%qx#G&{3qhhmNS8*o*Im6cMTcz*ojjXO$jg@kB1=N3p?NX z`3^#MY2*Q!8OzzIBilKHm?F^g(QVo;;)81=>*UW+z>dc38N@^vCiaRJb3dQ{XIDq2 zPoS#|0c#aT%?*RWp%bW>bP*q19$BTOVr@mMHg?ZV*oO-=JX98}Z!F87hF#sfBREx9 z+Fyv#eRmzzWaj< z9!Pe?vcpskH4*l_c;4)G8OqDGP&M`l!9`M;ZM0W=?bNP$^2{$`vfMY#tQ%I{$xl#a zyDmb6!^I}+26cC)v$HrY7S~XtWmzSi&+4-S#wOf^QI4R*>1kIPODvS5GhajqM~uzQ z5j|PmPM$%~eF$xAUWRhfT6M(ORQVoY-q1DqkbK93bpRo?)d#&e66VQ}JBGbi1%sE{ zRRGFBHNSb-t8OYNcQ1~Fc``Ec0ggC4&Y!oG3bW>mwdDNr?C!}d>M7+db0zDnPT1W8 zqNy#d^D$g@$k@~|&~3IM8rhRH&?bT1zDRHM{Gd5xY_5omXG1jIiRl)z@1S#27zv+` zF|vckXr6}6hBLcYv$U3AQv)#*FJgqF#`Luo#D6-%&hti1!%ec_a7o{YoIdTEE2I7x zAVpT;w&%+~4EI7CfyH0w`pylTD+7p^)1v=UU9FxDhl&s8Ma&P5?HbfNQfODW|2eOI z(BRIfP<)ea9h)_zH4KO$0%fbCP0uO|jG!>slvdBte!l$_;pprz{EzO;*-UugMJ#(# zbdKGyVs4gU|F*ur*e6-Qni$BNC`R)@+9h7SDwwhCr?;wx~2TnT29r@~$PA+u7 z8F?VJ??TPMWP{qSC`f!2? zHsAx=-}46>@1b2UTV~lZ1)#&19|S!=kSu6w(s?!HTThV=XHIF*ruU&CGhcZhIP;4| zZ(n+%H4vav?K3$Ormj1*bi3-<7{AdQB^FV?DrA0`HQ!a83g0XecMly$B`0YQs$Wia{BCXAi5*3DR%>EA zbhz{G$oT3ko?{ez+X=>LBr+9bXE7uJMFsFjc69RIt~#`v+%B2hKEAw(@4xcqeb_bk zO&t|#WwwYYW;447Ff_6I3nz4F*4#Hi+Cn`HJy>(IsD!M&Iq{WU^uug-vvZ(vR@W$f z_|erxA7C8gE;@f&cVAsDVUPP&@G;O%JVfx)(%SU%etE{j2y9WiuL^dq3go#*36t6JwK zl0;rbueMD;xy-)Vo-W&mkL@P6J0T7PNVJRs6wnsekxmyd+v6iz&;IL5g^i^f_!)$l6vmnaIm}=Qf9{QDr%xI?NbsiY3 zVC9vTyDQC&f`LJ2i7S=(PulF zeKVE6iWU)ac0KL!S+M;k1{7NwN|#LY%?Q-fkZOZa|9mr|yZ*GMQv4r#vrA ztWJeS0@g*qNwIl$GdAsgBV7pDwrmlcoT+vUie#VeM z00A>vK5yUHYSS#pVay6OZTGGP)Z~c<_~Mbzqk^qA(NF?HG=AY|%*OWm{N{*04!OhK zcZ&zWL0YUe#t?Ahl;P)G=W`m{YhnsGshCJ(l9#Da_^A+Tthtr*^*LK>;tPP8R6prm5TD0Au+AnYo)xIokoci%+|A%Qx`o-9mAdzIRWkuE5H`Ft9nwG15o01J{q=# zripSGvo89^rg1+Jh1rU4E59hY56znQ)~t#Yv{MJaYDWygPL&p_e_3!pHf!En>E3QS z$fz5*y4fv81JP_cAG_N&lgiXoQ6$IB9uF&bW|3pN+4<^mn)^2RXg29;e6qwsDx!oZ4)iR~ zkAk@`sNF!@8b7mX`&`H}FT#)f?0jRzHkd#aB9IIFuHTXso<-RC^v=^c~H{5SRUIo|5fe5&aAT8G?Pv6 zVf(GRH@bdBT^Fyx{I?;eGIn?zeowOP7dXo*g3@_duI7pv?POi$kNyn1wq!K6S|L;~vn^W(q68?ECOUaDoxpouW^LtRK!>JR09mCoN=`OA zLtC5KJfA?(f0*^h-Kp5v?a}Vz7`VR?##y|clXU-W*9=G9W_Etzaq5^M_&$vbbjWvm*nZ7`>A>0EcC*=?gvj`s7(NZNJBr}bW@$b*T--2- z#R%Dcv`Ot40MtNb^>_xCdE2dKmoYhjxj4$7`S((m-@2`;n2BVcV&Gfz}5_Z zd{hVPdD~Qf>AQr@r(Jht#YsfZVovUG9r^Ofj1EQG^Y512k!fLW1_N^3=7tMYJ#_Z! z5h2vuuzB+2MD6N=-f1zzTN5sJh|<2QS$-NePlnSl6d*06)c%6eC02kaXn~yId)|i4 zlPBC*U_ikc+ZX|?Eh|?f>MNg&+uw$V8{V74t&r{en_x!TGStN52ub#~Ypy)u9)sp) zz>()ZS6IT+#W!I87$*F*MPz zJIUAIR?U;=rUl;QsA=ZM<0=;_dubu{la~M6u(|TwV#-J@=@Tupy9odXs%0m;6mP59 zt)d;P_{}z(Kdl@sYrKKZk*tcDGXb}61R{pJ=F_8HXs?18yeWiEd22vl*WchfKXgMfz!S1 zni~&xV084iyLq|_of*Bq?QbV-;Ch=iN8WmZ zyG7WOC#$U?VpjkrK_GE%*Zg<@CIc(2%A)#fXu#|`T#c&2&cmd^$yhOBjyqp`gk~2q z@D91hI@@M@+ci&qiF8AGxGCt|Fyz{mjOE2}durG`d4O0{IAnwGkp#B_F_3N(*w!!X znkSDu*i+X@TRs+K%mx@8?SyhP26@|cM+VtLD<`Y87;ZN&R_%3Vq@(%!+pasZjw{WP zy48TpE;|IAvxzd$xmovPNJ`1uVULb}(3I8Fq0I>QUX+K1D$^ltD-06r;&h5&kUM%8 z+uu%>?SI%cRGIF1)So5;tLjr?Xx+f^L8jI(-mk&Rx^lD#MH8&Rj~uuzdQu>4<`GHm z+ir5Zx*7^}I&mbonybqC1rX?H4DdEU)=E41{$b*UB^vP<#7dKX+?=b_!uW$0DYH%u)4K2qSiSNpe4 zW*jE~#foQ;;oO49y?>Pk|1|5ate=k(sSZZ;9JnG1)u=^#JAux>&AKNeEKqeT z#NHht%0>dBWQ(woPR6Bg!{*EIyFms9*6xu7a}6=bQG`&!aejggWY!tFD9o^5K7BDt zhifS$Wfn`0Bq-i?&6V4x`(p}{>m&9s;MvPoh@D-$2=in*909c+rMsR4BVZLWiA979 z^W1KCyL=+}F;;&h!8HO(1+&!3$>Q1Du6goK$DmOM$YuvvIVFCm3PCaRNZ;*k*gUzh zpA%>2dUwv%vAM%GvxOq)MR{mQGgU;R>G~|&_j?8o4=csvcAzHj*KVP~^uq0ITvZ2fdqWuCy5y&%a!EUuH21WL#X+uVuEBkp@t-ju)}r@MgM- zA=6eatk2|Wuu?W)UEpSCzob-#!M_cgFW31(H5WLx zioalZKID!ROc&{iLC$&w*;&x@Qr7Ehqp0*PN8%xG!{*BsDC*t>$&N1>W$$+- z9o~}CMeoEwXNrU@>?78ls_!DoFmRT(X|7J-l0WPk>};dPxyY#6Y=)XqDAfC2DBWF* zbqse#N3sT`@4C*SpEF%nFh*_V_=~#JU}wmKkc7~X=#MTaAEQ7;%PMV(Kg=5ZOc}&x5-ha%oSctYS%4lszOLz0v(cXxvYrBrXFYOY zh=SQWkFx z!=RxO(F2Tp$?8+X=EwyLioQS(A33nns~+|$4<3H;D%_D37y?Y+ zTXWv+CSH7jrSJ$<{kH3l41#)tO>|N{1V==y)?SA#CwsbYyY9%0x!EVh#|;$ZGtm2BPF2VGj#py>+xs z`ZjE?%xE`&`E0R0QegIELW~!_OW8Hv0XW@!ZnHq}lPW!`4ZlQWp0hEzySo&h6N31z_2 zWp~8z_O@%DoLMIroWf5}mabC(gp*C2Xy=kor&2f(k7#kyj4C%a>Njjf4JJp~bW=6okJGRhqz}*S3 za-CxfoKL%hq-9SzfwT31MyZdeWMvWbI8r*Hc4rV07)dI5d}PnHr?$o?C+f)WuiU=1M;fl+LHE`+X=B zr9NEfsGT1NhmGmetSzxn;JSVo zMmU-I-|G2yXgsKNG~gu8rf5Jl#|Qe z`{_qgf^W0t$*8JB_{UWGky%HS7NJ``Ql)-dH9uy%OMwg7q6%J;I`D){nV(V0U8(VpG+N&UU+X$R-1u6hI*^?jeVa#m#5iUd3rPlOxjS)vv{jZ0nq`oiy%jXu|)~b zIqcktw6_pNV5*){^Ypg^XbJF1?oFn1yVdMgW;StaELr*s1B!w&P8 zL=sTqpWAguPD}?2G#||qU^IQ%8wCyfLJpl8c1Kobqc5MgzuAjm(uM{qK~u5lc3M-F2>~1pDxDRbFA7crjVWb>45@Ke zi>1IM#3Qh8$G*!suE4ppsjo6p=ivA~1%?WuhY1VU;uM}6HdjWN5MU}ohc?&hvqP7v zI8n;^^vRjD+w3HyDvT%*{b@Tq0HeV;GtP(qPNdC7XM-tPJ~u4=Rf?_nsS`#Uf1VHj zok_cGDsDQQ1K4hN!&}X>un~3+5ON}I2q5@A;C`3qzkSxN7XUXT@R)PM=E-^m$QuIn zYt>&wQ38Nmnw&13Pyd}r8yYIub>eoo;dV0$^YPN(3@-+)uRgp=p3;)<;X>8{p&0yZ z>l`@eMB1?4A#XUi&5FO?-1n+y-(nQ!X5EY9X4lbe)XO6SM&%5o!M>5sAq7sP4Yo5X zcVy(avcF!^6#&h0#(oYucOvZu`C5RIVJf&kzFTCcB=`N~7xktA#x|E66{~WQkrMgbtogBS_yX1e#UEEVMkcMmvx$JpkyiQJusJeKy}%yP=4>c#n58JbXK(R@ z`|fRcxZwoSYVV9ca$r?M`k>GRrIOBv7*3=O;%@6d7H)NmJY#Hh4=*|8ix6ypu}1G9 zG@vBSB?@Vc&#(a(S02~hlVRW5916*aHa0zC9GlVd0?p(++8{{>TrP2exHgYtgD(L7 zBVRe+vvVG8I-cYTTL8d;8*FJHoiKq4_wzZu^Jq8todb=s6z3xc-k%eM6kUXiH{q@f z(aa{cUDb17MxK!K_8;JgQ?u^KDux0qlbN94FIW`|Tva^-?Y?+kb7i2uL789KM38>a z^e?eq(-CSp$8}}Hi?J;V5@;|l9@9N8Fa3L^P~+u`W{d&GSY9g-e?!$cJoZ4Q3p8Ag zQO;M)vf;({DoxmwPcL%oA9i0?RHBJcFD_5Ri)~~-r23nH)05Q!!Y6|+BnLQu5e688 zxo`8$pd;yw2(Sl1RJLig&(Y!85Mvv@j1=3Jy{!J_kg|ezVinkE&kZLx4C91S$^zEh zKAn!B%6$kU=8LNL)UY{ohg5-=i_+h8yBVn(micZP_y!r+`($EEPID4<<~|b!IE^A_@=pWj#+I z{wnqHG?)iQ!+duGFY31#Y74aOaL;pfMyOAb^)XX);b~+4I9|h_Rhi zvN8_7qC|H#dNlg~Q`xb`IjSHVVvH*eNyeD=nwr@K;EuurnbWwF0W^Dh5{Ho}P8BY~#Yf53aN=;1{paO%80lI=OXhU`$Nw5xYW)HC` zrfb0B3lz*yU2u80``x>_D3^#jlsMBuER1Ddh{ z#@d#j1xGMYO>nz$p@pp*4u#BfyY9uv!3b92 z+mlsYKLbGMcDRf3&;VoXit1iS8)o9nN9>h{NxKsLfD60s$~5@eLb=MdS@!OWN*d^~ z;{^*qHo#a@5t~7NfKV{OXFirnCAd85%~-(TVms)vH?&zYmYa>FS7U3dpF_^G;l=Pb z-MfUHQ64!kw?CBr>}%(X>c#M4jQ%S^V;SLW&w=Sryas7Y(f&N78D30(GlJfSe7`+% zV0jEQy|pUz)N|N*Ho#Z|gGm-0V@aL^H`seS9Cu`C7bneJxmL92P`OCMbKtGk%mPiL zDSq)H%$1S(1`rRV|B(YX5E6Cj^+0NUUe6k2tf5~PkkP^AJXZyfMOCw1?tD0F6DRn(;D(_2uiRm4@Y z16au4LSh`w)t4{e)+Ks`V70JrVaeo)bwfK@UzY=TK8P#PBLqyBy31r|G*2kuR@h3e zO6Br7n!OlcjGGgV^u+ws6YCbs-r24ocF)W2Vt_GFf{Nh^XAJo~vED?WNxveE^E|Z} zUQA|HzIV77jnk5;No^32)MM~+9N3Ft##p2qh-t9q^c@q-QEE-)ru(@){x^s#=R;S7jL>=W z)C}+q<&Icx0)*kkphH1HGHzlJcZ~5|Ua^WJcd+&5OxJ18SB1)s%gk1$B(< zfzp!mQnnamtYsvb^^p*0nLh9ZwhFHWk^O>Lpcs-zm7&tJOgE!FAXo)L(8Z?FRN8(X z&J0-~yDV6ujZ@|$v6h!1l`AJlmFhg4*{BoB23gR-i@ADY&GKPDl$NUKyp}E59T^EZ zUT8<6?qQfYu+A88>vI4=anM-aigy(det%@vFu1V`gt*HQrSsdcxiV_7frS@zl036U zUo>pdPSiXuTyDAH1hww2sk+5VR<~5+#_e+Fn|20jVsKOU=CMN0bSnL%5c%r9mm5@5 zanu-jf0&zIqk0Zp*#1BT0|tt7*KpJr99^OB%Hf*ews zc@8@-4jW4-Ni1lXW!6yuN@o?46eu^b%*BB)Uk36J*ukkFINar6tmJ*bad-|pFOC~a zS@(|qc4nyQee0}HNtZlBa(HnaIdClPZ^ZnmdV3?IiW#?lWi02Y^Wv~E<%}wS^4K2d zkBh&ck&eTi1ODtl47NK$RMMooez?S9Qpy(E>~l+9oxfj)jV1kBpuem@J(6GuIG)u7o5eM;Bydljv-4Z=d7Yi{r+WgDA?-iKAy=K9|vXQhI?-_qa&Z z95)tcgspd9fzqSox=c%Bgf*tCn0?-@sS8;fL{~~upC}Iueq|K4u>_v&uUs8J)?*K zV-+li7iFgd$0G7+fCyFHdmb$Rt3fFx`*QUn2s?0${9`M8;d0sSX3B(k>2VGm+p_Hy zIt;%tCgnV$858*2`Mj(+Zj8(?koNaY&18?O|3roYbtumBVTj_F|#PTl}92Fq?Zq1+U=&z&C#M~+d#)9_U3={lseS(U9))!zV{ ziXq49f|yD*%tD68(?58dLf<*;oEN31A;;<}Fq4isQH1pOTi33`Qr?Rn=XSH%rKzLa zypQOsf!sk#-Ox#V!2nOq7B`Fj*#4cCe zGU+K_6rSB3nQaE>0_!tZRW{i}fk%+Vv|eSWmwfngyirX22cz) zCc{ws=tl25e!k73&&xnOg338JYkoZ8@Kc$>-dFZcCHnK5Y!r^ei^yZRv5E>&*o`SU zoM)?gvl#{C?Q!RNELLT}Im4&#RBYX3#+1faFP=BM**G?#>?RL%v*1EI+tmG{h%?Zb za`nCT1-V>`-U5vS_3o&mMz_&9x}X?ntSSsbWvzU_jOdLB{eGzY8z91Zjy^Aj8ek~C$o3E!1za(zU@ez2GE*JRxdgy!yP%XxQO)i(rxE& zxG_xY8_GJ)(dT8jD>El-G6~tE7s{|oq1G9pIJ~fHp1ieKYXr&PAL%O7r?A;s@VN6g zY`8J~`pKqRrA^|}ue#f|^eg@6dJ!rOJH|XuQBf%?gNMyWqjRvS zERIgkf_3IrsF1d*^tfI9DFi`@d;w)7cat`SxIAOH0@-kG*L-=vISma9%cGtJE2zV80kuOpN3s-yj)AVxi9ohuW>11;PF1<$Cp3o_ zk87UHUquZqEBmu6u7T?ho1jqP{v3Q>3^@k9=L!?dJTmrfH_{+ZOMX1hvFF8*V|A#0 zS14fK5}OsC4Uj2ABx2IJRd-|P393rC?QPYpj)^ACmJPp9wzD=GiZSSE#f>#_2|Ah1 z0qWAVDc8>Jx*JEOQW=%)l$*ca!AtLRF&?-mI+wd~MhlW=hl8Ey=8R}C9hx*0s&FupzY-yy0#4`+rOtL(QhHAb_w z_7eT?1F6O9)8dJrB zY%5I@6p{S})BU06hr^xE>#cFk3K&2xW@9dqQIMXkkTRX;Ds-fKaU#0$5u@UXS1lr;hgpXL{`n_~c{ z3UA^$n5P*W{ zwxys0Kw{oywx{J!du64n*5e$i8e*)nAQHCZK^mS@qix7wW1uH^L8epHqePQqo>y5lxg#gYkaZO!hc?Y@hTs^< zdR9Hp&AK0_$o05T3GCHha6*7=Z>zM6W!#$Fkx}v6d`*a>x!poKnNb)@=X(>?Kx1r? zqiT-&VtHiMGj4)zl=;$mI#~@grg#IFx9&&AMILw9wWlAGm7(0cU~^;DLhSPP?QZrS z#;V9CukCK<)uzp4VKY$~u;6y@u4orus>~>$&v|jn8)i(83;oX2h0GAo9C-_0M-?~F zCZ1#DtD(kf1!SbVzGOHYx*OIF2e~Y~iRZ9_YOt}Y7{MmafC%o9Sd#~Z1i}Q?+3)!%y&7I@b7~<=g}ZrFhiXpVvbMXOM+bw8 zAw4apK+QNm^z0%83GnugE4H<2aIp%NPXL#<@iG3qa=uVu2`T;Jnm4?d&Te6uV?ylm z$bU)Q=?afcf^+dA%!}C>fer)`3(tQ?+E9N85%LADs~TRc0>KQMWIFEh?}uGIJDOVq zt@HCDwi-OBmfaA*Sry5b+bwXrp(eBP`Ru3bb7J8(=N}%H2LWJM&eDq(ft*oOl*<+*~ zGd=yK7YFzG$eY&W+^#!vX3t5-7mrI1!kfIG{A2m~YHdk!h6mYL|} zE0JPp4d9U_JoG99ERygWp2u|ym8{skfPzqfS^nF3?cj{ph6$b9t!_7h4%!5B-5Y+a zX$7t$_zllDovVSx^lFvabRYl~VH7i5deSXj;E1T_v;S&nF+@@n!~vD${>Xrlpa_&7 zFdUx4&a1)2m_ahhL3l1dLQmd-13+)>=lgKg;9{uO(pmFcjYilxHAXeO zm<-1S{^jL!?i>l#b{z?P?i_Y*pAK|iQSQj*Ppx*_>xfKJN@$!)KA)aegN)HgrT9jE zBswPq)V^Io6v8Pz@A)Y<$XMMx;g#<6W|MuH#eoBuBc=xDX!hFNmlePRtwgfG;dVu1=aClitT(j$UAEGmMUCRL1C$r7M;(Y zssqMoA2TmjMin1P@aD`=B9C&VPr8Yystly>a?590{Up zd)!5NXoxZ0_xf;&hD>xpJLL})ag{~oat>Fhju?YJhr zFqYV!M>cSz?s>5OF{Sfmbf)wd2h9OvsZ5xuSZ>Xp2g9Qc#U^ym<)Y|x#28u<5QP_L zPp3by&TgoLid^eOzsn(G2n!=Jmzn!E_c+qG-CY~Gosa*kL&g-=RS~b8AieT8kt*au zBrX3BXYaNvOOqREK9K@`K3obj4c(}vhGrm4U-kRH#qeWhlCeU77ehh`T~+GecI?QY zI}b*)7utT!8DmPdlAj)#x#fvkSHG#M-sS5><>`zuIVNfG!O_f~2xI<)+)1CR#T>_7 zjw^`|Y_XN+EfJP8o#`Bu^#0<6aK>1oqmyW&C1tgIVHehB0#3las1S`XhVTP?ZJxMV zo`%L9Z4*q)_KO0>31dleylbfm(RN2LB|2T$ZlIm`qEvRm7(US#n^Z_ESsu*JI*zL( zL-8f`=7ceI!jSV<*|h$+>dZF~-v9CPWCM5`ll_@ZI!gt3GU1}c<0tIX#Q7iUR1?$J1WQFuCGOp?)j;qgu5 z+usAtK%5=WIE-(^H7AUrZX;(cS7*Lu!8EL3>cKQgaRNYd!kByt6^cd~bKbIGwJ_}o zV!+5RW_->VOG)#*r4i@ppOj4fYB+7~R$i2d&KQ#fO_CF}&2&#YDU#?^A}%%G^dgKf zR{3Er`Z23{Yf4h%CKn%tM``g*yJmbbq=~RVlh287)o?S*>*~dwUgWAq7lTL2?}Pq$ zz9qqpfs9%xqq7kI(2Oo7z>-Qa_#SEAlHkVP8OO3t-lDLAd(fGf7`i4jVTQz@H~#wsiM7fr5Q zs%NWpu&g4*9Rc^EkaNyhQrg3UX>v24Lrwe|SSYIMS{&CSnH?%9^7BIm`aQ24bQ9ro zDPQ7j_O?Bg6K;7@dt(x3W(ysiJa%at?7A1hygir`?p;%EG923di5m-7^p*0SFXnMZ z8{^<hXXnZ)q?p09Rky-(D~Jl|~zrd>wm}ofEzu{w9=@RG2087oV`P#?Tg!Ai!`! z{sd2hYfTIHEt*&PMOtaBF*ywJ%}kOBmF8(M)*FzTQ5G*R=JiG!LoRwTaq4I=dm1dc zw%kz-x3@QSr_sjbkSWS1#*y38U$^I=s)4&}5& z-7ot~NEO}oh9+Y;Cc2Tv>OvM8|AVZw${()k-=s|)T4ubM?M54`=p9`YhVn*k$+eso z{51y0$BGxTJ^AsFk&fxiZ-yt=k_@6*$;Se69M?|=43P`8*IYcgMqBgRNE#?@H;!w^ zgZzW2Act#5f6{8*Qx8EW_LU zqdaZ6os|@D*=%bz_Yo zz$4#(V$rnSQtMJ{=A~Ek7~{2UH`W-UfC-bB#x{ee*0l34e4ddwjFaTMvBr?f%X071 zj2xc$6%-XK0+sTHUp%jc@uUsMBz4Z>nKfop_#Rdi4&!9@o;{2+(@rVC*+loux{wc+ zhI-7XFHT@%HF02F2xrr5@C9G~7!<`P#dn-^V*>}K=IO$SWk{mn9#H&-%s^*GpePJ$ zLti;oXns~U7;cs{NzNPow28YwoGfM221*uspK^5P<^~z)ugTA*4_9db`@hdSI{8j zzF@sB9uZctR7w0VICrEu)nO|v2t2mNi%F!tSUGPXQMQ!%jcm$L<!aZ2s*qg=lKOZ69v|6Y^r)oW|2T3<{chexh_m)F3m zE`RK^zPMA(3*@!J%M70gHeaBDA)#N{W67SFN~OKvKTNEhrQSVF!jlTL&lFR9+}et$83wAsQ$*AmX5XPQt0q^YhwkusS3SQ_A~44 z-KtLM#^G?pyYx0wm<5mKwdA&1ySs_CT0KfU58iJ42)ChvBnP9z&Vj+_JVdlfrxHS_ zuH&EB^ysCECQHC+$1SW$%{YYzZy`Q*oF2Xz6E&1`+jQq<>NcEqM^@eVFsz4fD(X&L zRH^m{yJ#yztf01zFX7lk0kvlOn=NN(1a^kW?+D$2Tv9?UoF>>r0md`^O{q-fw#^Q! zD@wKFAP}cS**szLA{jPQ4SQEl)r>BLvrzhvui)4?L2xtO&8Cs&Z(+jWX}T*CKNjp^ z@qD=wowr$CM?#WYc8v>qE-60Ww^71Gvgo22(#D7C^L^OCE}$G=N)!x#xHe0G&ss3? zM9(1GtOl>!i7E(;;1b{Jv0(zfm9$1Fc)NQpV!Q`^LcZNyjFq1!Nqk+mU`q!H@2y3yMNxHMV$hxa1w-de8i%oZG?h=0m z9=y5J(4 zhYe$(41X6tU4|He5LkCVVz2*vE?*qm2({ z`fMRm{_6bo2TS@d9~i>^@t05?HPLlUIJx>cKG8m7rb!+3;@w&R%j=VnYMLi|k~?vF zf{{v8$9rNnIgskJ(Q=gR6Ar6Cu2eztg(%@A-nz26!NfxOQhg~5<1A&G*yJo$dr*vx zzXglm#1x$E(N>ygxumDpq+Y>9BYcc)58ndeZ6c|cx@TgNmO*PMQd+$D$vk@HBFExH zqs-qyTW}h(2HC0T`Duq6i;luIzg^-6c4E4TziE(>` z=Re*8|M>lnfBqGM|NKAw=l}En@p6*`EewHJvsu-zS<7<<=Ob zb%P}6l63r-e>`A|!%F4MK?7;QKHSV`1M3$A8ODjN_rR@=JGZV+Ok$%NE=I+=bu>Pl z!ys;R+)aJPs*zJgkZ+Y+12t)+G!B>V1GXD1ePCAC<jdyWQK58UmzD39&@C(>|j>MDP-&UzVtQT@x6*GPw)Nm`l~Xbbl+m31rBs9{)- zU#=Yvg#l^79+vGI%+%TrM~@BAnTiWtB<4R%>&lM7iHw03JSTq8|tnYnn59x zUoISI#lu&9T`*!{dG7~{b1<$}RR2fi^9QWQZ&it(LDzmvgQaJK&c2kCn4kQtRO(w|Mk6T`ZGxVU=nGj-uMFq<&+3CP_hDk6uP{E0<54+2P3G zPd798C{B~qzqtN|C6%JaS+h1PCrLr;h2#gKo#8mE9=;7K7bZ%1`Ew3E3*hIQ_uo!Xdre1@2fF)q|HhU(yhER8(*J3yUiqd$W{Kr~c9d*I#X0I&Hzm z*L?#^RwwDkmA=P|=k@5tWs4)QQs?@Y!0cM?;ELqhX}I&i_3-UT3Bd#(?suc2_g8{~ z#bhEJ5Y@w%-V7cVFdywL6bM47aY%`I6dHTLdiX-VtEkIi{A0gbfd-2#L`MK8eA-lx zU-|p9K(gED+hJKC85I#i`to5|4_}B!4L7)3m|={8@q2$2W&R-~tt9ur$IKIa>=BS= z)D_e?s>d!0u=0)R3YZnBP_c9)rP3~p=QYivcVMh4#a@wjXzgJs&Wl>&vVyu!Q=f;VVbVLQ#8Si|6s%Kbow_@9d*}%L8{iE*g9CGVY`iDpD|dyaA#}$$YVwE0FLrRdSFh7?n2@4PKD3cevcC`1?5JJ6Q#1L(@^{dY z>d;Q?Q3m<}TO4-4IBl>Ulu`4A9SeIO!X%B;HunR!I<8u&4R+9!#{b2g>?I|Zkk9JJ z58URsI`v%;th_YAX!+8GkaF|_e4twRV)z0boo={oOJPctwUCm8ti=-$xlgCTvWkfr1k!QG;`=Xb@)tMR!)RD%TTRfC z?$F{LB6rm3r~QlSPb&o*;m9I$={Hz3a;FqVQhgtjmA|-ld~6Je8VeU(B=#2A{ZYq6 zOp=dn`oFk#d~8Hur4lCJYj&Zs^aa682LSO;!N*n`TdhnHC+EF!QQ#R$z(m*>VL4RQV(_IJ~E^3&mj`hC)meL51Zb|KrwZ>o6wybfQ;tMt9;^5d24fukhK4**{4?;##k{A=g(djjOh=+(&?+HW zIrj>_=159%cSkc47D<^P$yBXx5Og$;IwZi%+x)m$B3wM)lC8D3XK#j~OorD!Qqc zn$&8CIgD%3D?gSTfE_p8)cjL`0aX?TdFg0+`+&8VOObUU|CVkYRN7mT3ujcY+Ofy? z7qvMmja}`vEW3E?VoMfR8oYR9ei2d>5M9IA4P zvynXSbX=)JC$uEFn8W#p&#b13zYUbk`?frxE|!Pn5JSQ4xMusnweX#=trwEFt-Es; zt7C=peIS&6au0amTKqDnh3%wqwhR_)dUON`v-oT9ER23Hi8*FKb~rZr4;W0yp$=^| z44eFEkusJ0b>RZj{BfBtE!7ZR@w~?8R+GXCEf{E6+hCz_N(P;){woaY@rz*(<0OXo z+dl?1A+4xh^>cIQFRll0DXLsZ_t+ziKTLAo#K`$Ly!(sl0gO@!dy3V&-mLqN&zeB* z$4T~IP!Hd<$?1(Jz&hx@pg$7G@$XiJ$Q673Y$mOVI>LwID7V?lC?3|<*>{y z<(tKz*~=%Es<0Hs;q*AR`-5tqft=nMw?=J$I4UC%v~IQg5eVJ?`1gPP+g8aRmcd3@ z=hmi?*uxD|uAY46kp0EYhKmaWGFFn3di?}3{Nfv_2;c;Q;Rox_%Qk>qWfE)%viyK` zeNn0t6h=-qEl*s3UTpN>Xv_clCOL4Q(UyGH?)^BB_56g zn+Ndk0Omj$zkbO4E)u{{sB-ltNKvB|PEGmXPBMB=;AX&hhN0X#&<3-Up-c+(I=MWK z(v?r(Zoq~4Uy+<^O7U2h*R9R=gbK(DSc_%qa(`DBu~yXRHvF7 zHsVxK85*vy5mOY_8l(TG5Xhqt1KU@!E-2=wCb-3)lpr)xRn^YOc z5!M6O!+5Ls3pUhc+};D4JJ@ouO>$aiegaz{7xG9JDrvm?J&(%bHPsSmAIFXlT#MsE z{xO#t^eu;-+$1oo)P^gLyB${*Gp2@H@$-S<0ojGdTl_?;9>**WNVzton)WzmQ*!B^ zb33Z*JYg-43yur&<<62n?Xd6jF6Kp-6t@szQ8}dbxO)^G# z@x;Y}@hX&;m}<2_A$lkUFPo9S<#!a<-mt{kz;S`Sq-vEmG%dQG5Q&eP#7|rcW%-vJ zX$_yhdh3)m-Ss7=*NaHDwhOmGzo0Kq*0Ak|lUGZ-Z)wx>NTYZFTPS1oEF~5Tm#(4Q z8rtiSi8R~dC}hM`7`>CqFM|~?vN)LM}~?+@1^xU!|j_U4qD-546UWEK_%r>a-K_;@XZIlnF@&4Ufr z?!a63YVbimDquW;mjiQ{;UGsF%D&*PqE;-gt#YQCJ%BBWC7|RmYuX!giCB~$Hw~0e zyv08@`|0Gs;kVx#GiAr6`^!K9Wxk_h?i1GnxenUf6EoVjjjsX~AtPDAYDGDcb{@bU z%Y$<=vH#P3L%*SKdQIhF^%uu__h6o!lSPNPzG2hw6uy`U_AzRt{r>~@a7J53P%xV> z{vtY55^bB!8tEJTa6OuLO1e`3m)vnj(#1?{wOgd2{=@ZX#t(HTB-nZzH(+dy+~AdVa_Kh>*e>$!Egk19v&@ zWcgMuqup;G8m_KX`F)p2i}nX=p^WwcGHe=g4$J(Vei8w9B#8aR9UjTl-BO*@natxt zl^dyN5*`)t^T8;@l)^)I4J9|*-42MMri~M_Yg;E>#V2rfV3u;x1(m0>aDzfFdBvLY z-^D*(qY#rUvd^2vS{F3r&8OM(Jk7g8NJ!!A0_G!09^(bucrO0yqL@9DKShIbLJ&llFdTqZ0RMsLb<_wHFiLz?w!jta*zOZ2g7?~O2ll~h2gQh9~>NDqI&x{SA^?GPyjF3V<1AVtaM zx@9-;xE=n4bu)SiIkwrMal5=j#}(!xtsD8H-V+6OJNgBOS!e3Q1hM*RN}u~IOq@m$ zPh5ASXV0yvyVou^+=(=Fx!y?~Riwb2J8IzsW%oJa8-E``EJaNs^2V;?I zAUZeu&2oDUIAUarCHK?4`UEylA35hBUQShhzCAE*r;=>qW_R53cmkWL&nPNjohl#g z?TM%S*O-THT{VmYo2$=|!ig~ws=71zrzYnLvUCunkF(k*@N!_N<{N_ow+~ngsa?4F zr5*PRpRnfXgYv|+<=7+QTqu31)D+qNY^PTF1J_Ro92TW`ED14Xk7P#qLpyi*RP8=t zEs_VHDi-V|-=38kPtGCu_@`p`iE3dyB&e=Tfqruj*!bhHfJyh}bOAnLi^DS9Vk3OG z#UB2#a*NXhI*((XuolILa$W_=!JC%3{9Y_LAYpTsbbrEn5c4h7n%Yio847b7?bccm zWRD+Z*+ZDFM`OgnQp^r{k^( zg*@p9(cf>&ov!oP$lSjGwgARThPjJWr1Ezj4ve6&%FX0-T4Q|zTL`mYeaWK|YRkpf zG2D|{XMAEU{zbS+#adD{xn2YwjMw6Pr~L>4IOL5Ddq2e^GZh<_oDSI z(bi5bK~LQ5xSLM&UzvKW{( zcXpRFRbFp}rHV~`9M~h7zi{ECm1pw@=2Wjb4a6%vj_Z-E^O32pFvA`gb=`ynK%uGs zBI_$2$-ChNr0Vh0T5N-jHmo+-<=ijQJ{wFaJ)H^YC*Z0B%NIP?=f00$@;A>Yc@c@f zajDhKs0u5or9wG4DvLg0Esoj3W@}oCqi)m|x$4TtHC!pdaoFsz3wb2Ci91=NQE+jH zl&e%i?%1Vy0$U)XQGq$U4zB%qxiRDj?eG9!B$GCkVhMX(a&OcO(aCVRF|_R3NZiJ8 zEs}BhV*bg^TW`1$7zHCKW)d%6aCcnxZF{XWFddis0?DAY%TX=siEDwpI*QP6+j8rU z%ekKM+o5q0hqX9Xk!2(NcqN3#tHztZ2+p~|Ri6t+JUT(LaoA|SN?tcr)wU{TQxf5B3sE-?*^t27nM@p=W)*y*g|>JaIFF~DLNfj z_rT?wkW2f;3${p>3m%dMOC!Vj*&}mfNPOV2c2op?;x@u0^tB(aJ7F^sqZ_(G^hy@Kb(~T-$J}u~*5b z3ZZn28?Y|a_o~VkFMc=+~!*{N^Wvu zSw3-x;YtE@F#yaoDGe88TZu-A?{ShHdBRSEEtzFg%+v0l83#K)xGGi@hg}Z4*s4Mc z!>uL;x)m&NR#eemq<%J->he>4b`l6TKhX*&dxJMbxWzw*VR2mGlqx@5QeL}~QTDx9 zCt^}_B11lLlRYqNGZ+uC_F?;F@+%@wv+-xy^F>l=vndJIf~_`+xvp=6nk&Mr8z+_e zBB`|LlwNL^s`I0J=fG^)z;Z{t>qS;+<0(}LmHJ&Jgq#m4z!DjizNp;U@qjal8yIM; z5ezP)>MvO5J#eT-d2v1rkK=`aseHx~8SVDNabv(KYWZl{{KWM*X3vGe%4UtNIJ~39?Hm#Y`&+idv(819yD<|+RAai=!t8gj3nfR!DQci z`-|Yln5f*!gP-=-p12mtQi)eQlu;2*UdI(}r*!rS=8I~;v~FN^D33)h$dUajf>dUS4BCRyF8Lcv`)9T90M4c;%4Mkn^YIXwRA? zOT@*W*W7eB{-AZpeX*7|t}C=#2(wMTj29<_xj?b)J*2*bEtR%EE?352NeJ_)_)%#t zQ23-uH9$w5J#0JDDtCs|0;`mEw2^%RTPPQ{&k9+ud=`GmRW6N@RbhI*$STbUs`Esb z{B0#-Q}H*!r2!4TYS%H2YmqF8^^$~a%v5Ui^ty6u^sRdNZ=Sb1?jBd0td5UX{eihO z5;k6}MZ|F}j^+AoNWq~(=$CTkY)2ki4xrN}*Av#Fn15aLYqk9G-;JX^QDc;KR84vU zTObQs!q|^O&{s7$gfLiUsdUX3Ln!lu>QDzR1R;nx$K`03Watxd@h{lyt9PsNyS1!D z6T|H=-!AQ+yds)xkK~D8D1M*AM>=|yUXZ~s!F@4;+8)XBDe1-lGO~u-xGyj%z^5ht zcDKhdj`LeefreIy!G8Pia?IryRgoD&(ZGO}z;Q`$$8q^fU};ui$;N@318?|{88?z@ z8#aTPw`45%I@1)416v#;D9z2wXu%E5nU%p-uUYkwQscai-pmE&o zxX3+%%5t&6N-W}^K){ug-@{*AA0|tM4NaUo-@+InW84qwfZ`VZ*=&-GX%Ph5y5x{d ze$iLm_d}t-)%c?tUrY|zq38FWXz_czaSLe|evKF8f?|9zl4=^?@Q)1{(W2~cmn$Y zu`pa>iDdQa56m@$<-=O>9z;ogI&4;w3(m^RljrxWxxsM%<8}ZR=v7d~Pdy7^^Ixq<#qd#tY73 zY_ZPb9vk18{Lk-AdW#p%gmM(k_*|etHYwFh9n7V+6JrR<$~0IKkY_6VJ(OoVtWEz)LO=2xuSDh zsKdouUB!kY>OOD{{eO0|V|@_c*F>Ye&7(EY6W9Zpy$Sgnmp)KZF<{Ph$z@>|uXvVP zvPZH!DCVtm);|1AaIQ14?s$a@x6%nMxi+XHj3b8qy$%V{I?32c$vRf&_m zJJ=R}^l-2b)PY|`oZa$LSTN#NNAhBGxQ=@4g@l`wTVuw5IlJg-qHX3^RcF6;w0AY= zxjKI?xl=A{p7y3nQT2d^$b2FXYw89baoaMJ+}d<7Z=qPwvO^ znJWB%2QJd6_qG(z&x|o9|Fb6+f2@$&Ei^dQaj4@qeUYIWVNB9R)N=}bPakOw4s|O0 z{#iwfqFM}h%qiu8>C}$xxE$&U0eSgyXl($ql5j=IyA#u`VBi##(%)W#5?m zP-^(djJs98jpg>2Zx{7FtOnz-9>ZHo0IIf)7_EN|+!yK$gAL#dV2|Own1RTB-q)?e zKDaOVFK1urMLubSF}P+>N96~s#m%$j&0^oNsD>mCEzCkb7S+XzDOvIF@{I-x+F7`DzLX3F1Bd7 z@|&Ktrk=1C$rG1R31TVYmRm!>vq9ZW&ZEIhD-LX-JaM9hN{d3DKkv|fhg__ZpTz^& z_+kQ!8l7Z2i09m3Vby4Yr6b0S-td|w;aUy@f60d_y)xE9Ltl=)%` z*V?D_`93gL25V2Hd@Kl~z!uADP_GWm<+e`CD&M}oDwZ=o(kjLn>-eHxwd$NA-M$De z4JjBi+F|h#Q8C7td|e6ou|yH$A_*)6tL8~xH1Fe~(LI(ib7_dt;h*cV%&jpql)oC^ zg)7b&n+wfPx*WOs(2(=~QZMj%wgG?QdMK;EF)1Z(O!m_eDRS1KkkIoewcYxfj`=bH?OemfCx5X3_S3>EYrlQ%ND>5$BAtg)X#CuIBP>m!(YJiVf$RR+sa| zP?@;o{E-46d(y6}2_Kdo+rx2Oi(^Jvtfout!*SriQ?Kg5=JSgz!ueuLK3kmDRawdj zO^YidrvxwdXn*_!wm4=dixGw-{aOFKFR!>$housfvVXP*5g=i zAOtRQA1=6x^Eh-QR9Nc^>RfTg7{>BAHfg*$E(g059t#`Q7ySYwvty_$08p3ZI`d{1 zOU`VRG!!ac;94Y0VF{r`ejE%t{CVZ=AgxCq?Zx!T31eHz_lvY8 zq|tDBM^h#q7)}=3NPlK#IVInoS0e3_!UhAR_9FRnx)=sVu%hMafH>#C#5bumMQ!}x zalY7|h^xv0qf79HD=$FhObECaxu0#1B=ujBd@>=fVY}o|^H>F!2bS22N1PMJ_WJrX z%iq|`!_AF>dM9Pqice>I!-j`W@jbTpBN4{<}6Lqw>a={;DvbyCpdN?4m|N7V7ZgLJ`Ow$xOS)%?0F;f@&#s@ z1XG=jDD8`K%LrrgKy{L=_Jp`bHC)d2uY&oD&WZ8G~{#jDex^WClQmK<+1{E0XaopsOyPK*iGMn|s<-$PEM1_T3Ja0B!l;@b>^w#zb zmkYHm@~r)00A+-+LE3l~Il$&^yvLGO){Spn+IjIFJdzip2a-qNCg-@^7?Uj+mY3ou z&-O^(DgY{pg5<)+`xIc7t<--R$L~0>NAg}O&Wk6{ zhg|G5TuyeD1(b360@&gh@p@)?A9X5^V~%ze`%N{yNdAm2CNCf_MVPAgXuurqthhoG zdXfDZUyS9=As_4bgq;6$obJl#(W)j2?4kvEVbH}E3FKX;j7fG)vlZjL_=)DC1vuM4 zrl2?Lic*ywYS_nV=A&`O6V^@$sVm#XL-V2DzBKM4b=YBZB^(;nIAe%qv+|Xt)b87V zH?AT^>J*)ER68KpHDxF?TifY|Iw81YQ8`YohEG^KAK1aZ=y8rxjBqp6>Y0&4Q-7p5 zZgX60p&7Srx5Aq6f9Ze9e<{gH_~Wg{7#l1_;-(|tushUXPF}SYqdtw{bFwBsA0#Ka z^eb*Sa2PNb2DPEwCncV#*5v1dq;`m2N-5mBsWr~5dz0{6ap7A}k0;*UsKyx^ z>>{JJPwUTjF(yJcmt=!-_SOIqx;emP_2q(c z)5ibQCp}hLK zATAMcR>#f4>0U|L^JjxJ2y7w%^7p46=5i>-Lzx}ww&`b=le@(e)gyUVvU6IL&9^3| zx{9o&=9NcV=qIp6GUlEa%UKimx#7SP8K|XI6rbZ~kI}{kepMIN%5>^Hy)G<}O7*jda4r_rd|5gRoVQXi?ECsTvUB+=OkZH_a z>?}%hQ*S>USBAVT3AvKvscN;!iOsyO2`{lCj9bpf>iR9Bbn#Vn1Es`sm&wJyW^6>!vJ}y@VVn|rWy+|voNAkoK zL4LP3*1o+5?u>#IWEmws-Ky0ic}aGH0GgDu$@A;NogsB-M~EUm-Ky0?c~im*)XKLn zO+7G+d<$ic_-v~hb8Ju+Nww9aU|hu;28BCg;*H2QSUfb0I!1jwFU%-VZVbAr zQY!8EWUCr;44YjQ8mXU++b?WZ*8p|2dT|dKZ;UO_3S)5H+<~!HK@MJugnINyexh0w zBh)6Ve=T`B2ug!`h-%4N|Raju@@Z0p)QgmHyPD{pjXw&O}zB$WQ}C6C8N^BBe_IJ5IY zMrpVl>db7@8tp|C8)s~wopW)o6^L^F%W`$>BBR;=fz>NM3xYY$0U#4(W_iwa!|l^*AT58fR>GtPk z+g+04sw9T^SgRUiY_OFA!-!RvWP^GEMpAvE=A6rm8^ai5C?ji{0N?L6ZfM*Y4Y_{K zyLf0AWsHraC8;h=xN7?@g1eaUFs3U#KGv#68IyV)-1$%q{>}4xByWTl)tkHY8?1c64Y^f$8S%Tbdm!(X)$p2#b6C`A z7OWToF8)238Y7mfmvVHIn-XILf1%>!) zFv{4V7mg31loNT~a0k0=iSPP_LVY#L7?IYmij-l!`EJd|jRA=pDef29q*2BOtpaR3 zU=-gnKPa*A{{{aj`~g8@aXaxE$^3os#kkDfw!YF{ucLy1^51!v@I$JSgnsdgM)B=P8 zB%#}-jAIPFMAVbxl)O4UmKRIo$i3!UXmoCjLVlJ?+~=oG4`n9ja;zh`v2L&u7;>%^ z7-KJT%ITrJ@!D07!@h?DR~=gyx%FFqkyILGY)s6!85kpI=eS%L>Y9U`^oti^k&JNa zg$&lFs#p$;7l$0sIMuzFKpAO_N)5TTsiP+QB9Qo#NZegV881dq#u^*Y)$#LS)uWme z*!-C2E#)5U7b7TRjnO?Ga!r=5R^tA|obQ8)ItzC%V#9)2;)4A*q$D|X4Vd#iv#O9* zTu{CmYivwb7x|?$=8EIWSIkkWF&F=eoi?V<&XS7p-|yfN6Xtc$Ru?HjI> znK!mlG!yoVG|hQqaLX?i)o^<;+`+AZZ%P~ag;0HU-WU$^pHYZ>!hUTGt_^IZQZ6t0 zz)lM4cRbBP~nP8wUPZ7J0Y zI->mstQI&;6OA_yY>`Z=%MuD1N;d*1F4#$3r3$3e7jM`?S<+JNP;12b3+8yoXHx?% zendELY;RKZ{Nb7yZw!Z5z7yq|yr5Y)Z)`QFo^n&lyPmf%f@_00FhhQO z4;YimgcylbUN6333ud)K+lXw*`|&3xy;dWrS3zf6�=h^wQ;ka84!G$4%MBl4Q0x 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 Y9p%pw`71Qjb-VMT-yTzbcbC@9$I*g!=EWtCx( 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
mhEG`e=+m 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 LVbSAW+?@Xbe3Dc6 literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitVehicles.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..3156247920716188746f17cfea00a1f98f67f97a GIT binary patch literal 640 zcmV-`0)PDGqCiB}Y?Gnn{u2IEXL;L1oUwD8oN)&h`)S zaer^GH-#~y98RHNf*A{IWIct>@YtB@C=Bbm4oYG)D+8%UVZk`)FgH1V@%Sa5frSKW=Gl$o=ZxMOK-zeXoEd`y@Y#($RYAU-zVhr>L=nz`Z=4lI>fh9+(RJIR>Nim>V&Alp9z~1o21dGqR>o4fT^YWJ>Ln`_?=-& z)B-8EtOrHGD6^t|I3v_Zg$nnziqs1m$Nn7|3{O6e4h}xO4!TiyCnY4iE1}Z~I?dMo zUzBh)_&7>9{Papl({v{##8G!wCA8Z?`&s+2N@!k-?(AjJSo`XpuIJ=nGf+wow#9_)b! ad+5P_UCXxYYuuVwp#ERAeq+uA4gdffHa9o` literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml new file mode 100644 index 00000000000..f45e23b0f3b --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml new file mode 100644 index 00000000000..89d2e617067 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml @@ -0,0 +1,57 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + 2 + + + + + 999 + + + + + 5 + + + + + 999 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml new file mode 100644 index 00000000000..05db21897f8 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml new file mode 100644 index 00000000000..790b6b25dae --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml @@ -0,0 +1,35 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From d7b82188066c83590e4bab64dda14bc13f152435 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Thu, 27 Apr 2023 16:26:07 +0200 Subject: [PATCH 007/258] Add railsim supply builder prototype - Core: Config group, transit schedule model, supply builder and supply factory. - Circuits: Default planner implementation for simple vehicle circuits. - Infrastructure: Default repository implementation for stop and track link lengths, capacities and speed limits. - Rolling stock: Default repository implementation for vehicle types. --- .../railsim/prototype/supply/DepotInfo.java | 106 +++++ .../supply/InfrastructureRepository.java | 47 +++ .../railsim/prototype/supply/LinkType.java | 31 ++ .../supply/RailsimSupplyBuilder.java | 364 ++++++++++++++++++ .../supply/RailsimSupplyConfigGroup.java | 333 ++++++++++++++++ .../supply/RollingStockRepository.java | 20 + .../prototype/supply/RouteDirection.java | 30 ++ .../prototype/supply/RouteStopInfo.java | 69 ++++ .../railsim/prototype/supply/RouteType.java | 35 ++ .../supply/RunRailsimSupplyBuilder.java | 151 ++++++++ .../prototype/supply/SectionPartInfo.java | 39 ++ .../prototype/supply/SectionSegmentInfo.java | 46 +++ .../railsim/prototype/supply/StopInfo.java | 87 +++++ .../prototype/supply/SupplyFactory.java | 220 +++++++++++ .../prototype/supply/TransitLineInfo.java | 214 ++++++++++ .../supply/VehicleAllocationInfo.java | 18 + .../supply/VehicleCircuitsPlanner.java | 15 + .../prototype/supply/VehicleTypeInfo.java | 61 +++ .../DefaultVehicleCircuitsPlanner.java | 256 ++++++++++++ .../supply/circuits/RouteDepartureEvent.java | 140 +++++++ .../TransitLineVehicleAllocation.java | 84 ++++ .../prototype/supply/circuits/Vehicle.java | 14 + .../supply/circuits/VehicleDepot.java | 116 ++++++ .../supply/circuits/VehicleFleet.java | 190 +++++++++ .../supply/circuits/VehicleQueue.java | 93 +++++ .../supply/circuits/VehicleReadyEvent.java | 14 + .../DefaultInfrastructureRepository.java | 74 ++++ .../DefaultRollingStockRepository.java | 39 ++ .../supply/RailsimSupplyBuilderTest.java | 131 +++++++ .../DefaultVehicleCircuitsPlannerTest.java | 81 ++++ .../testRunRailsim/config.xml | 44 +++ 31 files changed, 3162 insertions(+) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java new file mode 100644 index 00000000000..187a5e95ac3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java @@ -0,0 +1,106 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.network.Link; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +import java.util.HashMap; +import java.util.Map; + +/** + * Depot information + * + * @author Merlin Unterfinger + */ +public class DepotInfo { + private final String id; + private final Coord coord; + private final double length; + private final double inLength; + private final double outLength; + private final int capacity; + private final Map inLinkAttributes = new HashMap<>(); + private final Map depotLinkAttributes = new HashMap<>(); + private final Map outLinkAttributes = new HashMap<>(); + private TransitStopFacility depot; + private Link depotIn; + private Link depotLink; + private Link depotOut; + + public DepotInfo(String id, Coord coord, double length, double inLength, double outLength, int capacity) { + this.id = id; + this.coord = coord; + this.length = length; + this.capacity = capacity; + this.inLength = inLength; + this.outLength = outLength; + } + + public Map getInLinkAttributes() { + return inLinkAttributes; + } + + public Map getDepotLinkAttributes() { + return depotLinkAttributes; + } + + public Map getOutLinkAttributes() { + return outLinkAttributes; + } + + public String getId() { + return id; + } + + public Coord getCoord() { + return coord; + } + + public double getLength() { + return length; + } + + public int getCapacity() { + return capacity; + } + + public double getInLength() { + return inLength; + } + + public double getOutLength() { + return outLength; + } + + public TransitStopFacility getDepot() { + return depot; + } + + public Link getDepotIn() { + return depotIn; + } + + public Link getDepotLink() { + return depotLink; + } + + public Link getDepotOut() { + return depotOut; + } + + public void setDepot(TransitStopFacility depot) { + this.depot = depot; + } + + public void setDepotIn(Link depotIn) { + this.depotIn = depotIn; + } + + public void setDepotLink(Link depotLink) { + this.depotLink = depotLink; + } + + public void setDepotOut(Link depotOut) { + this.depotOut = depotOut; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java new file mode 100644 index 00000000000..79b583a2238 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java @@ -0,0 +1,47 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; + +import java.util.Map; + +/** + * Infrastructure repository + *

+ * Implement a repository to provide for capacities, speed limits and coordinates of depots, stops and links. + * + * @author Merlin Unterfinger + */ +public interface InfrastructureRepository { + StopInfo getStop(String stopId, double x, double y); + + DepotInfo getDepot(StopInfo stopInfo); + + SectionPartInfo getSectionPart(StopInfo fromStop, StopInfo toStop); + + static void addRailsimAttributes(StopInfo stopInfo, int capacity, double speedLimit, double grade) { + InfrastructureRepository.addRailsimLinkAttributes(stopInfo.getLinkAttributes(), capacity, speedLimit, grade); + } + + static void addRailsimAttributes(DepotInfo depotInfo, int capacity, int inOutCapacity, double speedLimit, double grade) { + // add in link attributes + addRailsimLinkAttributes(depotInfo.getInLinkAttributes(), inOutCapacity, speedLimit, grade); + // add depot link attributes + addRailsimLinkAttributes(depotInfo.getDepotLinkAttributes(), capacity, speedLimit, grade); + // add out link attributes + addRailsimLinkAttributes(depotInfo.getOutLinkAttributes(), inOutCapacity, speedLimit, grade); + } + + static void addRailsimAttributes(SectionSegmentInfo sectionSegmentInfo, int capacity, double speedLimit, double grade) { + InfrastructureRepository.addRailsimLinkAttributes(sectionSegmentInfo.getLinkAttributes(), capacity, speedLimit, grade); + } + + private static void addRailsimLinkAttributes(Map attributes, int capacity, double speedLimit, double grade) { + attributes.put(RailsimUtils.LINK_ATTRIBUTE_CAPACITY, capacity); + attributes.put(RailsimUtils.LINK_ATTRIBUTE_MAX_SPEED, speedLimit); + attributes.put(RailsimUtils.LINK_ATTRIBUTE_GRADE, grade); + } + + static void addRailsimLinkAttributes(Map attributes, String vehicleType, double speedLimit) { + attributes.put(String.format("%s_%s", RailsimUtils.LINK_ATTRIBUTE_MAX_SPEED, vehicleType), speedLimit); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java new file mode 100644 index 00000000000..4aa00894fc0 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java @@ -0,0 +1,31 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +/** + * The possible link types in the network. + * + * @author Merlin Unterfinger + */ +enum LinkType { + /** + * Links inside the stations. + */ + STOP("stp"), + /** + * Links in the depot and connecting the depot. + */ + DEPOT("dpt"), + /** + * Links on the route between two stations. + */ + ROUTE("rte"); + + private final String abbreviation; + + LinkType(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String getAbbreviation() { + return abbreviation; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java new file mode 100644 index 00000000000..74924f5d65e --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java @@ -0,0 +1,364 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.circuits.DefaultVehicleCircuitsPlanner; +import ch.sbb.matsim.contrib.railsim.prototype.supply.infrastructure.DefaultInfrastructureRepository; +import ch.sbb.matsim.contrib.railsim.prototype.supply.rollingstock.DefaultRollingStockRepository; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.config.ConfigUtils; +import org.matsim.pt.transitSchedule.api.Departure; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitRouteStop; +import org.matsim.vehicles.VehicleType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Generate the supply for MATSim railsim extension + *

+ * Create transit schedules and networks for running simulations with the railsim extension, by adding individual transit lines. + * + * @author Merlin Unterfinger + * @author Ihab Kaddoura + */ +public class RailsimSupplyBuilder { + + private static final Logger log = LogManager.getLogger(RailsimSupplyBuilder.class); + private final Scenario scenario; + private final InfrastructureRepository infrastructureRepository; + private final RollingStockRepository rollingStockRepository; + private final VehicleCircuitsPlanner vehicleCircuitsPlanner; + private final RailsimSupplyConfigGroup railsimSupplyConfigGroup; + private final SupplyFactory supplyFactory; + private final Map transitLineInfos = new HashMap<>(); + private final Map stopInfos = new HashMap<>(); + private final Map>> sectionParts = new HashMap<>(); + + + /** + * @param scenario a scenario, set the CRS. + */ + public RailsimSupplyBuilder(Scenario scenario) { + this(scenario, new DefaultInfrastructureRepository(scenario), new DefaultRollingStockRepository(scenario), new DefaultVehicleCircuitsPlanner(scenario)); + } + + /** + * @param scenario a scenario, set the CRS. + * @param infrastructureRepository an infrastructure provider for rail capacities, speed limits and coordinates of depots and stops. + * @param rollingStockRepository a rolling stock provider, to create the vehicle types with attributes (maximum velocity, passenger capacity, acceleration and deceleration). + * @param vehicleCircuitsPlanner a vehicle circuits planner for planning the transit line vehicle allocations. + */ + public RailsimSupplyBuilder(Scenario scenario, InfrastructureRepository infrastructureRepository, RollingStockRepository rollingStockRepository, VehicleCircuitsPlanner vehicleCircuitsPlanner) { + this.scenario = scenario; + this.infrastructureRepository = infrastructureRepository; + this.rollingStockRepository = rollingStockRepository; + this.vehicleCircuitsPlanner = vehicleCircuitsPlanner; + railsimSupplyConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); + supplyFactory = new SupplyFactory(scenario); + } + + /** + * Adds a stop info to the supply. + *

+ * Note: StopInfos have only to be added once. Use the RouteStopInfo to define a route profile. + * + * @param id the unique id or name of the stop. + * @param x the x coordinates of the stop. + * @param y the y coordinates of the stop. + */ + public void addStop(String id, double x, double y) { + // get stop infos from infrastructure provider + var stopInfo = infrastructureRepository.getStop(id, x, y); + x = stopInfo.getCoord().getX(); + y = stopInfo.getCoord().getY(); + final double stopLinkLength = stopInfo.getStopLinkLength(); + // create the stop link: (t_IN)<#####>(t_OUT) + var tIn = supplyFactory.createNode(id + "_IN", new Coord(x - stopLinkLength, y)); + var tOut = supplyFactory.createNode(id + "_OUT", stopInfo.getCoord()); + var stopLink = supplyFactory.createLink(LinkType.STOP, tIn, tOut, stopLinkLength, stopInfo.getLinkAttributes()); + // put transit stop on the link + var stop = supplyFactory.createTransitStopFacility(id, stopLink); + // store stop information + stopInfo.setStopLink(stopLink); + stopInfo.setStop(stop); + this.stopInfos.put(stopInfo.getId(), stopInfo); + } + + /** + * Get a stop info from the supply builder. + * + * @param id the unique id or name of the stop. + * @return returns a StopInfo from the supply if it exists and null otherwise. + */ + StopInfo getStop(String id) { + StopInfo stopInfo = stopInfos.get(id); + if (stopInfo == null) { + throw new RuntimeException(String.format("Stop %s not existing, it has to be added first.", id)); + } + return stopInfo; + } + + /** + * Creates and adds a transit line information to the supply. + *

+ * The TransitLineInfo has then to be completed with further stops (addStop), passes (addPass) and departures (addDepartures). + * + * @param id the unique name or id of the transit line. + * @param vehicleTypeid the unique name or id of the vehicle type used in the transit line. + * @param firstStopId the unique name or id of the first stop. + * @param waitingTime the waiting time in seconds at the first stop. + * @return A transit line information object. + */ + public TransitLineInfo addTransitLine(String id, String vehicleTypeid, String firstStopId, double waitingTime) { + if (transitLineInfos.containsKey(id)) { + throw new RuntimeException("Transit line already existing for id " + id); + } + var firstStopInfo = getStop(firstStopId); + // get vehicle type info from provider + var vehicleTypeInfo = rollingStockRepository.getVehicleType(vehicleTypeid); + // create transit line and matsim object + var transitLineInfo = new TransitLineInfo(id, new RouteStopInfo(firstStopInfo, waitingTime), vehicleTypeInfo, supplyFactory.createTransitLine(id), this); + // store transit line info + transitLineInfos.put(id, transitLineInfo); + return transitLineInfo; + } + + /** + * Build the transit schedule + *

+ * Plans the vehicle circuits and creates the needed MATSim objects (transit routes, vehicle types and vehicles). + */ + public void build() { + // build transit lines and create route links + transitLineInfos.values().forEach(TransitLineInfo::build); + // plan vehicle circuits + var vehicleAllocations = vehicleCircuitsPlanner.plan(new ArrayList<>(transitLineInfos.values())); + // add transit lines with allocated vehicles to the schedule + for (var entry : vehicleAllocations.entrySet()) { + final var transitLineInfo = entry.getKey(); + final var vehicleTypeInfo = transitLineInfo.getVehicleTypeInfo(); + final var vehicleAllocationInfo = entry.getValue(); + // construct or get vehicle type + var vehicleType = supplyFactory.getOrCreateVehicleType(vehicleTypeInfo.getId(), vehicleTypeInfo.getLength(), vehicleTypeInfo.getMaxVelocity(), vehicleTypeInfo.getCapacity(), vehicleTypeInfo.getAttributes()); + // add routes for each type and direction + for (var routeType : RouteType.values()) { + // from origin + var departures = vehicleAllocationInfo.getDepartures(routeType, RouteDirection.FORWARD); + var vehicleIds = vehicleAllocationInfo.getVehicleIds(routeType, RouteDirection.FORWARD); + addTransitRouteToTransitLine(transitLineInfo, routeType, vehicleType, RouteDirection.FORWARD, departures, vehicleIds); + // from destination + departures = vehicleAllocationInfo.getDepartures(routeType, RouteDirection.REVERSE); + vehicleIds = vehicleAllocationInfo.getVehicleIds(routeType, RouteDirection.REVERSE); + addTransitRouteToTransitLine(transitLineInfo, routeType, vehicleType, RouteDirection.REVERSE, departures, vehicleIds); + } + } + } + + /** + * Add a depot to the stop + *

+ * Creates a TransitStop facility and inLink, depotLink and outLink for the depot. Then constructs a DepotInfo and sets it on the stopInfo. + * + *

+	 * 		(t_IN)------------------>(t_OUT)
+	 * 		^          stationLink         |
+	 * 		| outLink               inLink |
+	 * 		|           depotLink          v
+	 * 		(t_DPT_OUT)<----------(t_DPT_IN)
+	 * 
+ * + * @param stopInfo the stopInfo to add the depot to. + */ + private void addDepotToStop(StopInfo stopInfo) { + // request depot info from infrastructure provider + var depotInfo = infrastructureRepository.getDepot(stopInfo); + // create nodes + log.info("Adding depot to stop " + stopInfo.getId()); + var dIn = supplyFactory.createNode(depotInfo.getId() + "_DPT_IN", depotInfo.getCoord()); + var dOut = supplyFactory.createNode(depotInfo.getId() + "_DPT_OUT", new Coord(depotInfo.getCoord().getX() - depotInfo.getLength(), depotInfo.getCoord().getY())); + // create depot links + var depotInLink = supplyFactory.createLink(LinkType.DEPOT, stopInfo.getStopLink().getToNode(), dIn, depotInfo.getInLength(), depotInfo.getInLinkAttributes()); + var depotLink = supplyFactory.createLink(LinkType.DEPOT, dIn, dOut, depotInfo.getLength(), depotInfo.getDepotLinkAttributes()); + var depotOutLink = supplyFactory.createLink(LinkType.DEPOT, dOut, stopInfo.getStopLink().getFromNode(), depotInfo.getOutLength(), depotInfo.getOutLinkAttributes()); + // put transit depot on the link + var depot = supplyFactory.createTransitStopFacility(stopInfo.getId() + "_DPT", depotLink); + // add links and stop facility to depot + depotInfo.setDepot(depot); + depotInfo.setDepotIn(depotInLink); + depotInfo.setDepotLink(depotLink); + depotInfo.setDepotOut(depotOutLink); + // add depot to transit stop + stopInfo.setDepotInfo(depotInfo); + } + + /** + * Creates and adds routes for a transit route type to the transit line + *

+ * Note: The TransitLine object has to be created and added to the TransitLineInfo object beforehand. The HashMaps containing the departures and vehicles for each route type have to be created + * already. + * + * @param transitLineInfo transit line info container (which holds the transit line). + * @param routeType the type of the route to create (e.g. STATION_TO_DEPOT). + * @param vehicleType the type of the vehicle for the route. + * @param routeDirection the direction of the route. + * @param departures the HashMap which holds the departures for the route type. + * @param vehicleIds the HashMap which holds the vehicle ids for the route type. + */ + private void addTransitRouteToTransitLine(TransitLineInfo transitLineInfo, RouteType routeType, VehicleType vehicleType, RouteDirection routeDirection, LinkedList departures, LinkedList vehicleIds) { + if (departures == null || vehicleIds == null) { + log.info(String.format("Omitting route type %s for transit line %s (%s)...", routeType.toString(), transitLineInfo.getId(), routeDirection)); + return; + } + if (departures.size() != vehicleIds.size()) { + throw new RuntimeException(String.format("Failed adding transit route for %s (%s); departures and vehicles must have same size.", transitLineInfo.getId(), routeDirection)); + } + log.info(String.format("Adding routes (n=%d) for route type %s for transit line %s (%s)...", departures.size(), routeType.toString(), transitLineInfo.getId(), routeDirection)); + // get a copy of the transit line attribute lists (shallow, since only the order matters) + final LinkedList routeStopInfos = new LinkedList<>(transitLineInfo.getRouteStopInfos(routeDirection)); + final LinkedList travelTimes = new LinkedList<>(transitLineInfo.getTravelTimes(routeDirection)); + final LinkedList> routeLinks = new LinkedList<>(transitLineInfo.getRouteLinks(routeDirection)); + // build route type + final double depotTravelTime = railsimSupplyConfigGroup.getDepotTravelTime(); + if (routeType == RouteType.DEPOT_TO_STATION || routeType == RouteType.DEPOT_TO_DEPOT) { + addDepotAtOriginOfRoute(departures, routeStopInfos, travelTimes, routeLinks, depotTravelTime); + } + if (routeType == RouteType.STATION_TO_DEPOT || routeType == RouteType.DEPOT_TO_DEPOT) { + addDepotAtDestinationOfRoute(routeStopInfos, travelTimes, routeLinks, depotTravelTime); + } + // construct route + final List stops = new ArrayList<>(); + double cumulativeTravelTime = 0.; + // first stop + stops.add(supplyFactory.createTransitRouteStop(routeStopInfos.get(0).getTransitStop(), cumulativeTravelTime)); + // intermediate stops + for (int stopCounter = 1; stopCounter <= routeStopInfos.size() - 2; stopCounter++) { + // increase cumulative travel time until arrival + cumulativeTravelTime += travelTimes.get(stopCounter - 1); + // increase cumulative travel time by waiting time until departure + double departureTime = cumulativeTravelTime + routeStopInfos.get(stopCounter).getWaitingTime(); + TransitRouteStop transitStop = supplyFactory.createTransitRouteStop(routeStopInfos.get(stopCounter).getTransitStop(), cumulativeTravelTime, departureTime, true); + stops.add(transitStop); + // set cumulative travel time to departure time + cumulativeTravelTime = departureTime; + } + // final stop + cumulativeTravelTime += travelTimes.getLast(); + stops.add(supplyFactory.createTransitRouteStop(routeStopInfos.get(routeStopInfos.size() - 1).getTransitStop(), cumulativeTravelTime)); + // define route and add to transit line + final String routeId = String.format("%s_%s_%s", transitLineInfo.getId(), routeDirection.getAbbreviation(), routeType); + TransitRoute route = supplyFactory.createTransitRoute(transitLineInfo.getTransitLine(), routeId, routeLinks, stops); + // add departures and vehicles + Iterator iter = vehicleIds.iterator(); + int depCounter = 0; + for (Double departureTime : departures) { + Departure departure = supplyFactory.createDeparture(String.valueOf(depCounter), departureTime); + departure.setVehicleId(supplyFactory.getOrCreateVehicle(vehicleType, iter.next()).getId()); + route.addDeparture(departure); + depCounter++; + } + } + + /** + * Adds a depot at the origin of the route + *

+ * The departure time is decreased by the depot travel time. + * + * @param departures the departures when the route is started. + * @param routeStopInfos the ordered stops of the route. + * @param travelTimes the ordered travel times between the stops. + * @param routeLinks the ordered links between the stops of the complete route. + * @param depotTravelTime the travel time to reach the depot. + */ + private void addDepotAtOriginOfRoute(LinkedList departures, LinkedList routeStopInfos, LinkedList travelTimes, LinkedList> routeLinks, double depotTravelTime) { + final var origStop = routeStopInfos.getFirst().getStopInfo(); + final double waitingTime = routeStopInfos.getFirst().getWaitingTime(); + if (origStop.hasNoDepot()) { + addDepotToStop(origStop); + } + // decrease departures, since the first stop is set to the depot + for (int i = 0; i < departures.size(); i++) { + double departure = departures.removeFirst(); + departures.addLast(departure - (depotTravelTime + waitingTime)); + } + // add origin depot stop and links + var origDepo = origStop.getDepotInfo(); + routeStopInfos.addFirst(new RouteStopInfo(origDepo.getDepotLink(), origDepo.getDepot(), 0.)); + routeLinks.addAll(0, List.of(origDepo.getDepotLink().getId(), origDepo.getDepotOut().getId())); + travelTimes.addFirst(depotTravelTime); + } + + /** + * Adds a depot at the destination of the route + * + * @param routeStopInfos the ordered stops of the route. + * @param travelTimes the ordered travel times between the stops. + * @param routeLinks the ordered links between the stops of the complete route. + * @param depotTravelTime the travel time to reach the depot. + */ + private void addDepotAtDestinationOfRoute(LinkedList routeStopInfos, LinkedList travelTimes, LinkedList> routeLinks, double depotTravelTime) { + var destStop = routeStopInfos.getLast().getStopInfo(); + if (destStop.hasNoDepot()) { + addDepotToStop(destStop); + } + // add destination depot stop and links + var destDepo = destStop.getDepotInfo(); + routeStopInfos.add(new RouteStopInfo(destDepo.getDepotLink(), destDepo.getDepot(), 0.)); + routeLinks.addAll(List.of(destDepo.getDepotIn().getId(), destDepo.getDepotLink().getId())); + travelTimes.add(depotTravelTime); + } + + /** + * Connect two stops + *

+ * Connects two stops (or passes) of a transit line in the transit network . + * + * @param fromStop starting stop. + * @param toStop destination stop. + * @return A list of connecting links. + */ + List> connectStops(StopInfo fromStop, StopInfo toStop) { + String id = String.format("%s_%s", fromStop.getId(), toStop.getId()); + var sectionPart = sectionParts.get(id); + if (sectionPart != null) { + log.info(String.format("Section part already existing, skipping %s", id)); + return sectionPart; + } + // request section part information from infrastructure provider + var sectionPartInfo = infrastructureRepository.getSectionPart(fromStop, toStop); + // create link for each section until last + var nodePrefix = String.format("%s_%s_", sectionPartInfo.getFromStopId(), sectionPartInfo.getToStopId()); + var links = new ArrayList>(); + var sectionSegmentInfos = sectionPartInfo.getSectionSegmentInfos(); + var currentNode = fromStop.getStopLink().getToNode(); + var currentSegmentInfo = sectionSegmentInfos.get(0); + int count = 0; + for (int i = 0; i < sectionSegmentInfos.size() - 1; i++) { + var nextNode = supplyFactory.createNode(nodePrefix + count, sectionSegmentInfos.get(i + 1).getToCoord()); + var nextSegmentInfo = sectionSegmentInfos.get(i + 1); + links.add(supplyFactory.createLink(LinkType.ROUTE, currentNode, nextNode, currentSegmentInfo.getLength(), currentSegmentInfo.getLinkAttributes()).getId()); + currentNode = nextNode; + currentSegmentInfo = nextSegmentInfo; + count++; + } + // create and add last link + var lastStop = toStop.getStopLink().getFromNode(); + var lastSegmentInfo = sectionSegmentInfos.get(sectionSegmentInfos.size() - 1); + links.add(supplyFactory.createLink(LinkType.ROUTE, currentNode, lastStop, lastSegmentInfo.getLength(), lastSegmentInfo.getLinkAttributes()).getId()); + // store links of section part + sectionParts.put(id, links); + return links; + } + + public Scenario getScenario() { + return scenario; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java new file mode 100644 index 00000000000..f3ef1e8c482 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java @@ -0,0 +1,333 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.config.Config; +import org.matsim.core.config.ReflectiveConfigGroup; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Map; + +/** + * Railsim supply config group + *

+ * Config group to configure the supply generation for railsim. + * + * @author Merlin Unterfinger + */ +public class RailsimSupplyConfigGroup extends ReflectiveConfigGroup { + + private static final Logger log = LogManager.getLogger(RailsimConfigGroup.class); + public static final String GROUP_NAME = "railsimSupply"; + // stop + private static final String STOP_LINK_LENGTH = "stopLinkLength"; // 751. + private static final String STOP_TRAIN_CAPACITY = "stopTrainCapacity"; // 3 + private static final String STOP_SPEED_LIMIT = "stopSpeedLimit"; // 90. / 3.6; + // route + private static final String ROUTE_TRAIN_CAPACITY = "routeTrainCapacity"; // 1 + private static final String ROUTE_SPEED_LIMIT = "routeSpeedLimit"; // 100. / 3.6; + private static final String ROUTE_EUCLIDEAN_DISTANCE_FACTOR = "routeEuclideanDistanceFactor"; // 1 + // depot + private static final String DEPOT_TRAIN_CAPACITY = "depotTrainCapacity"; // 999 + private static final String DEPOT_IN_OUT_CAPACITY = "depotInOutCapacity"; // 1 + private static final String DEPOT_OFFSET = "depotOffset"; // 100. + private static final String DEPOT_TRAVEL_TIME = "depotTravelTime"; // 10 * 60 + private static final String DEPOT_SPEED_LIMIT = "depotSpeedLimit"; // 5./3.6 + private static final String DEPOT_LINK_LENGTH = "depotLinkLength"; // 751. + // vehicle + private static final String VEHICLE_PASSENGER_CAPACITY = "vehiclePassengerCapacity"; // 500 + private static final String VEHICLE_LENGTH = "vehicleLength"; // 200. + private static final String VEHICLE_MAX_VELOCITY = "vehicleMaxVelocity"; // 150 / 3.6 + private static final String VEHICLE_MAX_DECELERATION = "vehicleMaxDeceleration"; // 0.5 + private static final String VEHICLE_MAX_ACCELERATION = "vehicleMaxAcceleration"; // 0.5 + private static final String VEHICLE_TURNAROUND_TIME = "vehicleTurnaroundTime"; // 5. * 60 + // circuit + private static final String CIRCUIT_MAX_WAITING_TIME = "circuitMaxWaitingTime"; // 20 * 60 + private static final String CIRCUIT_PLANNING_APPROACH = "circuitPlanningApproach"; // has no effect yet + + public enum CircuitPlanningApproach {WITH, WITHOUT} + + /** + * Ctor + */ + public RailsimSupplyConfigGroup() { + super(GROUP_NAME); + } + + // stop + private double stopLinkLength = 751.; // meters + private int stopTrainCapacity = 3; // number of rails in station + private double stopSpeedLimit = 90. / 3.6; // meters per second + // route + private int routeTrainCapacity = 1; // number of rails + private double routeSpeedLimit = 120. / 3.6; // meters per second + private double routeEuclideanDistanceFactor = 1.; // factor + // depot + private int depotTrainCapacity = 999; // number of trains + private int depotInOutCapacity = 1; // number of rails + private double depotOffset = 100.; // meters + private double depotTravelTime = 10. * 60; // seconds + private double depotSpeedLimit = 5. / 3.6; // meters per second + private double depotLinkLength = 751.; // meters + // vehicle + private int vehiclePassengerCapacity = 500; + private double vehicleLength = 200.; + private double vehicleMaxVelocity = 150 / 3.6; // meters per second + private double vehicleMaxAcceleration = 0.5; // meters per seconds^2 + private double vehicleMaxDeceleration = 0.5; // meters per seconds^2 + private double vehicleTurnaroundTime = 5. * 60; // seconds + // circuit + private double circuitMaxWaitingTime = 20. * 60; // meters per second + private CircuitPlanningApproach circuitPlanningApproach = CircuitPlanningApproach.WITH; // options + + @Override + public Map getComments() { + Map comments = super.getComments(); + // stop + comments.put(STOP_LINK_LENGTH, "Length of the stop links in the stations, should be longer than the longest stopping train."); + comments.put(STOP_TRAIN_CAPACITY, "The number of parallel rails in the station sections."); + comments.put(STOP_SPEED_LIMIT, "The maximum speed allowed in the station in meters per second."); + // route + comments.put(ROUTE_TRAIN_CAPACITY, "The number of parallel rails of the route sections. "); + comments.put(ROUTE_SPEED_LIMIT, "The maximum speed allowed on the route sections in meters per second."); + comments.put(ROUTE_EUCLIDEAN_DISTANCE_FACTOR, "Factor to scale the euclidean distance between to stops for the route length definition."); + // depot + comments.put(DEPOT_TRAIN_CAPACITY, "The number of trains allowed in the depot, should be high enough to prevent grid locks."); + comments.put(DEPOT_IN_OUT_CAPACITY, "The number of rails that are connecting a depot to its stop."); + comments.put(DEPOT_OFFSET, "The vertical offset in meters, to place the depot to the stop."); + comments.put(DEPOT_TRAVEL_TIME, "The travel time to enter a depot from a stop or to reach the stop from the depot."); + comments.put(DEPOT_SPEED_LIMIT, "The speed allowed in the depot and on the connecting links."); + comments.put(DEPOT_LINK_LENGTH, "The length of the depot link, should be larger than the maximum vehicle length."); + // vehicle + comments.put(VEHICLE_PASSENGER_CAPACITY, "The number of passengers per vehicle."); + comments.put(VEHICLE_LENGTH, "The total length of the vehicle in meters."); + comments.put(VEHICLE_MAX_VELOCITY, "The maximum speed the vehicle is capable of driving."); + comments.put(VEHICLE_MAX_ACCELERATION, "The maximum braking acceleration of the vehicle."); + comments.put(VEHICLE_MAX_DECELERATION, "The maximum braking declaration of the vehicle."); + comments.put(VEHICLE_TURNAROUND_TIME, "The time it takes the vehicle to turnaround in station (changing the driver's cab)"); + // circuit + comments.put(CIRCUIT_MAX_WAITING_TIME, "The maximum waiting time of a vehicle on a stop link for the next circuit. If the next departure is after this waiting time, the vehicle is sent to the " + "depot."); + comments.put(CIRCUIT_PLANNING_APPROACH, "The circuits planning approach: " + Arrays.toString(CircuitPlanningApproach.values()) + ". " + CircuitPlanningApproach.WITH + " Plan vehicle circuits (default). " + CircuitPlanningApproach.WITHOUT + " Omit vehicle circuits and sent a new vehicle from depot to depot for each route (needs a high depot capacity)."); + return comments; + } + + @Override + protected void checkConsistency(Config config) { + log.info("Checking consistency in railsim preparation config group..."); + for (Field field : this.getClass().getDeclaredFields()) { + if (field.getType().isPrimitive() && Number.class.isAssignableFrom(field.getType())) { + try { + field.setAccessible(true); + Number value = (Number) field.get(this); + if (value.doubleValue() < 0) { + throw new RuntimeException(String.format("Negative value %f found for %s", value.doubleValue(), field.getName())); + } + } catch (IllegalAccessException e) { + log.error("Unable to check field {}", field); + } + } + } + } + + @StringGetter(STOP_LINK_LENGTH) + public double getStopLinkLength() { + return stopLinkLength; + } + + @StringSetter(STOP_LINK_LENGTH) + public void setStopLinkLength(double stopLinkLength) { + this.stopLinkLength = stopLinkLength; + } + + @StringGetter(STOP_TRAIN_CAPACITY) + public int getStopTrainCapacity() { + return stopTrainCapacity; + } + + @StringSetter(STOP_TRAIN_CAPACITY) + public void setStopTrainCapacity(int stopTrainCapacity) { + this.stopTrainCapacity = stopTrainCapacity; + } + + @StringGetter(STOP_SPEED_LIMIT) + public double getStopSpeedLimit() { + return stopSpeedLimit; + } + + @StringSetter(STOP_SPEED_LIMIT) + public void setStopSpeedLimit(double stopSpeedLimit) { + this.stopSpeedLimit = stopSpeedLimit; + } + + @StringGetter(ROUTE_TRAIN_CAPACITY) + public int getRouteTrainCapacity() { + return routeTrainCapacity; + } + + @StringSetter(ROUTE_TRAIN_CAPACITY) + public void setRouteTrainCapacity(int routeTrainCapacity) { + this.routeTrainCapacity = routeTrainCapacity; + } + + @StringGetter(ROUTE_SPEED_LIMIT) + public double getRouteSpeedLimit() { + return routeSpeedLimit; + } + + @StringSetter(ROUTE_SPEED_LIMIT) + public void setRouteSpeedLimit(double routeSpeedLimit) { + this.routeSpeedLimit = routeSpeedLimit; + } + + @StringGetter(ROUTE_EUCLIDEAN_DISTANCE_FACTOR) + public double getRouteEuclideanDistanceFactor() { + return routeEuclideanDistanceFactor; + } + + @StringSetter(ROUTE_EUCLIDEAN_DISTANCE_FACTOR) + public void setRouteEuclideanDistanceFactor(double routeEuclideanDistanceFactor) { + this.routeEuclideanDistanceFactor = routeEuclideanDistanceFactor; + } + + @StringGetter(DEPOT_TRAIN_CAPACITY) + public int getDepotTrainCapacity() { + return depotTrainCapacity; + } + + @StringSetter(DEPOT_TRAIN_CAPACITY) + public void setDepotTrainCapacity(int depotTrainCapacity) { + this.depotTrainCapacity = depotTrainCapacity; + } + + @StringGetter(DEPOT_IN_OUT_CAPACITY) + public int getDepotInOutCapacity() { + return depotInOutCapacity; + } + + @StringSetter(DEPOT_IN_OUT_CAPACITY) + public void setDepotInOutCapacity(int depotInOutCapacity) { + this.depotInOutCapacity = depotInOutCapacity; + } + + @StringGetter(DEPOT_OFFSET) + public double getDepotOffset() { + return depotOffset; + } + + @StringSetter(DEPOT_OFFSET) + public void setDepotOffset(double depotOffset) { + this.depotOffset = depotOffset; + } + + @StringGetter(DEPOT_TRAVEL_TIME) + public double getDepotTravelTime() { + return depotTravelTime; + } + + @StringSetter(DEPOT_TRAVEL_TIME) + public void setDepotTravelTime(double depotTravelTime) { + this.depotTravelTime = depotTravelTime; + } + + @StringGetter(DEPOT_SPEED_LIMIT) + public double getDepotSpeedLimit() { + return depotSpeedLimit; + } + + @StringSetter(DEPOT_SPEED_LIMIT) + public void setDepotSpeedLimit(double depotSpeedLimit) { + this.depotSpeedLimit = depotSpeedLimit; + } + + @StringGetter(DEPOT_LINK_LENGTH) + public double getDepotLinkLength() { + return depotLinkLength; + } + + @StringSetter(DEPOT_LINK_LENGTH) + public void setDepotLinkLength(double depotLinkLength) { + this.depotLinkLength = depotLinkLength; + } + + @StringGetter(VEHICLE_PASSENGER_CAPACITY) + public int getVehiclePassengerCapacity() { + return vehiclePassengerCapacity; + } + + @StringSetter(VEHICLE_PASSENGER_CAPACITY) + public void setVehiclePassengerCapacity(int vehiclePassengerCapacity) { + this.vehiclePassengerCapacity = vehiclePassengerCapacity; + } + + @StringGetter(VEHICLE_LENGTH) + public double getVehicleLength() { + return vehicleLength; + } + + @StringSetter(VEHICLE_LENGTH) + public void setVehicleLength(double vehicleLength) { + this.vehicleLength = vehicleLength; + } + + @StringGetter(VEHICLE_MAX_VELOCITY) + public double getVehicleMaxVelocity() { + return vehicleMaxVelocity; + } + + @StringSetter(VEHICLE_MAX_VELOCITY) + public void setVehicleMaxVelocity(double vehicleMaxVelocity) { + this.vehicleMaxVelocity = vehicleMaxVelocity; + } + + @StringGetter(VEHICLE_MAX_ACCELERATION) + public double getVehicleMaxAcceleration() { + return vehicleMaxAcceleration; + } + + @StringSetter(VEHICLE_MAX_ACCELERATION) + public void setVehicleMaxAcceleration(double vehicleMaxAcceleration) { + this.vehicleMaxAcceleration = vehicleMaxAcceleration; + } + + @StringGetter(VEHICLE_MAX_DECELERATION) + public double getVehicleMaxDeceleration() { + return vehicleMaxDeceleration; + } + + @StringSetter(VEHICLE_MAX_DECELERATION) + public void setVehicleMaxDeceleration(double vehicleMaxDeceleration) { + this.vehicleMaxDeceleration = vehicleMaxDeceleration; + } + + @StringGetter(VEHICLE_TURNAROUND_TIME) + public double getVehicleTurnaroundTime() { + return vehicleTurnaroundTime; + } + + @StringSetter(VEHICLE_TURNAROUND_TIME) + public void setVehicleTurnaroundTime(double vehicleTurnaroundTime) { + this.vehicleTurnaroundTime = vehicleTurnaroundTime; + } + + @StringGetter(CIRCUIT_MAX_WAITING_TIME) + public double getCircuitMaxWaitingTime() { + return circuitMaxWaitingTime; + } + + @StringSetter(CIRCUIT_MAX_WAITING_TIME) + public void setCircuitMaxWaitingTime(double circuitMaxWaitingTime) { + this.circuitMaxWaitingTime = circuitMaxWaitingTime; + } + + @StringGetter(CIRCUIT_PLANNING_APPROACH) + public CircuitPlanningApproach getCircuitPlanningApproach() { + return circuitPlanningApproach; + } + + @StringSetter(CIRCUIT_PLANNING_APPROACH) + public void setCircuitPlanningApproach(CircuitPlanningApproach circuitPlanningApproach) { + this.circuitPlanningApproach = circuitPlanningApproach; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java new file mode 100644 index 00000000000..49b45bc8eb3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java @@ -0,0 +1,20 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; + +/** + * Rolling stock repository + *

+ * Implement a repository to provide attributes (maximum velocity, passenger capacity, acceleration and deceleration) for the vehicle types. + * + * @author Merlin Unterfinger + */ +public interface RollingStockRepository { + VehicleTypeInfo getVehicleType(String vehicleTypeId); + + static void addRailsimAttributes(VehicleTypeInfo vehicleTypeInfo, double maxAcceleration, double maxDeceleration) { + var attributes = vehicleTypeInfo.getAttributes(); + attributes.put(RailsimUtils.VEHICLE_ATTRIBUTE_MAX_ACCELERATION, maxAcceleration); + attributes.put(RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION, maxDeceleration); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java new file mode 100644 index 00000000000..cf49925a115 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java @@ -0,0 +1,30 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +/** + * Route direction + *

+ * The two possible direction of the routes. + * + * @author Merlin Unterfinger + */ +public enum RouteDirection { + /** + * The route starts at the origin and ends at the destination. + */ + FORWARD("F"), + /** + * The route starts at the destination and ends at the origin. + */ + REVERSE("R"); + + private final String abbreviation; + + RouteDirection(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String getAbbreviation() { + return abbreviation; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java new file mode 100644 index 00000000000..c914895fc64 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java @@ -0,0 +1,69 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import org.matsim.api.core.v01.network.Link; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +/** + * Route stop information + * + * @author Merlin Unterfinger + */ +public class RouteStopInfo { + + private final StopInfo stopInfo; + private final Link link; + private final TransitStopFacility transitStop; + private final double waitingTime; + + /** + * Create a RouteStopInfo + *

+ * A route stop info is a stop on a previously defined StopInfo on a transit line. + * + * @param stopInfo the stop. + * @param waitingTime the time to waiting at stop, is 0. for pass. + */ + RouteStopInfo(StopInfo stopInfo, double waitingTime) { + this.stopInfo = stopInfo; + this.link = stopInfo.getStopLink(); + this.transitStop = stopInfo.getStop(); + this.waitingTime = waitingTime; + } + + /** + * @param link the link where the depot sits on. + * @param transitStopFacility the depot transit stop facility. + * @param waitingTime time to wait in depot. + */ + RouteStopInfo(Link link, TransitStopFacility transitStopFacility, double waitingTime) { + this.stopInfo = null; + this.link = link; + this.transitStop = transitStopFacility; + this.waitingTime = waitingTime; + } + + /** + * Checks if the route stop info is a stopping pass. + * + * @return true if stopping, else false (non-stopping pass). + */ + public boolean isStoppingPass() { + return waitingTime > 0.; + } + + public Link getLink() { + return link; + } + + public TransitStopFacility getTransitStop() { + return transitStop; + } + + public double getWaitingTime() { + return waitingTime; + } + + public StopInfo getStopInfo() { + return stopInfo; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java new file mode 100644 index 00000000000..b17941991f4 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java @@ -0,0 +1,35 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +/** + * Route type + *

+ * A transit line can have those route types in two directions (F: forward, R: Reverse). + * + * @author Merlin Unterfinger + */ +public enum RouteType { + /** + * New vehicle is created of vehicle type and route start is in the depot. + *

+ * The departure time is decreased by the travel time from the depot to the station plus the waiting time of the origin station. + */ + DEPOT_TO_STATION, + /** + * Existing vehicle at the origin station is used. + *

+ * The departure time and the route profile is not changed. + */ + STATION_TO_STATION, + /** + * Existing vehicle at the origin station is used, but the destination transit stop facility is the depot of the destination station. + *

+ * The departure time is not changed, but the route profile is extended into the depot. + */ + STATION_TO_DEPOT, + /** + * New vehicle is created of vehicle type and route start and end is in the depot. + *

+ * The departure time is decreased as in DEPOT_TO_STATION, and the route is extended from the depot at the origin to the depot at the destination. + */ + DEPOT_TO_DEPOT +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java new file mode 100644 index 00000000000..51df03e5443 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java @@ -0,0 +1,151 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import org.matsim.api.core.v01.network.NetworkWriter; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.pt.transitSchedule.api.TransitScheduleWriter; +import org.matsim.vehicles.MatsimVehicleWriter; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Example run of RailsimSupplyBuilder + *

+ * Create a small example with different lines: slow, express, internation and cargo + * + * @author Merlin Unterfinger + */ +public final class RunRailsimSupplyBuilder { + + public static void main(String[] args) { + final String outputDir = "contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/"; + final double waitingTime = 3 * 60.; + createOutputDirectory(outputDir); + + // setup supply builder + var config = ConfigUtils.createConfig(); + config.global().setCoordinateSystem("CH1903plus_LV95"); + var scenario = ScenarioUtils.loadScenario(config); + var supply = new RailsimSupplyBuilder(scenario); + + // first add the stop information + supply.addStop("lyon_part_dieu", 2399392., 1070947.); + supply.addStop("geneve_la_praille", 2498624., 1115476.); + supply.addStop("geneve", 2499965., 1119074.); + supply.addStop("versoix", 2501905., 1126194.); + supply.addStop("coppet", 2503642., 1130305.); + supply.addStop("nyon", 2507480., 1137714.); + supply.addStop("gland", 2510108., 1141628.); + supply.addStop("rolle", 2515307., 1146292.); + supply.addStop("allaman", 2520200., 1147698.); + supply.addStop("morges", 2527534., 1151505.); + supply.addStop("renens", 2534852., 1154073.); + supply.addStop("lausanne", 2538101., 1151907.); + supply.addStop("puidoux", 2548503., 1148595); + supply.addStop("vevey", 2554287., 1145945.); + supply.addStop("fribourg", 2581093., 1182309); + supply.addStop("bern", 2598563, 1200033.); + supply.addStop("bern_express", 2599563, 1200333.); // some minor shifts of the coordinates + supply.addStop("bern_cargo", 2599563, 1199833.); // some minor shifts of the coordinates + + // add transit lines + // slow line + var slowLine = supply.addTransitLine("slow", "slow", "geneve", waitingTime); + slowLine.addStop("versoix", 5 * 60., waitingTime); + slowLine.addStop("coppet", 4 * 60., waitingTime); + slowLine.addStop("nyon", 5 * 60., waitingTime); + slowLine.addStop("gland", 3 * 60., waitingTime); + slowLine.addStop("rolle", 4 * 60., waitingTime); + slowLine.addStop("allaman", 3 * 60., waitingTime); + slowLine.addStop("morges", 5 * 60., waitingTime); + slowLine.addStop("renens", 6 * 60., waitingTime); + slowLine.addStop("lausanne", 6 * 60., waitingTime); + slowLine.addStop("puidoux", 10 * 60., waitingTime); + slowLine.addStop("vevey", 5 * 60., waitingTime); + // express line + var expressLine = supply.addTransitLine("express", "express", "geneve", waitingTime); + expressLine.addPass("versoix"); + expressLine.addPass("coppet"); + expressLine.addPass("nyon"); + expressLine.addPass("gland"); + expressLine.addPass("rolle"); + expressLine.addPass("allaman"); + expressLine.addPass("morges"); + expressLine.addPass("renens"); + expressLine.addStop("lausanne", 45 * 60., waitingTime); + expressLine.addStop("fribourg", 41 * 60., waitingTime); + expressLine.addPass("bern"); + expressLine.addStop("bern_express", 24 * 60., waitingTime); + // international line + var internationalLine = supply.addTransitLine("international", "international", "lyon_part_dieu", waitingTime); + internationalLine.addStop("geneve", 128 * 60., waitingTime); + internationalLine.addPass("versoix"); + internationalLine.addPass("coppet"); + internationalLine.addPass("nyon"); + internationalLine.addPass("gland"); + internationalLine.addPass("rolle"); + internationalLine.addPass("allaman"); + internationalLine.addPass("morges"); + internationalLine.addPass("renens"); + internationalLine.addStop("lausanne", 45 * 60., waitingTime); + internationalLine.addStop("fribourg", 41 * 60., waitingTime); + internationalLine.addPass("bern"); + internationalLine.addStop("bern_express", 24 * 60., waitingTime); + // cargo line + var cargoLine = supply.addTransitLine("cargo", "cargo", "geneve_la_praille", waitingTime); + cargoLine.addPass("geneve"); + cargoLine.addPass("versoix"); + cargoLine.addPass("coppet"); + cargoLine.addPass("nyon"); + cargoLine.addPass("gland"); + cargoLine.addPass("rolle"); + cargoLine.addPass("allaman"); + cargoLine.addPass("morges"); + cargoLine.addPass("renens"); + cargoLine.addPass("lausanne"); + cargoLine.addPass("fribourg"); + cargoLine.addPass("bern"); + cargoLine.addStop("bern_cargo", 193 * 60., waitingTime); + + // add departures: slow line + addDepartureTimes(slowLine, 0. * 3600., 3600., 900.); + // add departures: express line + addDepartureTimes(expressLine, 0.5 * 3600., 7200., 3600.); + // add departures: international line + addDepartureTimes(internationalLine, 0.25 * 3600., 3600 * 3., 7200.); + // add departures: cargo line + addDepartureTimes(cargoLine, 0.75 * 3600., 3600 * 3., 7200.); + + // build schedule + supply.build(); + + // export + new NetworkWriter(scenario.getNetwork()).write(outputDir + "trainNetwork.xml.gz"); + new TransitScheduleWriter(scenario.getTransitSchedule()).writeFile(outputDir + "transitSchedule.xml.gz"); + new MatsimVehicleWriter(scenario.getTransitVehicles()).writeFile(outputDir + "transitVehicles.xml.gz"); + } + + private static void addDepartureTimes(TransitLineInfo slowLine, double initialDeparture, double nvzHeadway, double hvzHeadway) { + for (double timeOfDay = initialDeparture; timeOfDay <= 24 * 3600.; ) { + slowLine.addDeparture(RouteDirection.FORWARD, timeOfDay); + slowLine.addDeparture(RouteDirection.REVERSE, timeOfDay); + if (timeOfDay < 6 * 3600. || timeOfDay > 19 * 3600.) { + timeOfDay = timeOfDay + nvzHeadway; + } else { + timeOfDay = timeOfDay + hvzHeadway; + } + } + } + + private static void createOutputDirectory(String outputDir) { + try { + Files.createDirectories(Paths.get(outputDir)); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + + diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java new file mode 100644 index 00000000000..efd6759253f --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java @@ -0,0 +1,39 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import java.util.ArrayList; +import java.util.List; + +/** + * Section part information + *

+ * A section part is a route between two stops, which is can be shared between multiple transit lines. Which means the rail capacity is also shared. + * + * @author Merlin Unterfinger + */ +public class SectionPartInfo { + private final String fromStopId; + private final String toStopId; + private final List sectionSegmentInfos = new ArrayList<>(); + + public SectionPartInfo(String fromStopId, String toStopId) { + this.fromStopId = fromStopId; + this.toStopId = toStopId; + } + + public void addSegment(SectionSegmentInfo sectionSegmentInfo) { + sectionSegmentInfos.add(sectionSegmentInfo); + } + + public String getFromStopId() { + return fromStopId; + } + + public String getToStopId() { + return toStopId; + } + + public List getSectionSegmentInfos() { + return sectionSegmentInfos; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java new file mode 100644 index 00000000000..f7d7017249c --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java @@ -0,0 +1,46 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import org.matsim.api.core.v01.Coord; + +import java.util.HashMap; +import java.util.Map; + +/** + * Section segment information + *

+ * A section segment is a segment of the rail network with constant attributes (number of rails, speed limit, grade). + * + * @author Merlin Unterfinger + */ +public class SectionSegmentInfo { + private final Coord fromCoord; + private final Coord toCoord; + private final double length; + private final Map linkAttributes = new HashMap<>(); + + public SectionSegmentInfo(Coord fromCoord, Coord toCoord, double length) { + this.fromCoord = fromCoord; + this.toCoord = toCoord; + this.length = length; + } + + public void addLinkAttribute(String key, Object value) { + linkAttributes.put(key, value); + } + + public Coord getFromCoord() { + return fromCoord; + } + + public Coord getToCoord() { + return toCoord; + } + + public double getLength() { + return length; + } + + public Map getLinkAttributes() { + return linkAttributes; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java new file mode 100644 index 00000000000..d51e766cda7 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java @@ -0,0 +1,87 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.network.Link; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +import java.util.HashMap; +import java.util.Map; + +/** + * Stop information + *

+ * Can also be passes for some transit lines. + * + * @author Merlin Unterfinger + */ +public class StopInfo { + + private final String id; + private final Coord coord; + private final double stopLinkLength; + private final Map linkAttributes = new HashMap<>(); + private TransitStopFacility stop; + private Link stopLink; + private DepotInfo depotInfo; + + /** + * @param id the unique id or name of the stop. + * @param coord the coordinates of the stop, + * @param stopLinkLength the maximum length of the stop link in the station. + */ + public StopInfo(String id, Coord coord, double stopLinkLength) { + this.id = id; + this.coord = coord; + this.stopLinkLength = stopLinkLength; + this.depotInfo = null; + } + + /** + * Checks if the stopInfo has no associated depot. + * + * @return true if no depot is connected, false otherwise. + */ + boolean hasNoDepot() { + return depotInfo == null; + } + + public String getId() { + return id; + } + + public Coord getCoord() { + return coord; + } + + public double getStopLinkLength() { + return stopLinkLength; + } + + public Map getLinkAttributes() { + return linkAttributes; + } + + public TransitStopFacility getStop() { + return stop; + } + + public void setStop(TransitStopFacility stop) { + this.stop = stop; + } + + public Link getStopLink() { + return stopLink; + } + + public void setStopLink(Link stopLink) { + this.stopLink = stopLink; + } + + public DepotInfo getDepotInfo() { + return depotInfo; + } + + public void setDepotInfo(DepotInfo depotInfo) { + this.depotInfo = depotInfo; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java new file mode 100644 index 00000000000..db51c5da054 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java @@ -0,0 +1,220 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +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.core.population.routes.RouteUtils; +import org.matsim.pt.transitSchedule.api.Departure; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitRouteStop; +import org.matsim.pt.transitSchedule.api.TransitSchedule; +import org.matsim.pt.transitSchedule.api.TransitScheduleFactory; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.matsim.utils.objectattributes.attributable.Attributable; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; +import org.matsim.vehicles.VehicleUtils; +import org.matsim.vehicles.Vehicles; +import org.matsim.vehicles.VehiclesFactory; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * Supply factory + *

+ * Create MATSim instances and add them to a transit schedule and network. + * + * @author Merlin Unterfinger + */ +class SupplyFactory { + private static final Logger log = LogManager.getLogger(SupplyFactory.class); + private static final double DEFAULT_LINK_FREESPEED = 999.; + private static final double DEFAULT_LINK_CAPACITY = 999.; + private static final double DEFAULT_LINK_LANES = 1.; + private static final String DEFAULT_LINK_MODE = "rail"; + private static final double DEFAULT_VEHICLE_ACCESS_TIME = 1.; + private static final double DEFAULT_VEHICLE_EGRESS_TIME = 1.; + private static final String ID_FORMAT_DEPARTURE = "%s"; + private static final String ID_FORMAT_TRANSIT_LINE = "%s_line"; + private static final String ID_FORMAT_TRANSIT_ROUTE = "%s_route"; + private static final String ID_FORMAT_TRANSIT_STOP = "%s"; + private static final String ID_FORMAT_VEHICLE = "%s"; + private static final String ID_FORMAT_VEHICLE_TYPE = "train_%s"; + private static final String ID_FORMAT_LINK = "%s_%s-%s"; + private static final String ID_FORMAT_NODE = "%s"; + private final TransitSchedule schedule; + private final Vehicles vehicles; + private final Network network; + private final TransitScheduleFactory sf; + private final VehiclesFactory vf; + private final NetworkFactory nf; + + + SupplyFactory(Scenario scenario) { + schedule = scenario.getTransitSchedule(); + vehicles = scenario.getTransitVehicles(); + network = scenario.getNetwork(); + sf = schedule.getFactory(); + vf = vehicles.getFactory(); + nf = network.getFactory(); + } + + Departure createDeparture(String id, double time) { + var departureId = Id.create(String.format(ID_FORMAT_DEPARTURE, id), Departure.class); + log.debug("Creating Departure {}", departureId); + return sf.createDeparture(departureId, time); + } + + TransitLine createTransitLine(String id) { + var lineId = Id.create(String.format(ID_FORMAT_TRANSIT_LINE, id), TransitLine.class); + var transitLine = schedule.getTransitLines().get(lineId); + if (transitLine != null) { + log.warn("TransitLine {} is already existing", lineId); + return transitLine; + } + log.debug("Creating TransitLine {}", lineId); + transitLine = sf.createTransitLine(lineId); + schedule.addTransitLine(transitLine); + return transitLine; + } + + TransitRoute createTransitRoute(TransitLine transitLine, String id, List> routeLinks, List stops) { + var routeId = Id.create(String.format(ID_FORMAT_TRANSIT_ROUTE, id), TransitRoute.class); + var networkRoute = RouteUtils.createLinkNetworkRouteImpl(routeLinks.get(0), routeLinks.subList(1, routeLinks.size() - 1), routeLinks.get(routeLinks.size() - 1)); + var transitRoute = transitLine.getRoutes().get(routeId); + if (transitRoute != null) { + log.warn("TransitRoute {} is already existing on TransitLine {}", routeId, transitLine.getId()); + return transitRoute; + } + log.debug("Creating TransitRoute {} and adding to TransitLine {}", routeId, transitLine.getId()); + transitRoute = sf.createTransitRoute(routeId, networkRoute, stops, DEFAULT_LINK_MODE); + transitLine.addRoute(transitRoute); + return transitRoute; + } + + TransitRouteStop createTransitRouteStop(TransitStopFacility transitStopFacility, double cumulativeTravelTime, double departureTime, boolean awaitDeparture) { + log.debug("Creating TransitRouteStop at TransitStopFacility {}", transitStopFacility.getId()); + var transitRouteStop = sf.createTransitRouteStop(transitStopFacility, cumulativeTravelTime, departureTime); + transitRouteStop.setAwaitDepartureTime(awaitDeparture); + return transitRouteStop; + } + + TransitRouteStop createTransitRouteStop(TransitStopFacility transitStopFacility, double cumulativeTravelTime) { + log.debug("Creating TransitRouteStop at TransitStopFacility {} (using builder)", transitStopFacility.getId()); + return sf.createTransitRouteStopBuilder(transitStopFacility).departureOffset(cumulativeTravelTime).build(); + } + + TransitStopFacility createTransitStopFacility(String id, Link link) { + var stopId = Id.create(String.format(ID_FORMAT_TRANSIT_STOP, id), TransitStopFacility.class); + var transitStopFacility = schedule.getFacilities().get(stopId); + if (transitStopFacility != null) { + log.warn("TransitStopFacility {} is already existing", stopId); + return transitStopFacility; + } + log.debug("Creating TransitStopFacility {}", stopId); + transitStopFacility = sf.createTransitStopFacility(stopId, link.getToNode().getCoord(), false); + transitStopFacility.setLinkId(link.getId()); + schedule.addStopFacility(transitStopFacility); + return transitStopFacility; + } + + Node createNode(String id, Coord coord) { + var nodeId = Id.create(String.format(ID_FORMAT_NODE, id), Node.class); + var node = network.getNodes().get(nodeId); + if (node != null) { + log.warn("Node {} is already existing", nodeId); + return node; + } + log.debug("Creating Node {}", nodeId); + node = nf.createNode(nodeId, coord); + network.addNode(node); + return node; + } + + Link createLink(LinkType linkType, Node fromNode, Node toNode, double length, Map attributes) { + var linkId = Id.create(String.format(ID_FORMAT_LINK, linkType.getAbbreviation(), fromNode.getId().toString(), toNode.getId().toString()), Link.class); + var link = network.getLinks().get(linkId); + if (link != null) { + log.warn("Link {} is already existing", linkId); + return link; + } + log.debug("Creating Link {}", linkId); + link = nf.createLink(linkId, fromNode, toNode); + link.setAllowedModes(new HashSet<>(List.of(DEFAULT_LINK_MODE))); + link.setLength(length); + link.setFreespeed(DEFAULT_LINK_FREESPEED); + link.setCapacity(DEFAULT_LINK_CAPACITY); + link.setNumberOfLanes(DEFAULT_LINK_LANES); + putAttributes(link, attributes); + network.addLink(link); + return link; + } + + /** + * Creates a new vehicle type or returns if already existing. + * + * @param id vehicle type id. + * @param length the length of the complete vehicle in meters. + * @param maxVelocity maximum velocity the vehicle type is allowed to drive (m/s). + * @param capacity the passenger capacity of the vehicle type. + * @param attributes a map holding further attributes (acceleration, deceleration) + * @return the corresponding vehicle type. + */ + VehicleType getOrCreateVehicleType(String id, double length, double maxVelocity, int capacity, Map attributes) { + Id vehicleTypeId = Id.create(String.format(ID_FORMAT_VEHICLE_TYPE, id), VehicleType.class); + VehicleType vehicleType = vehicles.getVehicleTypes().get(vehicleTypeId); + if (vehicleType != null) { + log.debug("VehicleType {} is already existing", vehicleTypeId); + return vehicleType; + } + log.debug("Creating VehicleType {}", vehicleTypeId); + vehicleType = vf.createVehicleType(vehicleTypeId); + vehicleType.getCapacity().setSeats(capacity); + vehicleType.setMaximumVelocity(maxVelocity); + VehicleUtils.setDoorOperationMode(vehicleType, VehicleType.DoorOperationMode.parallel); + VehicleUtils.setAccessTime(vehicleType, DEFAULT_VEHICLE_ACCESS_TIME); + VehicleUtils.setEgressTime(vehicleType, DEFAULT_VEHICLE_EGRESS_TIME); + vehicleType.setLength(length); + putAttributes(vehicleType, attributes); + vehicles.addVehicleType(vehicleType); + return vehicleType; + } + + /** + * Creates a new vehicle for a given vehicle type + *

+ * The vehicle id is set to the vehicle type name and the count of the total amount of existing vehicles of this type. + *

+ * Note: The vehicle type has to be created beforehand using getOrCreateVehicleType. + * + * @param vehicleType The type of the vehicle to create. + * @return The created vehicle. + */ + Vehicle getOrCreateVehicle(VehicleType vehicleType, String id) { + Id vehicleId = Id.create(String.format(ID_FORMAT_VEHICLE, id), Vehicle.class); + Vehicle vehicle = vehicles.getVehicles().get(vehicleId); + if (vehicle != null) { + log.debug("Vehicle {} is already existing", vehicleId); + return vehicle; + } + log.debug("Creating Vehicle {}", vehicleId); + vehicle = vf.createVehicle(vehicleId, vehicleType); + vehicles.addVehicle(vehicle); + return vehicle; + } + + private static void putAttributes(Attributable object, Map attributes) { + for (var entry : attributes.entrySet()) { + object.getAttributes().putAttribute(entry.getKey(), entry.getValue()); + } + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java new file mode 100644 index 00000000000..083d00cda75 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java @@ -0,0 +1,214 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.utils.misc.Time; +import org.matsim.pt.transitSchedule.api.TransitLine; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Transit line information. + *

+ * Create an instance of this class to add transit lines and routes in both directions to the transit schedule using addTransitLineWithVehicleCircuits. + * + * @author Merlin Unterfinger + */ +public class TransitLineInfo { + + private static final Logger log = LogManager.getLogger(TransitLineInfo.class); + + private static final double NO_STOP_TIME = 0.; + private final RailsimSupplyBuilder supplyBuilder; + // line + private final String id; + private final VehicleTypeInfo vehicleTypeInfo; + private final TransitLine transitLine; + // route profile + private final EnumMap> routeStopInfos = new EnumMap<>(RouteDirection.class); + private final EnumMap>> routeLinks = new EnumMap<>(RouteDirection.class); + private final EnumMap> travelTimes = new EnumMap<>(RouteDirection.class); + // route departure + private final EnumMap> departures = new EnumMap<>(RouteDirection.class); + private boolean built; + + /** + * Constructs a new transit line object and makes shallow copies of the input lists to prevent from outside manipulations. Reverses the route stop infos and travel times. + * + * @param id unique name or id of the transit line. + * @param firstRouteStop unique name or id of the first stop in the route. + * @param vehicleTypeInfo the vehicle type information. + * @param supplyBuilder the supply builder the transit line resides on. + */ + TransitLineInfo(String id, RouteStopInfo firstRouteStop, VehicleTypeInfo vehicleTypeInfo, TransitLine transitLine, RailsimSupplyBuilder supplyBuilder) { + this.id = id; + this.supplyBuilder = supplyBuilder; + this.vehicleTypeInfo = vehicleTypeInfo; + this.transitLine = transitLine; + routeStopInfos.put(RouteDirection.FORWARD, new ArrayList<>()); + travelTimes.put(RouteDirection.FORWARD, new ArrayList<>()); + departures.put(RouteDirection.FORWARD, new ArrayList<>()); + departures.put(RouteDirection.REVERSE, new ArrayList<>()); + built = false; + addFirstStop(firstRouteStop); + } + + public void addStop(String stopId, double travelTime, double waitingTime) { + if (built) { + throw new RuntimeException(String.format("Cannot add stop %s to already built TransitLineInfo %s", stopId, id)); + } + StopInfo stopInfo = supplyBuilder.getStop(stopId); + this.routeStopInfos.get(RouteDirection.FORWARD).add(new RouteStopInfo(stopInfo, waitingTime)); + this.travelTimes.get(RouteDirection.FORWARD).add(travelTime); + } + + public void addPass(String stopId) { + if (built) { + throw new RuntimeException(String.format("Cannot add pass %s to already built TransitLineInfo %s", stopId, id)); + } + StopInfo stopInfo = supplyBuilder.getStop(stopId); + this.routeStopInfos.get(RouteDirection.FORWARD).add(new RouteStopInfo(stopInfo, NO_STOP_TIME)); + } + + public void addDeparture(RouteDirection routeDirection, double departureTime) { + if (built) { + throw new RuntimeException(String.format("Cannot add departure %s to already built TransitLineInfo %s", Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), id)); + } + this.departures.get(routeDirection).add(departureTime); + } + + /** + * Build the transit line + *

+ * After calling this method no further stops, passes or departures can be added to the transit line. + */ + void build() { + if (departures.get(RouteDirection.FORWARD).isEmpty() && departures.get(RouteDirection.REVERSE).isEmpty()) + throw new RuntimeException("Transit line needs at least one departure"); + if (routeStopInfos.get(RouteDirection.FORWARD).size() < 2) + throw new RuntimeException("Transit line needs at least one additional stop to origin stop"); + log.info("Building TransitLineInfo {}", id); + sortDepartures(); + initializeReverseDirection(); + createLinksAndConnectStops(); + logEntry(); + built = true; + } + + private void addFirstStop(RouteStopInfo firstRouteStop) { + routeStopInfos.get(RouteDirection.FORWARD).add(firstRouteStop); + } + + private void sortDepartures() { + Collections.sort(departures.get(RouteDirection.FORWARD)); + Collections.sort(departures.get(RouteDirection.REVERSE)); + } + + private void initializeReverseDirection() { + this.routeStopInfos.put(RouteDirection.REVERSE, new ArrayList<>(this.routeStopInfos.get(RouteDirection.FORWARD))); + this.travelTimes.put(RouteDirection.REVERSE, new ArrayList<>(this.travelTimes.get(RouteDirection.FORWARD))); + Collections.reverse(this.routeStopInfos.get(RouteDirection.REVERSE)); + Collections.reverse(this.travelTimes.get(RouteDirection.REVERSE)); + } + + /** + * Create transit line links in both directions + */ + private void createLinksAndConnectStops() { + final var routeLinks = new ArrayList>(); + final var routeLinksReverse = new ArrayList>(); + final var routeStopInfos = getRouteStopInfos(RouteDirection.FORWARD); + // F: iterate over route stop infos and create links + StopInfo nextStop = null; + for (int i = 0; i < routeStopInfos.size() - 1; i++) { + var currentStop = routeStopInfos.get(i).getStopInfo(); + nextStop = routeStopInfos.get(i + 1).getStopInfo(); + // add terminal link + routeLinks.add(currentStop.getStopLink().getId()); + // add links to next stop + routeLinks.addAll(supplyBuilder.connectStops(currentStop, nextStop)); + } + assert nextStop != null; + routeLinks.add(nextStop.getStopLink().getId()); + // R: iterate over route stop infos and create links + nextStop = null; + for (int i = routeStopInfos.size() - 1; i > 0; i--) { + var currentStop = routeStopInfos.get(i).getStopInfo(); + nextStop = routeStopInfos.get(i - 1).getStopInfo(); + // add terminal link + routeLinksReverse.add(currentStop.getStopLink().getId()); + // add links to next stop + routeLinksReverse.addAll(supplyBuilder.connectStops(currentStop, nextStop)); + } + assert nextStop != null; + routeLinksReverse.add(nextStop.getStopLink().getId()); + // set route links on transit line info + setRouteLinks(RouteDirection.FORWARD, routeLinks); + setRouteLinks(RouteDirection.REVERSE, routeLinksReverse); + // remove route stop infos with a waiting time of 0 minutes, since they are no stops, but needed for route link creation + setRouteStopInfos(RouteDirection.FORWARD, getRouteStopInfos(RouteDirection.FORWARD).stream().filter(RouteStopInfo::isStoppingPass).collect(Collectors.toList())); + setRouteStopInfos(RouteDirection.REVERSE, getRouteStopInfos(RouteDirection.REVERSE).stream().filter(RouteStopInfo::isStoppingPass).collect(Collectors.toList())); + } + + public RouteStopInfo getOrigin(RouteDirection routeDirection) { + return routeStopInfos.get(routeDirection).get(0); + } + + public RouteStopInfo getDestination(RouteDirection routeDirection) { + var directedRouteStopInfos = routeStopInfos.get(routeDirection); + return directedRouteStopInfos.get(directedRouteStopInfos.size() - 1); + } + + public List getRouteStopInfos(RouteDirection routeDirection) { + return routeStopInfos.get(routeDirection); + } + + void setRouteStopInfos(RouteDirection routeDirection, List routeStopInfos) { + this.routeStopInfos.put(routeDirection, routeStopInfos); + } + + List> getRouteLinks(RouteDirection routeDirection) { + return routeLinks.get(routeDirection); + } + + void setRouteLinks(RouteDirection routeDirection, List> routeLinks) { + this.routeLinks.put(routeDirection, routeLinks); + } + + public List getTravelTimes(RouteDirection routeDirection) { + return travelTimes.get(routeDirection); + } + + public List getDepartures(RouteDirection routeDirection) { + return departures.get(routeDirection); + } + + private void logEntry() { + var sb = new StringBuilder("Transit line log entry of ").append(id).append("\n"); + // departures + sb.append("Departures:\n").append("- FORWARD: ").append(this.departures.get(RouteDirection.FORWARD).stream().map(d -> Time.writeTime(d, Time.TIMEFORMAT_HHMMSS)).collect(Collectors.joining(", "))).append("\n- REVERSE: ").append(this.departures.get(RouteDirection.REVERSE).stream().map(d -> Time.writeTime(d, Time.TIMEFORMAT_HHMMSS)).collect(Collectors.joining(", "))); + // route stops + sb.append("\nRoute stops and passes:\n").append(this.routeStopInfos.get(RouteDirection.FORWARD).stream().map(r -> String.format("%s (%s)", r.getStopInfo().getId(), Time.writeTime(r.getWaitingTime(), Time.TIMEFORMAT_HHMMSS))).collect(Collectors.joining(", "))); + // route stops + sb.append("\nTravel times:\n").append(this.travelTimes.get(RouteDirection.FORWARD).stream().map(d -> Time.writeTime(d, Time.TIMEFORMAT_HHMMSS)).collect(Collectors.joining(", "))); + log.info(sb); + } + + public String getId() { + return id; + } + + TransitLine getTransitLine() { + return transitLine; + } + + public VehicleTypeInfo getVehicleTypeInfo() { + return vehicleTypeInfo; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java new file mode 100644 index 00000000000..87e0afdbadb --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java @@ -0,0 +1,18 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import java.util.LinkedList; + +/** + * Vehicle allocation information + *

+ * Denotes which vehicles are used by a departure of a transit line. + * + * @author Merlin Unterfinger + */ +public interface VehicleAllocationInfo { + + LinkedList getDepartures(RouteType routeType, RouteDirection routeDirection); + + LinkedList getVehicleIds(RouteType routeType, RouteDirection routeDirection); + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java new file mode 100644 index 00000000000..f7f27ebabc8 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java @@ -0,0 +1,15 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import java.util.List; +import java.util.Map; + +/** + * Vehicle circuits planner + *

+ * Takes a list of (built) transit line information objects, plan circuits and allocates a vehicle to each departure. + * + * @author Merlin Unterfinger + */ +public interface VehicleCircuitsPlanner { + Map plan(List transitLineInfos); +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java new file mode 100644 index 00000000000..89165066404 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java @@ -0,0 +1,61 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import java.util.HashMap; +import java.util.Map; + +/** + * Vehicle type information + *

+ * Use this class in combination with TransitLineInfo to provide a vehicle type for a transit line. + * + * @author Merlin Unterfinger + */ +public class VehicleTypeInfo { + + private final String id; + private final int capacity; + private final double length; + private final double maxVelocity; + private final double turnaroundTime; + + private final Map attributes = new HashMap<>(); + + /** + * @param id the name of the vehicle type. + * @param capacity the passenger capacity of the vehicle. + * @param length the vehicle length. + * @param maxVelocity the maximum velocity of the vehicle. + * @param turnaroundTime the time needed to change direction of travel in a station (change drivers cab). + */ + public VehicleTypeInfo(String id, int capacity, double length, double maxVelocity, double turnaroundTime) { + this.id = id; + this.capacity = capacity; + this.length = length; + this.maxVelocity = maxVelocity; + this.turnaroundTime = turnaroundTime; + } + + public String getId() { + return id; + } + + public int getCapacity() { + return capacity; + } + + public double getLength() { + return length; + } + + public double getMaxVelocity() { + return maxVelocity; + } + + public double getTurnaroundTime() { + return turnaroundTime; + } + + public Map getAttributes() { + return attributes; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java new file mode 100644 index 00000000000..8c23520a613 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java @@ -0,0 +1,256 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteStopInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; +import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleAllocationInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleCircuitsPlanner; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.utils.misc.Time; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Default implementation of the vehicle circuits planner + *

+ * Uses the RouteType and RouteDirection enums to determine the type of route and the direction of travel. The RouteType enum defines the possible route types for a transit line in two directions. The + * RouteDirection enum defines the direction of the route, either starting from the origin or the destination (F: forward, R: Reverse). + * + * @author Merlin Unterfinger + */ +public class DefaultVehicleCircuitsPlanner implements VehicleCircuitsPlanner { + + private static final Logger log = LogManager.getLogger(DefaultVehicleCircuitsPlanner.class); + private final double depotTravelTime; + private final double circuitMaxWaitingTime; + private final HashMap allocations = new HashMap<>(); + private final LinkedList routeDepartureEventQueue = new LinkedList<>(); + private final VehicleFleet vehicleFleet = new VehicleFleet(); + private int fromDepotCount = 0; + private int toDepotCount = 0; + + /** + * @param scenario the scenario with the railsim supply config group. + */ + public DefaultVehicleCircuitsPlanner(Scenario scenario) { + var config = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); + this.depotTravelTime = config.getDepotTravelTime(); + this.circuitMaxWaitingTime = config.getCircuitMaxWaitingTime(); + } + + /** + * Plan the day with vehicle circuits + *

+ *

    + *
  • Creates a TransitLineVehicleAllocation object per TransitLineInfo.
  • + *
  • Sorts the departures according to arrivals
  • + *
  • Loop through the day and creates vehicle circuits
  • + *
+ */ + @Override + public Map plan(List transitLineInfos) { + // reset state + fromDepotCount = 0; + toDepotCount = 0; + routeDepartureEventQueue.clear(); + vehicleFleet.clear(); + allocations.clear(); + // create departure events, sorted according to arrival time + sortAllDepartures(transitLineInfos); + // process departure events + for (RouteDepartureEvent routeDepartureEvent : routeDepartureEventQueue) { + handleDepartureEvent(routeDepartureEvent); + } + // run consistency checks + log.info("Checking planned circuits for consistency..."); + if (Stream.of(checkDepotCounts(), checkEmptyStopQueues(), checkDepotVehicleCounts()).anyMatch(check -> !check)) { + throw new RuntimeException("Not all consistency checks passed! Aborting..."); + } + log.info("PASSED - vehicle circuits are consistent"); + // convert to interface type + return new HashMap<>(allocations); + } + + /** + * Handles a chronologically ordered route departure event. + * + * @param routeDepartureEvent the current departure event. + */ + private void handleDepartureEvent(RouteDepartureEvent routeDepartureEvent) { + log.info(String.format("Handling departure event: %s %s -> %s (departure: %s, arrival: %s, ready: %s)", routeDepartureEvent.getTransitLineInfo().getId(), routeDepartureEvent.getOrigin().getStopInfo().getId(), routeDepartureEvent.getDestination().getStopInfo().getId(), Time.writeTime(routeDepartureEvent.getDepartureTime(), Time.TIMEFORMAT_HHMMSS), Time.writeTime(routeDepartureEvent.getArrivalTime(), Time.TIMEFORMAT_HHMMSS), Time.writeTime(routeDepartureEvent.getReadyAgainTime(), Time.TIMEFORMAT_HHMMSS))); + // get next vehicle from stop or depot, needs: transitLineInfo, routeDirection, departureTime + final VehicleFleet.AvailableVehicle availableVehicle = vehicleFleet.getNextVehicleForDeparture(routeDepartureEvent.getTransitLineInfo(), routeDepartureEvent.getRouteDirection(), routeDepartureEvent.getDepartureTime()); + final boolean fromDepot = availableVehicle.fromDepot(); + final Vehicle vehicle = availableVehicle.vehicle(); + // increase counter for validation + if (fromDepot) { + fromDepotCount++; + } + // check if destination is depot and send vehicle to depot + RouteDepartureEvent oppositeDeparture = routeDepartureEvent.getNextPossibleOppositeDeparture(); + boolean toDepot = false; + if (oppositeDeparture == null) { + // calculate time it takes to drive to the depot and back: + // arrival time at stop + waiting time at stop + travel time to and from depot (2x) + waiting time at stop. + final double readyFromDepotTime = routeDepartureEvent.getArrivalTime() + 2 * routeDepartureEvent.getDestinationWaitingTime() + 2 * depotTravelTime; + toDepot = true; + // increase counter for validation + toDepotCount++; + vehicleFleet.sendToDepot(routeDepartureEvent.getTransitLineInfo(), routeDepartureEvent.getRouteDirection(), vehicle, readyFromDepotTime); + } else { + vehicleFleet.stayOnStopLink(routeDepartureEvent.getTransitLineInfo(), routeDepartureEvent.getRouteDirection(), vehicle, routeDepartureEvent.getReadyAgainTime()); + } + // determine route type + RouteType routeType = fromDepot ? + // from depot to ... + (toDepot ? RouteType.DEPOT_TO_DEPOT : RouteType.DEPOT_TO_STATION) : + // from station to ... + (toDepot ? RouteType.STATION_TO_DEPOT : RouteType.STATION_TO_STATION); + // allocate vehicle to departure + allocations.get(routeDepartureEvent.getTransitLineInfo()).addVehicleAllocation(routeDepartureEvent, routeType, vehicle); + } + + /** + * Sort route departures + *

+ * Sorts route departures of all transit lines according to the arrival time of the route. Further it initializes a TransitLineVehicleAllocation for each TransitLineInfo. + */ + private void sortAllDepartures(List transitLineInfos) { + log.info("Sorting departures of individual transit lines..."); + for (TransitLineInfo transitLineInfo : transitLineInfos) { + // create allocation object for each transit line to store the allocations + allocations.put(transitLineInfo, new TransitLineVehicleAllocation()); + // retrieve total route time (symmetric in both directions) + final double totalRouteTime = calculateTotalRouteTime(transitLineInfo); + // create linked lists for reference to opposite departures + LinkedList plannedDeparturesOrig = new LinkedList<>(); + LinkedList plannedDeparturesDest = new LinkedList<>(); + // forward: starting from origin + double destinationWaitingTime = calculateDestinationWaitingTime(transitLineInfo, RouteDirection.FORWARD); + for (double departure : transitLineInfo.getDepartures(RouteDirection.FORWARD)) { + RouteDepartureEvent routeDepartureEvent = new RouteDepartureEvent(departure, totalRouteTime, destinationWaitingTime, circuitMaxWaitingTime, RouteDirection.FORWARD, transitLineInfo, plannedDeparturesDest); + plannedDeparturesOrig.add(routeDepartureEvent); + routeDepartureEventQueue.add(routeDepartureEvent); + } + // reverse: starting from destination + destinationWaitingTime = calculateDestinationWaitingTime(transitLineInfo, RouteDirection.REVERSE); + for (double departure : transitLineInfo.getDepartures(RouteDirection.REVERSE)) { + RouteDepartureEvent routeDepartureEvent = new RouteDepartureEvent(departure, totalRouteTime, destinationWaitingTime, circuitMaxWaitingTime, RouteDirection.REVERSE, transitLineInfo, plannedDeparturesOrig); + plannedDeparturesDest.add(routeDepartureEvent); + routeDepartureEventQueue.add(routeDepartureEvent); + } + } + log.info("Sorting planned departures for all transit lines..."); + Collections.sort(routeDepartureEventQueue); + } + + /** + * Check if outgoing and ingoing depot counts are equal. + * + * @return true if check passes, false otherwise. + */ + private boolean checkDepotCounts() { + final boolean passed = fromDepotCount == toDepotCount; + final String message = String.format("Outgoing and ingoing depot counts are %s (%d OUT %s %d IN)", passed ? "equal" : "not equal", fromDepotCount, passed ? "==" : "!=", toDepotCount); + if (!passed) { + log.warn(message); + return false; + } + log.info(message); + return true; + } + + /** + * Check if all vehicles queues on stop links are empty at the end of the day. + * + * @return true if check passes, false otherwise. + */ + private boolean checkEmptyStopQueues() { + final int totalVehicleQueueSize = vehicleFleet.getTotalQueueSize(); + final boolean passed = totalVehicleQueueSize == 0; + final String message = String.format("%s", passed ? "All vehicle queues on stop links are empty" : "Not all vehicle queues on stop links are empty"); + if (!passed) { + log.warn(message + " (vehicles left = " + totalVehicleQueueSize + ")"); + return false; + } + log.info(message); + return true; + } + + /** + * Check if all created vehicles are collected in a depot at the end of the day. + * + * @return true if check passes, false otherwise. + */ + private boolean checkDepotVehicleCounts() { + final Map vehiclesCreated = vehicleFleet.getTotalVehicleCounts(); + final Map vehiclesCollected = vehicleFleet.getTotalVehicleInDepotCounts(); + StringBuilder sb = new StringBuilder(); + sb.append("("); + boolean passed = true; + for (Map.Entry entry : vehiclesCreated.entrySet()) { + int created = entry.getValue(); + int collected = vehiclesCollected.get(entry.getKey()); + passed = passed && created == collected; + sb.append(entry.getKey().getId()); + sb.append(": "); + sb.append(created); + sb.append("+, "); + sb.append(collected); + sb.append("-; "); + } + sb.delete(sb.length() - 2, sb.length()); + sb.append(")"); + final String message = String.format("%s created vehicles are collected at the end of the day %s", passed ? "All" : "Not all", sb); + if (!passed) { + log.warn(message); + return false; + } + log.info(message); + return true; + } + + /** + * Total route time + *

+ * Get total route travel and waiting time between route origin and destination without first and last waiting time at station. Note: The direction does not matter, since it is symmetric. But + * could be changed in the future. + * + * @param transitLineInfo the transit line information. + * @return the total route time. + */ + private static double calculateTotalRouteTime(TransitLineInfo transitLineInfo) { + final List routeStopInfos = transitLineInfo.getRouteStopInfos(RouteDirection.FORWARD); + final List travelTimes = transitLineInfo.getTravelTimes(RouteDirection.FORWARD); + // sum waiting times at intermediate stops + return routeStopInfos.stream().limit(routeStopInfos.size() - 1).skip(1).mapToDouble(RouteStopInfo::getWaitingTime).sum() + // sum travel times + + travelTimes.stream().mapToDouble(Double::doubleValue).sum(); + } + + /** + * Destination waiting time + *

+ * Total time to wait at destination before next departure is possible. The calculation depends on the direction, since the waiting time at the last stop is taken. + * + * @param transitLineInfo the transit line information. + * @param routeDirection does the route start at the origin? + * @return the destination waiting time. + */ + private static double calculateDestinationWaitingTime(TransitLineInfo transitLineInfo, RouteDirection routeDirection) { + final double turnaroundTime = transitLineInfo.getVehicleTypeInfo().getTurnaroundTime(); + return Math.max(transitLineInfo.getDestination(routeDirection).getWaitingTime(), turnaroundTime); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java new file mode 100644 index 00000000000..2f8b6795c4f --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java @@ -0,0 +1,140 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteStopInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.utils.misc.Time; + +import java.util.LinkedList; + +/** + * A route departure event is the first departure of a route at the origin station. + * + * @author Merlin Unterfinger + */ +class RouteDepartureEvent implements Comparable { + + private static final Logger log = LogManager.getLogger(RouteDepartureEvent.class); + private final double departureTime; + private final double totalRouteTime; + private final double destinationWaitingTime; + private final double circuitMaxWaitingTime; + private final RouteDirection routeDirection; + private final TransitLineInfo transitLineInfo; + private final LinkedList oppositeDepartures; + + /** + * This time is important for sorting, we want to process the departures according to the time when they are ready again for another departure. + */ + private final double readyAgainTime; + + /** + * @param departureTime the departure time. + * @param totalRouteTime the total time until the destination is reached (without waiting times at origin and destination). + * @param destinationWaitingTime the waiting time at the destination station. + * @param circuitMaxWaitingTime the maximum time to wait at the destination for a next circuit departure, otherwise the vehicle is sent to the depot. + * @param routeDirection the route direction. + * @param transitLineInfo the transit line information. + * @param oppositeDepartures the departures from the same transit line in the opposite direction. + */ + RouteDepartureEvent(double departureTime, double totalRouteTime, double destinationWaitingTime, double circuitMaxWaitingTime, RouteDirection routeDirection, TransitLineInfo transitLineInfo, LinkedList oppositeDepartures) { + this.departureTime = departureTime; + this.totalRouteTime = totalRouteTime; + this.destinationWaitingTime = destinationWaitingTime; + this.circuitMaxWaitingTime = circuitMaxWaitingTime; + this.routeDirection = routeDirection; + this.transitLineInfo = transitLineInfo; + this.oppositeDepartures = oppositeDepartures; + this.readyAgainTime = departureTime + totalRouteTime + destinationWaitingTime; + } + + /** + * Search and get the next possible departures from the opposite station. + * + * @return the next planned departure or null. + */ + public RouteDepartureEvent getNextPossibleOppositeDeparture() { + // delete all opposite departures which are departing before this current departure would get ready again in the destination stop queue. + // we can do this, since the denatures are sorted according to arrivalTime, therefore no sooner readyAgain vehicle in the stop queue is possible than the current one. + // e.g. ready again at 11:05 at destination, opposite departure at 11:00 could be deleted. + while (!oppositeDepartures.isEmpty() && oppositeDepartures.getFirst().getDepartureTime() < readyAgainTime) { + RouteDepartureEvent oppositeDeparture = oppositeDepartures.removeFirst(); + log.info(String.format("DELETING - %s", renderDepartureInfo(oppositeDeparture))); + } + // get the next departure that is departing after the current vehicle is ready again and check if it is in the possible time window for departure + if (!oppositeDepartures.isEmpty()) { + RouteDepartureEvent oppositeDeparture = oppositeDepartures.getFirst(); + log.info(String.format("CHECKING - %s", renderDepartureInfo(oppositeDeparture))); + if (oppositeDeparture.departureTime >= this.readyAgainTime && oppositeDeparture.departureTime <= this.readyAgainTime + circuitMaxWaitingTime) { + // match! Remove route departure event from queue and return + oppositeDeparture = oppositeDepartures.removeFirst(); + log.info(String.format("SELECTED - %s", renderDepartureInfo(oppositeDeparture))); + return oppositeDeparture; + } + } + return null; + } + + /** + * Sort according to ready again time (=arrival time + destinationWaitingTime). + * + * @param other the object to be compared. + * @return comparison. + */ + @Override + public int compareTo(RouteDepartureEvent other) { + return Double.compare(this.readyAgainTime, other.readyAgainTime); + } + + /** + * Render route departure event info to string. + * + * @param oppositeDeparture the next opposite departure. + * @return the departure event information as string. + */ + private String renderDepartureInfo(RouteDepartureEvent oppositeDeparture) { + return String.format("departure: %s, ready: %s, maxWaitingTime: %s, oppositeDeparture: %s", Time.writeTime(this.departureTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(this.readyAgainTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(this.readyAgainTime + circuitMaxWaitingTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(oppositeDeparture.departureTime, Time.TIMEFORMAT_HHMMSS)); + } + + public RouteStopInfo getOrigin() { + return transitLineInfo.getOrigin(routeDirection); + } + + public RouteStopInfo getDestination() { + return transitLineInfo.getDestination(routeDirection); + } + + public double getArrivalTime() { + return departureTime + totalRouteTime; + } + + public double getDepartureTime() { + return departureTime; + } + + public double getTotalRouteTime() { + return totalRouteTime; + } + + public double getDestinationWaitingTime() { + return destinationWaitingTime; + } + + public RouteDirection getRouteDirection() { + return routeDirection; + } + + public TransitLineInfo getTransitLineInfo() { + return transitLineInfo; + } + + public LinkedList getOppositeDepartures() { + return oppositeDepartures; + } + + public double getReadyAgainTime() { + return readyAgainTime; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java new file mode 100644 index 00000000000..c56adf5d796 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java @@ -0,0 +1,84 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleAllocationInfo; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.utils.misc.Time; + +import java.util.EnumMap; +import java.util.LinkedList; + +/** + * Transit line vehicle allocation + *

+ * Container class to store the route departures with allocated vehicles per line. + * + * @author Merlin Unterfinger + */ +class TransitLineVehicleAllocation implements VehicleAllocationInfo { + + private static final Logger log = LogManager.getLogger(TransitLineVehicleAllocation.class); + + private final EnumMap>> directedDepartures = new EnumMap<>(RouteDirection.class); + private final EnumMap>> directedVehicleIds = new EnumMap<>(RouteDirection.class); + + /** + * Ctor + */ + public TransitLineVehicleAllocation() { + directedDepartures.put(RouteDirection.FORWARD, new EnumMap<>(RouteType.class)); + directedDepartures.put(RouteDirection.REVERSE, new EnumMap<>(RouteType.class)); + directedVehicleIds.put(RouteDirection.FORWARD, new EnumMap<>(RouteType.class)); + directedVehicleIds.put(RouteDirection.REVERSE, new EnumMap<>(RouteType.class)); + } + + /** + * Adds a vehicle allocation for a route departure to the transit line. + * + * @param routeDepartureEvent the departure event. + * @param routeType the route type of the route. + * @param vehicle the vehicle to allocate. + */ + public void addVehicleAllocation(RouteDepartureEvent routeDepartureEvent, RouteType routeType, Vehicle vehicle) { + final RouteDirection routeDirection = routeDepartureEvent.getRouteDirection(); + final double departureTime = routeDepartureEvent.getDepartureTime(); + final String vehicleId = vehicle.id(); + log.info(String.format("Allocating vehicle %s to departure %s of line %s (%s)", vehicleId, Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), routeDepartureEvent.getTransitLineInfo().getId(), routeType.name())); + // add departure time + EnumMap> departureMap = directedDepartures.get(routeDirection); + LinkedList departuresList = departureMap.getOrDefault(routeType, new LinkedList<>()); + departuresList.add(departureTime); + departureMap.put(routeType, departuresList); + // add vehicle id + EnumMap> vehicleIdMap = directedVehicleIds.get(routeDirection); + LinkedList vehicleIds = vehicleIdMap.getOrDefault(routeType, new LinkedList<>()); + vehicleIds.add(vehicleId); + vehicleIdMap.put(routeType, vehicleIds); + } + + /** + * Retrieve the departure times for a route type and direction. + * + * @param routeType the type of the route. + * @param routeDirection the route direction. + * @return a list containing the departure times. + */ + @Override + public LinkedList getDepartures(RouteType routeType, RouteDirection routeDirection) { + return directedDepartures.get(routeDirection).get(routeType); + } + + /** + * Retrieve the vehicle ids for a route type and direction. + * + * @param routeType the type of the route. + * @param routeDirection the route direction. + * @return a list containing the vehicle ids. + */ + @Override + public LinkedList getVehicleIds(RouteType routeType, RouteDirection routeDirection) { + return directedVehicleIds.get(routeDirection).get(routeType); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java new file mode 100644 index 00000000000..928f68a4149 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java @@ -0,0 +1,14 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; + +/** + * A package internal vehicle class. + * + * @param id vehicle id. + * @param type the vehicle type information. + * @author Merlin Unterfinger + */ +record Vehicle(String id, VehicleTypeInfo type) { + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java new file mode 100644 index 00000000000..b3c53d3801e --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java @@ -0,0 +1,116 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.utils.misc.Time; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +/** + * A vehicle depot + *

+ * Contains a queue for each vehicle type that enters the depot. Can create new vehicles if empty. + * + * @author Merlin Unterfinger + */ +class VehicleDepot { + + private static final Logger log = LogManager.getLogger(VehicleDepot.class); + private final HashMap> vehicles = new HashMap<>(); + private final VehicleFleet fleet; + private final String name; + + /** + * Ctor + * + * @param name the name of the depot. + * @param fleet the fleet, the depot corresponds to. + */ + VehicleDepot(String name, VehicleFleet fleet) { + this.name = name; + this.fleet = fleet; + } + + /** + * Gets or creates a new vehicle + *

+ * If there is a vehicle of the corresponding type is ready in the queue, it is taken. Otherwise, a new vehicle of this type is generated and returned. + * + * @param vehicleTypeInfo the vehicle type information, used to determine the requested vehicle type. + * @return a new or existing vehicle. + */ + public Vehicle getVehicle(VehicleTypeInfo vehicleTypeInfo, double departureTime) { + // get vehicle queue of corresponding type, add back to hashmap (only relevant the first time) + LinkedList vehiclesOfType = vehicles.getOrDefault(vehicleTypeInfo, new LinkedList<>()); + vehicles.put(vehicleTypeInfo, vehiclesOfType); + // check if there are vehicles in the depot, if there are any, check if the first one is ready again before departure + if (!vehiclesOfType.isEmpty() && departureTime >= vehiclesOfType.getFirst().time()) { + VehicleReadyEvent vehicleReadyEvent = vehiclesOfType.removeFirst(); + Vehicle vehicle = vehicleReadyEvent.vehicle(); + log.info(String.format("Leaving depot link, remove available vehicle '%s' (departure: %s, ready: %s) from depot queue '%s'", vehicleReadyEvent.vehicle().id(), Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS), name)); + return vehicle; + } + // No vehicle was ready in the depot queue, create a new one + final String vehicleId = String.format("%s_%d", vehicleTypeInfo.getId(), fleet.increaseVehicleCount(vehicleTypeInfo)); + log.info(String.format("No vehicle ready (departure: %s) in depot, creating new vehicle '%s' one", Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), vehicleId)); + return new Vehicle(vehicleId, vehicleTypeInfo); + } + + /** + * Add a vehicle to the depot. + * + * @param vehicle the vehicle to add to the depot. + * @param readyFromDepotTime the time until a vehicle is ready again for a departure from depot: (arrival + waitingTime + 2 * depot travel time + waitingTime) + */ + public void addVehicle(Vehicle vehicle, double readyFromDepotTime) { + log.info(String.format("Entering depot link, add vehicle '%s' to depot queue '%s'", vehicle.id(), name)); + LinkedList vehiclesOfType = vehicles.getOrDefault(vehicle.type(), new LinkedList<>()); + vehiclesOfType.addLast(new VehicleReadyEvent(vehicle, readyFromDepotTime)); + vehicles.put(vehicle.type(), vehiclesOfType); + } + + /** + * Get the vehicle type specific count of all vehicles in the depot + * + * @return a hashmap containing the counts per vehicle type. + */ + public HashMap getVehicleCounts() { + HashMap vehicleCounter = new HashMap<>(); + for (Map.Entry> entry : vehicles.entrySet()) { + vehicleCounter.put(entry.getKey(), entry.getValue().size()); + } + return vehicleCounter; + } + + /** + * Create a log message with the inventory of the depot. + *

+ * Useful for debugging. + */ + public void logInventory() { + StringBuilder sb = new StringBuilder("Depot inventory of "); + sb.append(name); + sb.append(":\n"); + for (Map.Entry> entry : vehicles.entrySet()) { + if (entry.getValue().isEmpty()) { + continue; + } + sb.append(" - "); + sb.append(entry.getKey().getId()); + sb.append(": "); + for (VehicleReadyEvent vehicleReadyEvent : entry.getValue()) { + sb.append(vehicleReadyEvent.vehicle().id()); + sb.append(" ("); + sb.append(Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS)); + sb.append("), "); + } + sb.delete(sb.length() - 2, sb.length()); + sb.append("\n"); + } + sb.delete(sb.length() - 1, sb.length()); + log.info(sb); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java new file mode 100644 index 00000000000..60bcfca027e --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java @@ -0,0 +1,190 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; +import ch.sbb.matsim.contrib.railsim.prototype.supply.StopInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; + +import java.util.HashMap; +import java.util.Map; + +/** + * Vehicle fleet + *

+ * This class manages the depots and vehicle queues at stop links. Gets or sends vehicles to the queues. + * + * @author Merlin Unterfinger + */ +class VehicleFleet { + + private enum TargetQueue { + /** + * Get the queue at the origin of a route departure. + */ + ORIGIN, + /** + * Get the queue at the destination of a route departure. + */ + DESTINATION + } + + public record AvailableVehicle(Boolean fromDepot, Vehicle vehicle) { + + } + + private final Map stops = new HashMap<>(); + private final Map depots = new HashMap<>(); + private final Map vehicleCounts = new HashMap<>(); + + /** + * Default ctor + */ + VehicleFleet() { + } + + /** + * Request a vehicle for the next route departure from the origin of the transit line. + *

+ * Note: The origin is switched depending on the requested departure. + * + * @param transitLineInfo the transit line information of the departure. + * @param routeDirection the direction of the route departure. + * @param departureTime the time of the departure. + * @return A pair holding the information if a vehicle came from the depot and the vehicle itself. + */ + public AvailableVehicle getNextVehicleForDeparture(TransitLineInfo transitLineInfo, RouteDirection routeDirection, double departureTime) { + final VehicleQueue vehicleQueue = getQueue(transitLineInfo, routeDirection, TargetQueue.ORIGIN); + // set initial value to a vehicle from the stop queue (STATION) + boolean fromDepot = false; + // log the current state of the stop queue for debugging + vehicleQueue.logState(); + // get existing vehicle from stop queue if there is one waiting + Vehicle vehicle = vehicleQueue.getVehicle(departureTime); + // get a vehicle from depot if no vehicle (null) is available in the stop queue and set origin to depot queue (DEPOT) + if (vehicle == null) { + VehicleDepot vehicleDepot = getDepot(transitLineInfo, routeDirection, TargetQueue.ORIGIN); + vehicleDepot.logInventory(); + fromDepot = true; + vehicle = vehicleDepot.getVehicle(transitLineInfo.getVehicleTypeInfo(), departureTime); + } + // return the vehicle and a boolean indicating if the vehicle is from the depot or not + return new AvailableVehicle(fromDepot, vehicle); + } + + /** + * Send a vehicle to the depot. + * + * @param transitLineInfo the transit line information. + * @param routeDirection the direction of the route. + * @param vehicle the vehicle driving on the current route. + * @param readyFromDepotTime the time until a vehicle is ready again for a departure from depot: (arrival + waitingTime + 2 * depot travel time + waitingTime). + */ + public void sendToDepot(TransitLineInfo transitLineInfo, RouteDirection routeDirection, Vehicle vehicle, double readyFromDepotTime) { + // set target queue to destination, since we want to send the vehicle to the destination depot + VehicleDepot vehicleDepot = getDepot(transitLineInfo, routeDirection, TargetQueue.DESTINATION); + vehicleDepot.addVehicle(vehicle, readyFromDepotTime); + } + + /** + * Let vehicle wait on the current stop link for the next departure. + * + * @param transitLineInfo the transit line information. + * @param routeDirection the direction of the route. + * @param vehicle the vehicle driving on the current route. + * @param readyAgainTime the time until a vehicle is ready again for a departure from the stop link: arrival + max(waitingTime, turnaroundTime). + */ + public void stayOnStopLink(TransitLineInfo transitLineInfo, RouteDirection routeDirection, Vehicle vehicle, double readyAgainTime) { + // set target queue to destination, since we want to queue the vehicle at the destination + VehicleQueue vehicleQueue = getQueue(transitLineInfo, routeDirection, TargetQueue.DESTINATION); + vehicleQueue.addVehicle(vehicle, readyAgainTime); + } + + /** + * Get and increase vehicle type specific count + *

+ * This method is used to generate globally unique vehicle ids. + * + * @param vehicleTypeInfo the vehicle type of the vehicle. + * @return the not yet increased count. + */ + public int increaseVehicleCount(VehicleTypeInfo vehicleTypeInfo) { + int vehicleTypeCount = vehicleCounts.getOrDefault(vehicleTypeInfo, 0); + int tmp = vehicleTypeCount; + vehicleCounts.put(vehicleTypeInfo, ++vehicleTypeCount); + return tmp; + } + + /** + * Get the number of vehicles in all stop link queues. + * + * @return the summed size of all vehicle queues. + */ + public int getTotalQueueSize() { + if (stops.isEmpty()) { + return 0; + } + return stops.values().stream().mapToInt(VehicleQueue::size).sum(); + } + + /** + * A counter of all created vehicles + * + * @return a hashmap containing the counts for all created vehicles. + */ + public Map getTotalVehicleCounts() { + return vehicleCounts; + } + + /** + * A counter of all vehicles currently located in a depot + * + * @return a hashmap containing the counts for all vehicles in depots. + */ + public HashMap getTotalVehicleInDepotCounts() { + HashMap globalVehicleCounter = new HashMap<>(); + for (VehicleDepot depot : depots.values()) { + for (Map.Entry entry : depot.getVehicleCounts().entrySet()) { + int count = globalVehicleCounter.getOrDefault(entry.getKey(), 0); + count += entry.getValue(); + globalVehicleCounter.put(entry.getKey(), count); + } + } + return globalVehicleCounter; + } + + /** + * Reset the vehicle fleet + */ + public void clear() { + this.stops.clear(); + this.depots.clear(); + this.vehicleCounts.clear(); + } + + private VehicleDepot getDepot(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { + final StopInfo stopInfo = getTargetStopInfo(transitLineInfo, routeDirection, targetQueue); + final VehicleDepot vehicleDepot = depots.getOrDefault(stopInfo, new VehicleDepot(stopInfo.getId(), this)); + depots.put(stopInfo, vehicleDepot); + return vehicleDepot; + } + + private VehicleQueue getQueue(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { + final String key = constructQueueKey(transitLineInfo, routeDirection, targetQueue); + final VehicleQueue vehicleQueue = stops.getOrDefault(key, new VehicleQueue(key)); + stops.put(key, vehicleQueue); + return vehicleQueue; + } + + private static String constructQueueKey(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { + // key: transitLineName_vehicleTypeName_stopInfoName + return String.format("%s_%s_%s", transitLineInfo.getId(), transitLineInfo.getVehicleTypeInfo().getId(), getTargetStopInfo(transitLineInfo, routeDirection, targetQueue).getId()); + } + + private static StopInfo getTargetStopInfo(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { + return switch (targetQueue) { + case ORIGIN -> transitLineInfo.getOrigin(routeDirection).getStopInfo(); + case DESTINATION -> transitLineInfo.getDestination(routeDirection).getStopInfo(); + }; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java new file mode 100644 index 00000000000..8024c109007 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java @@ -0,0 +1,93 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.utils.misc.Time; + +import java.util.LinkedList; + +/** + * A vehicle queue (FIFO) + *

+ * Stores the time when vehicles, which are waiting on a stop link, a ready for the next departure. + * + * @author Merlin Unterfinger + */ +class VehicleQueue { + + private static final Logger log = LogManager.getLogger(VehicleQueue.class); + private final LinkedList vehicleReadyEvents = new LinkedList<>(); + private final String name; + + /** + * Vehicle FIFO queue + * + * @param name the name of the vehicle queue. + */ + public VehicleQueue(String name) { + this.name = name; + } + + /** + * Add a vehicle to the end of the queue. + * + * @param vehicle the vehicle to add to the queue. + * @param readyAgainTime the time when a departure is possible again with this vehicle. + */ + public void addVehicle(Vehicle vehicle, double readyAgainTime) { + log.info(String.format("Staying on stop link, appending vehicle '%s' to queue '%s'", vehicle.id(), name)); + vehicleReadyEvents.addLast(new VehicleReadyEvent(vehicle, readyAgainTime)); + } + + /** + * Get a vehicle from the start of the queue, if the departure time is after the ready time. + * + * @param departureTime the departure time. + * @return the vehicle or null if no vehicle is available for departure. + */ + public Vehicle getVehicle(double departureTime) { + // check if vehicles are waiting in the queue + if (vehicleReadyEvents.isEmpty()) { + return null; + } + VehicleReadyEvent vehicleReadyEvent = vehicleReadyEvents.getFirst(); + // check if departure is before ready time of vehicle + if (departureTime < vehicleReadyEvent.time()) { + log.warn(String.format("Departure time (%s) is before ready time (%s) of vehicle (%s) at stop queue '%s'! Skipping...", Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS), vehicleReadyEvent.vehicle().id(), this.name)); + return null; + } + // there is a vehicle, where the departure is inside the time window of availability, remove it from the stop queue and return + log.info(String.format("Leaving stop link, remove vehicle '%s' from queue '%s'", vehicleReadyEvent.vehicle().id(), name)); + return vehicleReadyEvents.removeFirst().vehicle(); + } + + /** + * Get the size of the queue. + * + * @return the size. + */ + public int size() { + return vehicleReadyEvents.size(); + } + + /** + * Create a log message with the state of the queue + */ + public void logState() { + StringBuilder sb = new StringBuilder("Queue state of "); + sb.append(name); + sb.append(": "); + if (vehicleReadyEvents.isEmpty()) { + sb.append("Empty"); + } else { + for (VehicleReadyEvent vehicleReadyEvent : vehicleReadyEvents) { + sb.append(vehicleReadyEvent.vehicle().id()); + sb.append(" ("); + sb.append(Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS)); + sb.append(") "); + } + sb.delete(sb.length() - 1, sb.length()); + } + log.info(sb); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java new file mode 100644 index 00000000000..a490d273d4c --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java @@ -0,0 +1,14 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +/** + * Vehicle ready event + *

+ * An event holding the time when the vehicle is ready again for a departure from a stop link or a depot. + * + * @param vehicle the vehicle. + * @param time the time when the vehicle is ready again. + * @author Merlin Unterfinger + */ +record VehicleReadyEvent(Vehicle vehicle, double time) { + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java new file mode 100644 index 00000000000..3f22acc56a0 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java @@ -0,0 +1,74 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.infrastructure; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.DepotInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.InfrastructureRepository; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; +import ch.sbb.matsim.contrib.railsim.prototype.supply.SectionPartInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.SectionSegmentInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.StopInfo; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.network.NetworkUtils; + +/** + * Default implementation of the infrastructure repository + * + * @author Merlin Unterfinger + */ +public class DefaultInfrastructureRepository implements InfrastructureRepository { + + private final int stopCapacity; + private final double stopSpeedLimit; + private final double stopLinkLength; + private final int depotCapacity; + private final int depotInOutCapacity; + private final double depotLinkLength; + private final double depotSpeedLimit; + private final double depotOffset; + private final int routeTrainCapacity; + private final double routeSpeedLimit; + private final double routeEuclideanDistanceFactor; + + public DefaultInfrastructureRepository(Scenario scenario) { + var config = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); + stopCapacity = config.getStopTrainCapacity(); + stopSpeedLimit = config.getStopSpeedLimit(); + stopLinkLength = config.getStopLinkLength(); + routeTrainCapacity = config.getRouteTrainCapacity(); + routeSpeedLimit = config.getRouteSpeedLimit(); + routeEuclideanDistanceFactor = config.getRouteEuclideanDistanceFactor(); + depotCapacity = config.getDepotTrainCapacity(); + depotInOutCapacity = config.getDepotInOutCapacity(); + depotLinkLength = config.getDepotLinkLength(); + depotSpeedLimit = config.getDepotSpeedLimit(); + depotOffset = config.getDepotOffset(); + } + + @Override + public StopInfo getStop(String stopId, double x, double y) { + var stopInfo = new StopInfo(stopId, new Coord(x, y), stopLinkLength); + InfrastructureRepository.addRailsimAttributes(stopInfo, stopCapacity, stopSpeedLimit, 0.); + return stopInfo; + } + + @Override + public DepotInfo getDepot(StopInfo stopInfo) { + var depotInfo = new DepotInfo(stopInfo.getId(), new Coord(stopInfo.getCoord().getX(), stopInfo.getCoord().getY() - depotOffset), depotLinkLength, depotOffset, depotOffset, depotCapacity); + // add in link attributes + InfrastructureRepository.addRailsimAttributes(depotInfo, depotCapacity, depotInOutCapacity, depotSpeedLimit, 0.); + return depotInfo; + } + + @Override + public SectionPartInfo getSectionPart(StopInfo fromStop, StopInfo toStop) { + final double length = routeEuclideanDistanceFactor * NetworkUtils.getEuclideanDistance(fromStop.getCoord(), toStop.getCoord()); + var sectionPartInfo = new SectionPartInfo(fromStop.getId(), toStop.getId()); + // add one segment for th section + var sectionSegmentInfo = new SectionSegmentInfo(fromStop.getCoord(), toStop.getCoord(), length); + sectionPartInfo.addSegment(sectionSegmentInfo); + // add link attributes + InfrastructureRepository.addRailsimAttributes(sectionSegmentInfo, routeTrainCapacity, routeSpeedLimit, 0.); + return sectionPartInfo; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java new file mode 100644 index 00000000000..6b04bce0c92 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java @@ -0,0 +1,39 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.rollingstock; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RollingStockRepository; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.ConfigUtils; + +/** + * Default implementation of the rolling stock repository + * + * @author Merlin Unterfinger + */ +public class DefaultRollingStockRepository implements RollingStockRepository { + + private final int passengerCapacity; + private final double length; + private final double maxVelocity; + private final double maxAcceleration; + private final double maxDeceleration; + private final double turnaroundTime; + + public DefaultRollingStockRepository(Scenario scenario) { + var config = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); + passengerCapacity = config.getVehiclePassengerCapacity(); + length = config.getVehicleLength(); + maxVelocity = config.getVehicleMaxVelocity(); + maxAcceleration = config.getVehicleMaxAcceleration(); + maxDeceleration = config.getVehicleMaxDeceleration(); + turnaroundTime = config.getVehicleTurnaroundTime(); + } + + @Override + public VehicleTypeInfo getVehicleType(String vehicleTypeId) { + var vehicleTypeInfo = new VehicleTypeInfo(vehicleTypeId, passengerCapacity, length, maxVelocity, turnaroundTime); + RollingStockRepository.addRailsimAttributes(vehicleTypeInfo, maxAcceleration, maxDeceleration); + return vehicleTypeInfo; + } +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java new file mode 100644 index 00000000000..c61dfb81ea6 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java @@ -0,0 +1,131 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply; + +import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; +import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; +import ch.sbb.matsim.contrib.railsim.prototype.prepare.SplitTransitLinks; +import org.apache.logging.log4j.LogManager; +import org.junit.Assert; +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.QSimConfigGroup; +import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.network.io.NetworkWriter; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.pt.transitSchedule.api.TransitScheduleWriter; +import org.matsim.testcases.MatsimTestUtils; +import org.matsim.vehicles.MatsimVehicleWriter; + +import java.nio.file.Paths; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; + +/** + * Test for supply generation using railsim supply builder. + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class RailsimSupplyBuilderTest { + + private static final String CONFIG_FILE = "config.xml"; + private static final String NETWORK_FILE = "transitNetwork.xml"; + private static final String SCHEDULE_FILE = "transitSchedule.xml"; + private static final String VEHICLE_FILE = "transitVehicles.xml"; + private static final String RUN_ID = "test"; + private Scenario scenario; + private RailsimSupplyBuilder supply; + private double waitingTime; + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Before + public void setUp() { + Config config = ConfigUtils.createConfig(); + config.global().setCoordinateSystem("CH1903plus_LV95"); + scenario = ScenarioUtils.loadScenario(config); + supply = new RailsimSupplyBuilder(scenario); + waitingTime = 3. * 60; + } + + @Test + public void testBuild() { + supply.addStop("genf", 2499965., 1119074.); + supply.addStop("versoix", 2501905., 1126194.); + supply.addStop("coppet", 2503642., 1130305.); + supply.addStop("nyon", 2507480., 1137714.); + // add transit line: IR + final var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); + ir.addStop("versoix", 10 * 60., waitingTime); + ir.addStop("coppet", 30 * 60., waitingTime); + ir.addStop("nyon", 20 * 60., waitingTime); + Stream.of(10 * 3600., 11 * 3600., 12 * 3600., 13 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.FORWARD, departureTime)); + Stream.of(10.25 * 3600., 11.25 * 3600., 12.25 * 3600., 13.25 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.REVERSE, departureTime)); + // add transit line: IC + final var ic = supply.addTransitLine("IC", "IC", "genf", waitingTime); + ic.addPass("versoix"); + ic.addPass("coppet"); + ic.addStop("nyon", 45 * 60., waitingTime); + Stream.of(10.5 * 3600., 11.5 * 3600., 12.5 * 3600., 13.5 * 3600.).forEach(departureTime -> ic.addDeparture(RouteDirection.FORWARD, departureTime)); + Stream.of(10.5 * 3600., 11.5 * 3600., 12.5 * 3600., 13.5 * 3600.).forEach(departureTime -> ic.addDeparture(RouteDirection.REVERSE, departureTime)); + // build transit schedule and network + supply.build(); + // check generated transit schedule + // network + assertEquals(12, scenario.getNetwork().getNodes().size()); + assertEquals(16, scenario.getNetwork().getLinks().size()); + // vehicles + assertEquals(2, scenario.getTransitVehicles().getVehicleTypes().size()); + assertEquals(5, scenario.getTransitVehicles().getVehicles().size()); + // schedule + assertEquals(6, scenario.getTransitSchedule().getFacilities().size()); + assertEquals(2, scenario.getTransitSchedule().getTransitLines().size()); + assertEquals(10, scenario.getTransitSchedule().getTransitLines().values().stream().mapToInt(l -> l.getRoutes().size()).sum()); + // write files + String outputDir = utils.getOutputDirectory(); + new NetworkWriter(scenario.getNetwork()).write(outputDir + NETWORK_FILE); + new TransitScheduleWriter(scenario.getTransitSchedule()).writeFile(outputDir + SCHEDULE_FILE); + new MatsimVehicleWriter(scenario.getTransitVehicles()).writeFile(outputDir + VEHICLE_FILE); + } + + @Test + public final void testRunRailsim() { + System.setProperty("matsim.preferLocalDtds", "true"); + String inputDir = Paths.get("").toAbsolutePath() + "/" + utils.getOutputDirectory().replace("testRunRailsim/", "testBuild/"); + String outputDir = utils.getOutputDirectory(); + String[] args0 = {utils.getInputDirectory() + CONFIG_FILE}; + try { + // setup + Config config = RunRailsim.prepareConfig(args0); + config.controler().setLastIteration(0); + config.network().setInputFile(inputDir + NETWORK_FILE); + config.transit().setTransitScheduleFile(inputDir + SCHEDULE_FILE); + config.transit().setVehiclesFile(inputDir + VEHICLE_FILE); + config.qsim().setSnapshotStyle(QSimConfigGroup.SnapshotStyle.queue); + config.controler().setOutputDirectory(outputDir); + config.controler().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists); + // split links + Scenario scenario = RunRailsim.prepareScenario(config); + SplitTransitLinks splitTransitLinks = new SplitTransitLinks(scenario); + splitTransitLinks.run(100.); + assertEquals(412, scenario.getNetwork().getNodes().size()); + assertEquals(416, scenario.getNetwork().getLinks().size()); + // run one iteration + Controler controler = RunRailsim.prepareControler(scenario); + controler.run(); + // convert train events + ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); + analysis.run(RUN_ID, outputDir); + } catch (Exception ee) { + ee.printStackTrace(); + LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); + Assert.fail(); + } + } +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java new file mode 100644 index 00000000000..9cab124b92f --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java @@ -0,0 +1,81 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyBuilder; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; +import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleCircuitsPlanner; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.testcases.MatsimTestUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DefaultVehicleCircuitsPlannerTest { + + private Scenario scenario; + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Before + public void setUp() { + Config config = ConfigUtils.createConfig(); + config.global().setCoordinateSystem("CH1903plus_LV95"); + scenario = ScenarioUtils.loadScenario(config); + } + + @Test + public void plan() { + var supply = new RailsimSupplyBuilder(scenario); + // add transit stops + supply.addStop("genf", 2499965., 1119074.); + supply.addStop("nyon", 2507480., 1137714.); + supply.addStop("morges", 2527501., 1151542.); + supply.addStop("lausanne", 2507480., 1137714.); + // add transit line: IR + double waitingTime = 3 * 60.; + final var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); + ir.addPass("nyon"); + ir.addPass("morges"); + ir.addStop("lausanne", 55 * 60., waitingTime); + Stream.of(0. * 3600, 1 * 3600., 2 * 3600., 10 * 3600., 11 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.FORWARD, departureTime)); + Stream.of(0.25 * 3600., 1.25 * 3600., 2.25 * 3600., 11.25 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.REVERSE, departureTime)); + // add transit line: S + final var s = supply.addTransitLine("S", "S", "genf", waitingTime); + s.addStop("nyon", 25 * 60 * 2., waitingTime); + s.addStop("morges", 15 * 60 + 2., waitingTime); + s.addStop("lausanne", 15 * 60 * 2., waitingTime); + Stream.of(0. * 3600, 0.25 * 3600., 0.5 * 3600., 0.75 * 3600., 1 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.FORWARD, departureTime)); + Stream.of(0.10 * 3600., 0.35 * 3600., 0.60 * 3600., 0.85 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.REVERSE, departureTime)); + // let supply builder create transit lines + supply.build(); + // run circuit planer + var lines = new ArrayList(); + lines.add(ir); + lines.add(s); + VehicleCircuitsPlanner vcp = new DefaultVehicleCircuitsPlanner(scenario); + var allocations = vcp.plan(lines); + // check total number of planned allocations + assertEquals(2, allocations.keySet().size()); + // check IR + assertEquals(List.of("IR_1", "IR_2"), allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_DEPOT, RouteDirection.FORWARD)); + assertEquals(List.of("IR_0", "IR_2", "IR_0"), allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_STATION, RouteDirection.FORWARD)); + assertNull(allocations.get(ir).getVehicleIds(RouteType.STATION_TO_DEPOT, RouteDirection.FORWARD)); + assertNull(allocations.get(ir).getVehicleIds(RouteType.STATION_TO_STATION, RouteDirection.FORWARD)); + assertEquals(List.of("IR_1"), allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_DEPOT, RouteDirection.REVERSE)); + assertNull(allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_STATION, RouteDirection.REVERSE)); + assertEquals(List.of("IR_0", "IR_2", "IR_0"), allocations.get(ir).getVehicleIds(RouteType.STATION_TO_DEPOT, RouteDirection.REVERSE)); + assertNull(allocations.get(ir).getVehicleIds(RouteType.STATION_TO_STATION, RouteDirection.REVERSE)); + } +} diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml new file mode 100644 index 00000000000..8a9d31f4497 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 95fc050e15fe8036e1e91064eb5efbe094d4edc6 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 28 Apr 2023 11:12:08 +0200 Subject: [PATCH 008/258] Fix last stop in transit routes of schedule - Change departureOffset at terminal stop to arrivalOffset. - Extend tests and unify the IDs in the schedule. --- .../supply/RailsimSupplyBuilder.java | 4 +-- .../prototype/supply/SupplyFactory.java | 16 +++++++----- .../supply/RailsimSupplyBuilderTest.java | 26 ++++++++++++++++++- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java index 74924f5d65e..67d67142813 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java @@ -238,7 +238,7 @@ private void addTransitRouteToTransitLine(TransitLineInfo transitLineInfo, Route final List stops = new ArrayList<>(); double cumulativeTravelTime = 0.; // first stop - stops.add(supplyFactory.createTransitRouteStop(routeStopInfos.get(0).getTransitStop(), cumulativeTravelTime)); + stops.add(supplyFactory.createTransitTerminalStop(routeStopInfos.get(0).getTransitStop(), cumulativeTravelTime, true)); // intermediate stops for (int stopCounter = 1; stopCounter <= routeStopInfos.size() - 2; stopCounter++) { // increase cumulative travel time until arrival @@ -252,7 +252,7 @@ private void addTransitRouteToTransitLine(TransitLineInfo transitLineInfo, Route } // final stop cumulativeTravelTime += travelTimes.getLast(); - stops.add(supplyFactory.createTransitRouteStop(routeStopInfos.get(routeStopInfos.size() - 1).getTransitStop(), cumulativeTravelTime)); + stops.add(supplyFactory.createTransitTerminalStop(routeStopInfos.get(routeStopInfos.size() - 1).getTransitStop(), cumulativeTravelTime, false)); // define route and add to transit line final String routeId = String.format("%s_%s_%s", transitLineInfo.getId(), routeDirection.getAbbreviation(), routeType); TransitRoute route = supplyFactory.createTransitRoute(transitLineInfo.getTransitLine(), routeId, routeLinks, stops); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java index db51c5da054..b95d21ab973 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java @@ -44,11 +44,11 @@ class SupplyFactory { private static final double DEFAULT_VEHICLE_ACCESS_TIME = 1.; private static final double DEFAULT_VEHICLE_EGRESS_TIME = 1.; private static final String ID_FORMAT_DEPARTURE = "%s"; - private static final String ID_FORMAT_TRANSIT_LINE = "%s_line"; - private static final String ID_FORMAT_TRANSIT_ROUTE = "%s_route"; + private static final String ID_FORMAT_TRANSIT_LINE = "%s"; + private static final String ID_FORMAT_TRANSIT_ROUTE = "%s"; private static final String ID_FORMAT_TRANSIT_STOP = "%s"; private static final String ID_FORMAT_VEHICLE = "%s"; - private static final String ID_FORMAT_VEHICLE_TYPE = "train_%s"; + private static final String ID_FORMAT_VEHICLE_TYPE = "%s"; private static final String ID_FORMAT_LINK = "%s_%s-%s"; private static final String ID_FORMAT_NODE = "%s"; private final TransitSchedule schedule; @@ -108,9 +108,13 @@ TransitRouteStop createTransitRouteStop(TransitStopFacility transitStopFacility, return transitRouteStop; } - TransitRouteStop createTransitRouteStop(TransitStopFacility transitStopFacility, double cumulativeTravelTime) { - log.debug("Creating TransitRouteStop at TransitStopFacility {} (using builder)", transitStopFacility.getId()); - return sf.createTransitRouteStopBuilder(transitStopFacility).departureOffset(cumulativeTravelTime).build(); + TransitRouteStop createTransitTerminalStop(TransitStopFacility transitStopFacility, double cumulativeTravelTime, boolean origin) { + log.debug("Creating terminal TransitRouteStop at TransitStopFacility {} (using builder)", transitStopFacility.getId()); + if (origin) { + return sf.createTransitRouteStopBuilder(transitStopFacility).departureOffset(cumulativeTravelTime).build(); + } else { + return sf.createTransitRouteStopBuilder(transitStopFacility).arrivalOffset(cumulativeTravelTime).build(); + } } TransitStopFacility createTransitStopFacility(String id, Link link) { diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java index c61dfb81ea6..ffaeaef1808 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java @@ -10,6 +10,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runners.MethodSorters; +import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; @@ -18,6 +19,9 @@ import org.matsim.core.controler.OutputDirectoryHierarchy; import org.matsim.core.network.io.NetworkWriter; import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.pt.transitSchedule.api.Departure; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.pt.transitSchedule.api.TransitScheduleWriter; import org.matsim.testcases.MatsimTestUtils; import org.matsim.vehicles.MatsimVehicleWriter; @@ -25,7 +29,7 @@ import java.nio.file.Paths; import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; /** * Test for supply generation using railsim supply builder. @@ -33,6 +37,7 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class RailsimSupplyBuilderTest { + private static final double TOLERANCE_DELTA = 0.001; private static final String CONFIG_FILE = "config.xml"; private static final String NETWORK_FILE = "transitNetwork.xml"; private static final String SCHEDULE_FILE = "transitSchedule.xml"; @@ -87,6 +92,25 @@ public void testBuild() { assertEquals(6, scenario.getTransitSchedule().getFacilities().size()); assertEquals(2, scenario.getTransitSchedule().getTransitLines().size()); assertEquals(10, scenario.getTransitSchedule().getTransitLines().values().stream().mapToInt(l -> l.getRoutes().size()).sum()); + // line + final String id = "IC"; + var transitLine = scenario.getTransitSchedule().getTransitLines().get(Id.create(id, TransitLine.class)); + assertEquals(id, transitLine.getId().toString()); + assertNull(transitLine.getName()); + assertEquals(6, transitLine.getRoutes().size()); + // route + var transitRoute = transitLine.getRoutes().get(Id.create("IC_F_STATION_TO_STATION", TransitRoute.class)); + assertNull(transitRoute.getDescription()); + assertEquals(2, transitRoute.getDepartures().size()); + assertEquals(2, transitRoute.getStops().size()); + assertEquals(41400., transitRoute.getDepartures().get(Id.create("0", Departure.class)).getDepartureTime(), TOLERANCE_DELTA); + assertEquals(45000., transitRoute.getDepartures().get(Id.create("1", Departure.class)).getDepartureTime(), TOLERANCE_DELTA); + var firstStop = transitRoute.getStops().get(0); + assertTrue(firstStop.getArrivalOffset().isUndefined()); + assertEquals(0., firstStop.getDepartureOffset().seconds(), TOLERANCE_DELTA); + var lastStop = transitRoute.getStops().get(transitRoute.getStops().size() - 1); + assertEquals(2700., lastStop.getArrivalOffset().seconds(), TOLERANCE_DELTA); + assertTrue(lastStop.getDepartureOffset().isUndefined()); // write files String outputDir = utils.getOutputDirectory(); new NetworkWriter(scenario.getNetwork()).write(outputDir + NETWORK_FILE); From 82b346431f61d6c6161e909efcb50a2f7ff9c38b Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 28 Apr 2023 13:58:37 +0200 Subject: [PATCH 009/258] Add NONE as vehicle circuits planning approach - New NoVehicleCircuitsPlanner implementation, with Tests. - The approach uses a simple global counter per VehicleType. - Extends existing tests of DEFAULT approach. --- .../supply/RailsimSupplyBuilder.java | 6 +- .../supply/RailsimSupplyConfigGroup.java | 8 +- .../supply/RunRailsimSupplyBuilder.java | 9 +- .../circuits/NoVehicleCircuitsPlanner.java | 65 +++++++++++++ .../supply/RailsimSupplyBuilderTest.java | 2 + .../DefaultVehicleCircuitsPlannerTest.java | 97 +++++++++++-------- .../NoVehicleCircuitsPlannerTest.java | 96 ++++++++++++++++++ 7 files changed, 234 insertions(+), 49 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java index 67d67142813..7dc0ab764bf 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java @@ -1,6 +1,7 @@ package ch.sbb.matsim.contrib.railsim.prototype.supply; import ch.sbb.matsim.contrib.railsim.prototype.supply.circuits.DefaultVehicleCircuitsPlanner; +import ch.sbb.matsim.contrib.railsim.prototype.supply.circuits.NoVehicleCircuitsPlanner; import ch.sbb.matsim.contrib.railsim.prototype.supply.infrastructure.DefaultInfrastructureRepository; import ch.sbb.matsim.contrib.railsim.prototype.supply.rollingstock.DefaultRollingStockRepository; import org.apache.logging.log4j.LogManager; @@ -48,7 +49,10 @@ public class RailsimSupplyBuilder { * @param scenario a scenario, set the CRS. */ public RailsimSupplyBuilder(Scenario scenario) { - this(scenario, new DefaultInfrastructureRepository(scenario), new DefaultRollingStockRepository(scenario), new DefaultVehicleCircuitsPlanner(scenario)); + this(scenario, new DefaultInfrastructureRepository(scenario), new DefaultRollingStockRepository(scenario), switch (ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class).getCircuitPlanningApproach()) { + case DEFAULT -> new DefaultVehicleCircuitsPlanner(scenario); + case NONE -> new NoVehicleCircuitsPlanner(); + }); } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java index f3ef1e8c482..a27ed5e201b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java @@ -45,9 +45,9 @@ public class RailsimSupplyConfigGroup extends ReflectiveConfigGroup { private static final String VEHICLE_TURNAROUND_TIME = "vehicleTurnaroundTime"; // 5. * 60 // circuit private static final String CIRCUIT_MAX_WAITING_TIME = "circuitMaxWaitingTime"; // 20 * 60 - private static final String CIRCUIT_PLANNING_APPROACH = "circuitPlanningApproach"; // has no effect yet + private static final String CIRCUIT_PLANNING_APPROACH = "circuitPlanningApproach"; // DEFAULT - public enum CircuitPlanningApproach {WITH, WITHOUT} + public enum CircuitPlanningApproach {DEFAULT, NONE} /** * Ctor @@ -80,7 +80,7 @@ public RailsimSupplyConfigGroup() { private double vehicleTurnaroundTime = 5. * 60; // seconds // circuit private double circuitMaxWaitingTime = 20. * 60; // meters per second - private CircuitPlanningApproach circuitPlanningApproach = CircuitPlanningApproach.WITH; // options + private CircuitPlanningApproach circuitPlanningApproach = CircuitPlanningApproach.DEFAULT; // options @Override public Map getComments() { @@ -109,7 +109,7 @@ public Map getComments() { comments.put(VEHICLE_TURNAROUND_TIME, "The time it takes the vehicle to turnaround in station (changing the driver's cab)"); // circuit comments.put(CIRCUIT_MAX_WAITING_TIME, "The maximum waiting time of a vehicle on a stop link for the next circuit. If the next departure is after this waiting time, the vehicle is sent to the " + "depot."); - comments.put(CIRCUIT_PLANNING_APPROACH, "The circuits planning approach: " + Arrays.toString(CircuitPlanningApproach.values()) + ". " + CircuitPlanningApproach.WITH + " Plan vehicle circuits (default). " + CircuitPlanningApproach.WITHOUT + " Omit vehicle circuits and sent a new vehicle from depot to depot for each route (needs a high depot capacity)."); + comments.put(CIRCUIT_PLANNING_APPROACH, "The circuits planning approach: " + Arrays.toString(CircuitPlanningApproach.values()) + ". " + CircuitPlanningApproach.DEFAULT + " Plan simple vehicle circuits (default). " + CircuitPlanningApproach.NONE + " Omit vehicle circuits and sent a new vehicle from depot to depot for each route (needs a high depot capacity)."); return comments; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java index 51df03e5443..ebe2cb7e262 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java @@ -20,14 +20,17 @@ public final class RunRailsimSupplyBuilder { public static void main(String[] args) { - final String outputDir = "contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/"; + // Note! Overwrites test04 example + final String outputDir = "contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test04/"; final double waitingTime = 3 * 60.; createOutputDirectory(outputDir); - - // setup supply builder + // configure var config = ConfigUtils.createConfig(); config.global().setCoordinateSystem("CH1903plus_LV95"); + var railsimConfigGroup = ConfigUtils.addOrGetModule(config, RailsimSupplyConfigGroup.class); + railsimConfigGroup.setCircuitPlanningApproach(RailsimSupplyConfigGroup.CircuitPlanningApproach.DEFAULT); var scenario = ScenarioUtils.loadScenario(config); + // setup supply builder var supply = new RailsimSupplyBuilder(scenario); // first add the stop information diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java new file mode 100644 index 00000000000..6426358bb67 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java @@ -0,0 +1,65 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; +import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleAllocationInfo; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleCircuitsPlanner; +import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Implementation for no circuits + *

+ * Creates a new vehicle for each transit line vehicle allocation. Maintains a global counter of vehicles per type. + * + * @author Merlin Unterfinger + */ +public class NoVehicleCircuitsPlanner implements VehicleCircuitsPlanner { + + private static final HashMap counter = new HashMap<>(); + + private record DummyVehicleAllocationInfo(TransitLineInfo transitLineInfo) implements VehicleAllocationInfo { + + @Override + public LinkedList getDepartures(RouteType routeType, RouteDirection routeDirection) { + if (routeType == RouteType.STATION_TO_STATION) { + return new LinkedList<>(transitLineInfo.getDepartures(routeDirection)); + } + return null; + } + + @Override + public LinkedList getVehicleIds(RouteType routeType, RouteDirection routeDirection) { + if (routeType == RouteType.STATION_TO_STATION) { + final var vehicleTypeInfo = transitLineInfo.getVehicleTypeInfo(); + return IntStream.range(0, transitLineInfo.getDepartures(routeDirection).size()).mapToObj(i -> createVehicleId(vehicleTypeInfo)).collect(Collectors.toCollection(LinkedList::new)); + } + return null; + } + } + + @Override + public Map plan(List transitLineInfos) { + counter.clear(); + return transitLineInfos.stream().collect(Collectors.toMap(Function.identity(), DummyVehicleAllocationInfo::new)); + } + + private static String createVehicleId(VehicleTypeInfo vehicleTypeInfo) { + return String.format("%s_%s", vehicleTypeInfo.getId(), getAndIncreaseCount(vehicleTypeInfo)); + } + + private static int getAndIncreaseCount(VehicleTypeInfo vehicleTypeInfo) { + int count = counter.getOrDefault(vehicleTypeInfo, 0); + counter.put(vehicleTypeInfo, count + 1); + return count; + } + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java index ffaeaef1808..31963df79c8 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java @@ -33,6 +33,8 @@ /** * Test for supply generation using railsim supply builder. + * + * @author Merlin Unterfinger */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class RailsimSupplyBuilderTest { diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java index 9cab124b92f..417a5768bdc 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java @@ -1,43 +1,41 @@ package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyBuilder; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; -import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleCircuitsPlanner; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.matsim.api.core.v01.Scenario; -import org.matsim.core.config.Config; +import org.matsim.api.core.v01.Id; import org.matsim.core.config.ConfigUtils; import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.testcases.MatsimTestUtils; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +/** + * Testing default vehicle circuits planner approach + * + * @author Merlin Unterfinger + */ public class DefaultVehicleCircuitsPlannerTest { - - private Scenario scenario; - - @Rule - public MatsimTestUtils utils = new MatsimTestUtils(); + private RailsimSupplyBuilder supply; @Before public void setUp() { - Config config = ConfigUtils.createConfig(); + // configure + var config = ConfigUtils.createConfig(); config.global().setCoordinateSystem("CH1903plus_LV95"); - scenario = ScenarioUtils.loadScenario(config); - } - - @Test - public void plan() { - var supply = new RailsimSupplyBuilder(scenario); + var railsimConfigGroup = ConfigUtils.addOrGetModule(config, RailsimSupplyConfigGroup.class); + railsimConfigGroup.setCircuitPlanningApproach(RailsimSupplyConfigGroup.CircuitPlanningApproach.DEFAULT); + var scenario = ScenarioUtils.loadScenario(config); + // setup supply builder + supply = new RailsimSupplyBuilder(scenario); // add transit stops supply.addStop("genf", 2499965., 1119074.); supply.addStop("nyon", 2507480., 1137714.); @@ -45,37 +43,54 @@ public void plan() { supply.addStop("lausanne", 2507480., 1137714.); // add transit line: IR double waitingTime = 3 * 60.; - final var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); + var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); ir.addPass("nyon"); ir.addPass("morges"); ir.addStop("lausanne", 55 * 60., waitingTime); Stream.of(0. * 3600, 1 * 3600., 2 * 3600., 10 * 3600., 11 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.FORWARD, departureTime)); Stream.of(0.25 * 3600., 1.25 * 3600., 2.25 * 3600., 11.25 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.REVERSE, departureTime)); // add transit line: S - final var s = supply.addTransitLine("S", "S", "genf", waitingTime); + var s = supply.addTransitLine("S", "S", "genf", waitingTime); s.addStop("nyon", 25 * 60 * 2., waitingTime); s.addStop("morges", 15 * 60 + 2., waitingTime); s.addStop("lausanne", 15 * 60 * 2., waitingTime); Stream.of(0. * 3600, 0.25 * 3600., 0.5 * 3600., 0.75 * 3600., 1 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.FORWARD, departureTime)); Stream.of(0.10 * 3600., 0.35 * 3600., 0.60 * 3600., 0.85 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.REVERSE, departureTime)); - // let supply builder create transit lines + } + + @Test + public void plan() { + // let supply builder create transit lines and plan circuits supply.build(); - // run circuit planer - var lines = new ArrayList(); - lines.add(ir); - lines.add(s); - VehicleCircuitsPlanner vcp = new DefaultVehicleCircuitsPlanner(scenario); - var allocations = vcp.plan(lines); - // check total number of planned allocations - assertEquals(2, allocations.keySet().size()); - // check IR - assertEquals(List.of("IR_1", "IR_2"), allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_DEPOT, RouteDirection.FORWARD)); - assertEquals(List.of("IR_0", "IR_2", "IR_0"), allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_STATION, RouteDirection.FORWARD)); - assertNull(allocations.get(ir).getVehicleIds(RouteType.STATION_TO_DEPOT, RouteDirection.FORWARD)); - assertNull(allocations.get(ir).getVehicleIds(RouteType.STATION_TO_STATION, RouteDirection.FORWARD)); - assertEquals(List.of("IR_1"), allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_DEPOT, RouteDirection.REVERSE)); - assertNull(allocations.get(ir).getVehicleIds(RouteType.DEPOT_TO_STATION, RouteDirection.REVERSE)); - assertEquals(List.of("IR_0", "IR_2", "IR_0"), allocations.get(ir).getVehicleIds(RouteType.STATION_TO_DEPOT, RouteDirection.REVERSE)); - assertNull(allocations.get(ir).getVehicleIds(RouteType.STATION_TO_STATION, RouteDirection.REVERSE)); + // check + var schedule = supply.getScenario().getTransitSchedule(); + // IR + String lineId = "IR"; + assertTrue(checkVehicleIds(List.of("IR_1", "IR_2"), lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_DEPOT)); + assertTrue(checkVehicleIds(List.of("IR_0", "IR_2", "IR_0"), lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_STATION)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.STATION_TO_DEPOT)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.STATION_TO_STATION)); + assertTrue(checkVehicleIds(List.of("IR_1"), lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_DEPOT)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_STATION)); + assertTrue(checkVehicleIds(List.of("IR_0", "IR_2", "IR_0"), lineId, RouteDirection.REVERSE, RouteType.STATION_TO_DEPOT)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.STATION_TO_STATION)); + } + + private boolean checkVehicleIds(List vehicleIds, String lineId, RouteDirection routeDirection, RouteType routeType) { + var line = supply.getScenario().getTransitSchedule().getTransitLines().get(Id.create(lineId, TransitLine.class)); + var route = line.getRoutes().get(createRouteId(lineId, routeDirection, routeType)); + if (route != null) { + var allocatedVehicleIds = route.getDepartures().values().stream().map(d -> d.getVehicleId().toString()).collect(Collectors.toList()); + if (vehicleIds.equals(allocatedVehicleIds)) { + return true; + } + System.out.printf("Got %s instead of %s\n", allocatedVehicleIds, vehicleIds); + } + return route == null && vehicleIds == null; } + + private Id createRouteId(String lineId, RouteDirection routeDirection, RouteType routeType) { + return Id.create(String.format("%s_%s_%s", lineId, routeDirection.getAbbreviation(), routeType.name()), TransitRoute.class); + } + } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java new file mode 100644 index 00000000000..03cfedaa68b --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java @@ -0,0 +1,96 @@ +package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; + +import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyBuilder; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; +import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; +import org.junit.Before; +import org.junit.Test; +import org.matsim.api.core.v01.Id; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertTrue; + +/** + * Testing no vehicle circuits planner approach + * + * @author Merlin Unterfinger + */ +public class NoVehicleCircuitsPlannerTest { + + private RailsimSupplyBuilder supply; + + @Before + public void setUp() { + // configure + var config = ConfigUtils.createConfig(); + config.global().setCoordinateSystem("CH1903plus_LV95"); + var railsimConfigGroup = ConfigUtils.addOrGetModule(config, RailsimSupplyConfigGroup.class); + railsimConfigGroup.setCircuitPlanningApproach(RailsimSupplyConfigGroup.CircuitPlanningApproach.NONE); + var scenario = ScenarioUtils.loadScenario(config); + // setup supply builder + supply = new RailsimSupplyBuilder(scenario); + // add transit stops + supply.addStop("genf", 2499965., 1119074.); + supply.addStop("nyon", 2507480., 1137714.); + supply.addStop("morges", 2527501., 1151542.); + supply.addStop("lausanne", 2507480., 1137714.); + // add transit line: IR + double waitingTime = 3 * 60.; + var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); + ir.addPass("nyon"); + ir.addPass("morges"); + ir.addStop("lausanne", 55 * 60., waitingTime); + Stream.of(0. * 3600, 1 * 3600., 2 * 3600., 10 * 3600., 11 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.FORWARD, departureTime)); + Stream.of(0.25 * 3600., 1.25 * 3600., 2.25 * 3600., 11.25 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.REVERSE, departureTime)); + // add transit line: S + var s = supply.addTransitLine("S", "S", "genf", waitingTime); + s.addStop("nyon", 25 * 60 * 2., waitingTime); + s.addStop("morges", 15 * 60 + 2., waitingTime); + s.addStop("lausanne", 15 * 60 * 2., waitingTime); + Stream.of(0. * 3600, 0.25 * 3600., 0.5 * 3600., 0.75 * 3600., 1 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.FORWARD, departureTime)); + Stream.of(0.10 * 3600., 0.35 * 3600., 0.60 * 3600., 0.85 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.REVERSE, departureTime)); + } + + @Test + public void plan() { + // let supply builder create transit lines and plan circuits + supply.build(); + // check + var schedule = supply.getScenario().getTransitSchedule(); + // IR + String lineId = "IR"; + assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_DEPOT)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_STATION)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.STATION_TO_DEPOT)); + assertTrue(checkVehicleIds(List.of("IR_0", "IR_1", "IR_2", "IR_3", "IR_4"), lineId, RouteDirection.FORWARD, RouteType.STATION_TO_STATION)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_DEPOT)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_STATION)); + assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.STATION_TO_DEPOT)); + assertTrue(checkVehicleIds(List.of("IR_5", "IR_6", "IR_7", "IR_8"), lineId, RouteDirection.REVERSE, RouteType.STATION_TO_STATION)); + } + + private boolean checkVehicleIds(List vehicleIds, String lineId, RouteDirection routeDirection, RouteType routeType) { + var line = supply.getScenario().getTransitSchedule().getTransitLines().get(Id.create(lineId, TransitLine.class)); + var route = line.getRoutes().get(createRouteId(lineId, routeDirection, routeType)); + if (route != null) { + var allocatedVehicleIds = route.getDepartures().values().stream().map(d -> d.getVehicleId().toString()).collect(Collectors.toList()); + if (vehicleIds.equals(allocatedVehicleIds)) { + return true; + } + System.out.printf("Got %s instead of %s\n", allocatedVehicleIds, vehicleIds); + } + return route == null && vehicleIds == null; + } + + private Id createRouteId(String lineId, RouteDirection routeDirection, RouteType routeType) { + return Id.create(String.format("%s_%s_%s", lineId, routeDirection.getAbbreviation(), routeType.name()), TransitRoute.class); + } +} From 85815c9292c0d20ad0b10ad2ad1a935911c3e96f Mon Sep 17 00:00:00 2001 From: Ihab Kaddoura Date: Fri, 28 Apr 2023 15:41:15 +0200 Subject: [PATCH 010/258] Update pom.xml trying to get jitpack running; set required maven version from 3.6.3 down to 3.6.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3ec42c42063..8d6a74f8f71 100644 --- a/pom.xml +++ b/pom.xml @@ -334,7 +334,7 @@ - 3.6.3 + 3.6.1 From a277f6026bab7b4a6b433795f4711fd669256bc2 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 28 Apr 2023 19:42:39 +0200 Subject: [PATCH 011/258] Revert "Update pom.xml" This reverts commit 85815c9292c0d20ad0b10ad2ad1a935911c3e96f. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8d6a74f8f71..3ec42c42063 100644 --- a/pom.xml +++ b/pom.xml @@ -334,7 +334,7 @@ - 3.6.1 + 3.6.3 From a87d19f5e3f3f9e2bd55fe0bdb8e28d3a8147cb3 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 28 Apr 2023 21:18:40 +0200 Subject: [PATCH 012/258] Fix vehicle counter in default circuits planning - Use equality checks to prevent repositories from providing inconsistent variants for the same request. - Add equals and hashCode methods to info classes. --- .../railsim/prototype/supply/DepotInfo.java | 26 ++++++++ .../supply/RailsimSupplyBuilder.java | 65 ++++++++++++++++--- .../prototype/supply/RouteStopInfo.java | 23 +++++++ .../prototype/supply/SectionPartInfo.java | 18 +++++ .../prototype/supply/SectionSegmentInfo.java | 23 +++++++ .../railsim/prototype/supply/StopInfo.java | 15 +++++ .../prototype/supply/TransitLineInfo.java | 19 +++++- .../prototype/supply/VehicleTypeInfo.java | 19 ++++++ 8 files changed, 199 insertions(+), 9 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java index 187a5e95ac3..71b4e25195f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * Depot information @@ -36,6 +37,31 @@ public DepotInfo(String id, Coord coord, double length, double inLength, double this.outLength = outLength; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var depotInfo = (DepotInfo) o; + if (Double.compare(depotInfo.length, length) != 0) return false; + if (Double.compare(depotInfo.inLength, inLength) != 0) return false; + if (Double.compare(depotInfo.outLength, outLength) != 0) return false; + if (capacity != depotInfo.capacity) return false; + if (!Objects.equals(id, depotInfo.id)) return false; + if (!Objects.equals(coord, depotInfo.coord)) return false; + if (!inLinkAttributes.equals(depotInfo.inLinkAttributes)) return false; + if (!depotLinkAttributes.equals(depotInfo.depotLinkAttributes)) return false; + if (!outLinkAttributes.equals(depotInfo.outLinkAttributes)) return false; + if (!Objects.equals(depot, depotInfo.depot)) return false; + if (!Objects.equals(depotIn, depotInfo.depotIn)) return false; + if (!Objects.equals(depotLink, depotInfo.depotLink)) return false; + return Objects.equals(depotOut, depotInfo.depotOut); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + public Map getInLinkAttributes() { return inLinkAttributes; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java index 7dc0ab764bf..36342e31997 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java @@ -40,8 +40,11 @@ public class RailsimSupplyBuilder { private final VehicleCircuitsPlanner vehicleCircuitsPlanner; private final RailsimSupplyConfigGroup railsimSupplyConfigGroup; private final SupplyFactory supplyFactory; - private final Map transitLineInfos = new HashMap<>(); private final Map stopInfos = new HashMap<>(); + private final Map transitLineInfos = new HashMap<>(); + private final Map vehicleTypeInfos = new HashMap<>(); + private final Map depotInfos = new HashMap<>(); + private final Map sectionPartInfos = new HashMap<>(); private final Map>> sectionParts = new HashMap<>(); @@ -80,12 +83,16 @@ public RailsimSupplyBuilder(Scenario scenario, InfrastructureRepository infrastr * @param y the y coordinates of the stop. */ public void addStop(String id, double x, double y) { - // get stop infos from infrastructure provider + // ensure unique stop ids + if (stopInfos.containsKey(id)) { + throw new RuntimeException("Stop already existing for id " + id); + } + // get stop infos from infrastructure repository var stopInfo = infrastructureRepository.getStop(id, x, y); x = stopInfo.getCoord().getX(); y = stopInfo.getCoord().getY(); final double stopLinkLength = stopInfo.getStopLinkLength(); - // create the stop link: (t_IN)<#####>(t_OUT) + // create the stop link var tIn = supplyFactory.createNode(id + "_IN", new Coord(x - stopLinkLength, y)); var tOut = supplyFactory.createNode(id + "_OUT", stopInfo.getCoord()); var stopLink = supplyFactory.createLink(LinkType.STOP, tIn, tOut, stopLinkLength, stopInfo.getLinkAttributes()); @@ -123,12 +130,16 @@ StopInfo getStop(String id) { * @return A transit line information object. */ public TransitLineInfo addTransitLine(String id, String vehicleTypeid, String firstStopId, double waitingTime) { + // ensure unique transit line ids if (transitLineInfos.containsKey(id)) { throw new RuntimeException("Transit line already existing for id " + id); } var firstStopInfo = getStop(firstStopId); - // get vehicle type info from provider + // get vehicle type info from repository and ensure consistency var vehicleTypeInfo = rollingStockRepository.getVehicleType(vehicleTypeid); + if (!isConsistent(vehicleTypeInfo)) { + throw new RuntimeException("Vehicle type from repository is not consistent for id " + vehicleTypeInfo.getId()); + } // create transit line and matsim object var transitLineInfo = new TransitLineInfo(id, new RouteStopInfo(firstStopInfo, waitingTime), vehicleTypeInfo, supplyFactory.createTransitLine(id), this); // store transit line info @@ -136,6 +147,15 @@ public TransitLineInfo addTransitLine(String id, String vehicleTypeid, String fi return transitLineInfo; } + private boolean isConsistent(VehicleTypeInfo received) { + var existing = vehicleTypeInfos.get(received.getId()); + if (existing == null) { + vehicleTypeInfos.put(received.getId(), received); + return true; + } + return received.equals(existing); + } + /** * Build the transit schedule *

@@ -183,8 +203,11 @@ public void build() { * @param stopInfo the stopInfo to add the depot to. */ private void addDepotToStop(StopInfo stopInfo) { - // request depot info from infrastructure provider + // request depot info from infrastructure repository and ensure consistency var depotInfo = infrastructureRepository.getDepot(stopInfo); + if (!isConsistent(depotInfo)) { + throw new RuntimeException("Depot is from repository not consistent for id " + depotInfo.getId()); + } // create nodes log.info("Adding depot to stop " + stopInfo.getId()); var dIn = supplyFactory.createNode(depotInfo.getId() + "_DPT_IN", depotInfo.getCoord()); @@ -204,6 +227,15 @@ private void addDepotToStop(StopInfo stopInfo) { stopInfo.setDepotInfo(depotInfo); } + private boolean isConsistent(DepotInfo received) { + var existing = depotInfos.get(received.getId()); + if (existing == null) { + depotInfos.put(received.getId(), received); + return true; + } + return received.equals(existing); + } + /** * Creates and adds routes for a transit route type to the transit line *

@@ -333,13 +365,16 @@ List> connectStops(StopInfo fromStop, StopInfo toStop) { String id = String.format("%s_%s", fromStop.getId(), toStop.getId()); var sectionPart = sectionParts.get(id); if (sectionPart != null) { - log.info(String.format("Section part already existing, skipping %s", id)); + log.debug("Section part already existing, skipping {}", id); return sectionPart; } - // request section part information from infrastructure provider + // request section part information from infrastructure repository and ensure consistency var sectionPartInfo = infrastructureRepository.getSectionPart(fromStop, toStop); + if (!isConsistent(sectionPartInfo)) { + throw new RuntimeException("Section part from repository is not consistent for id " + createSectionPartInfoId(sectionPartInfo)); + } // create link for each section until last - var nodePrefix = String.format("%s_%s_", sectionPartInfo.getFromStopId(), sectionPartInfo.getToStopId()); + var nodePrefix = String.format("%s_", id); var links = new ArrayList>(); var sectionSegmentInfos = sectionPartInfo.getSectionSegmentInfos(); var currentNode = fromStop.getStopLink().getToNode(); @@ -362,6 +397,20 @@ List> connectStops(StopInfo fromStop, StopInfo toStop) { return links; } + private boolean isConsistent(SectionPartInfo received) { + var id = createSectionPartInfoId(received); + var existing = sectionPartInfos.get(id); + if (existing == null) { + sectionPartInfos.put(id, received); + return true; + } + return received.equals(existing); + } + + private static String createSectionPartInfoId(SectionPartInfo sectionPartInfo) { + return String.format("%s_%s", sectionPartInfo.getFromStopId(), sectionPartInfo.getToStopId()); + } + public Scenario getScenario() { return scenario; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java index c914895fc64..991085de8ef 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java @@ -3,6 +3,8 @@ import org.matsim.api.core.v01.network.Link; import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import java.util.Objects; + /** * Route stop information * @@ -51,6 +53,27 @@ public boolean isStoppingPass() { return waitingTime > 0.; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (RouteStopInfo) o; + if (Double.compare(that.waitingTime, waitingTime) != 0) return false; + if (!Objects.equals(link, that.link)) return false; + return Objects.equals(transitStop, that.transitStop); + } + + @Override + public int hashCode() { + int result; + long temp; + result = link != null ? link.hashCode() : 0; + result = 31 * result + (transitStop != null ? transitStop.hashCode() : 0); + temp = Double.doubleToLongBits(waitingTime); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + public Link getLink() { return link; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java index efd6759253f..ae0c3b18c49 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Section part information @@ -24,6 +25,23 @@ public void addSegment(SectionSegmentInfo sectionSegmentInfo) { sectionSegmentInfos.add(sectionSegmentInfo); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (SectionPartInfo) o; + if (!Objects.equals(fromStopId, that.fromStopId)) return false; + if (!Objects.equals(toStopId, that.toStopId)) return false; + return sectionSegmentInfos.equals(that.sectionSegmentInfos); + } + + @Override + public int hashCode() { + int result = fromStopId != null ? fromStopId.hashCode() : 0; + result = 31 * result + (toStopId != null ? toStopId.hashCode() : 0); + return result; + } + public String getFromStopId() { return fromStopId; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java index f7d7017249c..2d50f281377 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * Section segment information @@ -28,6 +29,28 @@ public void addLinkAttribute(String key, Object value) { linkAttributes.put(key, value); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (SectionSegmentInfo) o; + if (Double.compare(that.length, length) != 0) return false; + if (!Objects.equals(fromCoord, that.fromCoord)) return false; + if (!Objects.equals(toCoord, that.toCoord)) return false; + return linkAttributes.equals(that.linkAttributes); + } + + @Override + public int hashCode() { + int result; + long temp; + result = fromCoord != null ? fromCoord.hashCode() : 0; + result = 31 * result + (toCoord != null ? toCoord.hashCode() : 0); + temp = Double.doubleToLongBits(length); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + public Coord getFromCoord() { return fromCoord; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java index d51e766cda7..4483f9746e4 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * Stop information @@ -45,6 +46,20 @@ boolean hasNoDepot() { return depotInfo == null; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var stopInfo = (StopInfo) o; + // only compare id since uniqueness in managed by builder + return Objects.equals(id, stopInfo.id); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + public String getId() { return id; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java index 083d00cda75..c1a3770b8e9 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.EnumMap; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -23,7 +24,6 @@ public class TransitLineInfo { private static final Logger log = LogManager.getLogger(TransitLineInfo.class); - private static final double NO_STOP_TIME = 0.; private final RailsimSupplyBuilder supplyBuilder; // line @@ -101,6 +101,23 @@ void build() { built = true; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (TransitLineInfo) o; + // only compare id and supply builder instance since uniqueness in managed by builder + if (!Objects.equals(supplyBuilder, that.supplyBuilder)) return false; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + int result = supplyBuilder != null ? supplyBuilder.hashCode() : 0; + result = 31 * result + (id != null ? id.hashCode() : 0); + return result; + } + private void addFirstStop(RouteStopInfo firstRouteStop) { routeStopInfos.get(RouteDirection.FORWARD).add(firstRouteStop); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java index 89165066404..fdb41504cb7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * Vehicle type information @@ -35,6 +36,24 @@ public VehicleTypeInfo(String id, int capacity, double length, double maxVelocit this.turnaroundTime = turnaroundTime; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + var that = (VehicleTypeInfo) o; + if (capacity != that.capacity) return false; + if (Double.compare(that.length, length) != 0) return false; + if (Double.compare(that.maxVelocity, maxVelocity) != 0) return false; + if (Double.compare(that.turnaroundTime, turnaroundTime) != 0) return false; + if (!Objects.equals(id, that.id)) return false; + return attributes.equals(that.attributes); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } + public String getId() { return id; } From a99f308f0cb729c626c3adf081be9092df08ad64 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Tue, 2 May 2023 14:06:35 +0200 Subject: [PATCH 013/258] initial setup for new railsim --- contribs/railsim/README.md | 17 ++++ .../railsim/docs/network-specification.md | 62 ++++++++++++++ contribs/railsim/docs/train-specification.md | 16 ++++ .../matsim/contrib/railsim/RailsimModule.java | 34 ++++++++ .../contrib/railsim/RunRailsimExample.java | 50 ++++++++++++ .../railsim/config/RailsimConfigGroup.java | 38 +++++++++ .../railsim/qsimengine/RailsimQSimEngine.java | 81 +++++++++++++++++++ .../railsim/qsimengine/RailsimQSimModule.java | 45 +++++++++++ 8 files changed, 343 insertions(+) create mode 100644 contribs/railsim/README.md create mode 100644 contribs/railsim/docs/network-specification.md create mode 100644 contribs/railsim/docs/train-specification.md create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java diff --git a/contribs/railsim/README.md b/contribs/railsim/README.md new file mode 100644 index 00000000000..f9ea2efef06 --- /dev/null +++ b/contribs/railsim/README.md @@ -0,0 +1,17 @@ +# railsim + +*railsim* is a QSim-Engine for MATSim, specialized on the simulation of trains. + +Trains behave and interact with links differently than cars: While usually multiple cars can be on one link, +for safity reasons there should usually be only one train on a track per time. +And while a car is typically located on one link only, a long train may occupy space on multiple links. +To capture these differences and offer a realistic simulation of train traffic within MATSim, +the *railsim* contrib provides a custom QSim-Engine to simulate trains. + +## Configuration + +TODO + +## Enabling railsim in MATSim + +TODO, see RunExample.java diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md new file mode 100644 index 00000000000..e831bd8b689 --- /dev/null +++ b/contribs/railsim/docs/network-specification.md @@ -0,0 +1,62 @@ +# Network-Specification for railsim + +## Introduction + +As trains interact differently with links than regular cars, the typical attributes like `capacity`, `lanes` and even +`freespeed` might not be suitable for describing rail infrastructure. + +railsim uses custom link and node attributes to describe the essential parts of the rail infrastructure. +This document specifies these custom attributes. + +railsim supports the microscopic modelling of tracks, where each link represents a single track, and +a mesoscopic level of modelling, where a link may represent multiple tracks. + +## Specification + +### Link Attributes + +#### trainCapacity + +TODO + +#### trainOppositeDirectionLink + +TODO + +### Node Attributes + +TODO + +## Examples + +### Single track with contraflow + +```xml + + + 1 + B_A + + + + + 1 + A_B + + +``` + +### Two tracks, each with a single direction + +TODO + +### Three tracks, with contraflow in the middle track + +TODO + +### Two tracks that intersect each other + +It two tracks cross each other, e.g. like in the form of the letter `X` or a plus `+`, a train driving in one direction +effetively also blocks the intersecting tracks, even if they only share a common node, but not a common link. + +TODO diff --git a/contribs/railsim/docs/train-specification.md b/contribs/railsim/docs/train-specification.md new file mode 100644 index 00000000000..0732fc72374 --- /dev/null +++ b/contribs/railsim/docs/train-specification.md @@ -0,0 +1,16 @@ +# Train-Specification for railsim + +## Introduction + +railsim supports the simulation of specific, train-related behavior, e.g. acceleration based on the total weight of a train. +In order to simulate this detailed behavior, additional attributes must be specified per train, i.e. per vehicle type in MATSim. + +## Specification + +### TransitVehicle Attributes + +TODO, e.g. length and acceleration of a train. + +## Examples + +TODO diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java new file mode 100644 index 00000000000..27a01358868 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java @@ -0,0 +1,34 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim; + +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; + +public class RailsimModule extends AbstractModule { + + @Override + public void install() { + installQSimModule(new RailsimQSimModule()); + ConfigUtils.addOrGetModule(getConfig(), RailsimConfigGroup.class); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java new file mode 100644 index 00000000000..9f9f1280a48 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java @@ -0,0 +1,50 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim; + +import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; +import org.matsim.core.scenario.ScenarioUtils; + +/** + * Example script that shows how to use railsim included in this contrib. + */ +public class RunRailsimExample { + + public static void main(String[] args) { + String configFilename = args[0]; + Config config = ConfigUtils.loadConfig(configFilename); + + Scenario scenario = ScenarioUtils.loadScenario(config); + Controler controler = new Controler(scenario); + + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> { + new RailsimQSimModule().configure(components); + // if you have other extensions that provide QSim components, call their configure-method here + }); + + controler.run(); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java new file mode 100644 index 00000000000..688b1097a81 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -0,0 +1,38 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.config; + +import org.matsim.core.config.ReflectiveConfigGroup; + +public class RailsimConfigGroup extends ReflectiveConfigGroup { + + public final static String GROUP_NAME = "railsim"; + + public RailsimConfigGroup() { + super(GROUP_NAME); + } + + // TODO: add config parameters + // - "railNetworkModes", default "rail" + // - "railVehicleModes", default "rail"(or "train"?) + // - "linkEventsInterval", default "10", the iteration-interval when link-events should be generated by railsim + // - "visualizationInterval", default "10", the iteration-interval when various csv-files should be generated used to visualize the results in Via + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java new file mode 100644 index 00000000000..6ac75026081 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -0,0 +1,81 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.matsim.core.mobsim.qsim.QSim; +import org.matsim.core.mobsim.qsim.pt.TransitQSimEngine; +import org.matsim.core.mobsim.qsim.pt.TransitStopHandlerFactory; +import org.matsim.pt.UmlaufBuilder; + +public class RailsimQSimEngine extends TransitQSimEngine { + + public RailsimQSimEngine(QSim queueSimulation, TransitStopHandlerFactory stopHandlerFactory, UmlaufBuilder umlaufBuilder) { + super(queueSimulation, stopHandlerFactory, umlaufBuilder); + } + + // Get inspiration from SBBTransitQSimEngine on what methods to overwrite + + /* some implementation notes and ideas: + - data structure to store a track-state per link (or multiple track-state if a link depicts multiple parallel tracks) + - track-state can be `free`, `blocked` (only a single train can be in a blocked track), or `reserved` (multiple trains can reserve the same track) + - data structure to store position and other data of trains + - head position, given as meters from fromNode on a link + - current route (ordered list of links) + - length of the train + - current speed of the train + - current acceleration/deceleration of the train + - additional attributes as required, e.g. required stopping distance ("bremsweg") given the current speed + - in each simStep (may be optimized later to a lower interval), the position of each train is updated + - each train tries to block as many links in front of the train to cover the stopping distance + - if not enough links can be blocked, the train must decelerate accordingly + - a train can only accelerate, if the complete train is on links with the higher allowed speed + (e.g. train cannot accelerate if only the engine is on a faster link, but the rest of the train are still on links with lower freespeed) + + For visualization purposes, the following output should be produced: + - CSV containing time-dependent link-attributes depicting the track-state (needs discussion what we output when a link contains multiple tracks) + - optionally include information which trains (vehicleId) blocked or reserved a track? + - CSV containing time-dependent vehicle-attributes: e.g. current acceleration + - CSV containing XYT data to show head and tail of train, maybe even multiple points (e.g. every 25m) to show length of train? + - how often? every 1min, every 5min? --> config? + - additional attributes? e.g. current speed and acceleration? + - linkEnter/linkLeave-Events for the front of the train + instead of XYT (see above), we could think about if we could create linkEnter/linkLeave-Events for each wagon (estimated every 25m of the train), + but this might not look good as Via still interpolates the position independently. + + We will have to think about deadlock prevention at some stage. + Maybe design some test networks for that, e.g. a single track with a loop on one end; if too many trains try to get into the loop, they can't get out. + This is where the `reserved` track-state might come into play. + + + TODO Implementation steps: + 0. RailsimConfigGroup + 1. RailsimQSimEngine extracts all "rail"-links (depending on config) and builds a data structure to store track-states + 2. RailsimQSimEngine handles all "rail"-vehicles (also depending on config) and moves the trains each second + First implementation can be very basic, e.g. constant speed per link according to freespeed, no checks if track is free + 3. Each train blocks the links it currently occupies, plus 1 link in front of it if possible + 4. A train can only move to the next link it that link is blocked by that train + 5. Each train tries to block as many link in front of it along the route as it needs for the stopping distance + 6. Trains accelerate smoothly when entering links with a higher freespeed + 7. Trains decelerate smoothly before entering links with a lower freespeed + 8. Trains decelerate smoothly if they cannot block enough links in front of them + 9. Deadlock Prevention + + */ +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java new file mode 100644 index 00000000000..2b517259965 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java @@ -0,0 +1,45 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import org.matsim.core.mobsim.qsim.components.QSimComponentsConfig; +import org.matsim.core.mobsim.qsim.components.QSimComponentsConfigurator; +import org.matsim.core.mobsim.qsim.pt.TransitEngineModule; + +public class RailsimQSimModule extends AbstractQSimModule implements QSimComponentsConfigurator { + + public static final String COMPONENT_NAME = "Railsim"; + + @Override + public void configure(QSimComponentsConfig components) { + if (components.hasNamedComponent(TransitEngineModule.TRANSIT_ENGINE_NAME)) { + components.removeNamedComponent(TransitEngineModule.TRANSIT_ENGINE_NAME); + } + + components.addNamedComponent(COMPONENT_NAME); + } + + @Override + protected void configureQSim() { + bind(RailsimQSimEngine.class).asEagerSingleton(); + addQSimComponentBinding(COMPONENT_NAME).to(RailsimQSimEngine.class); + } +} From f799356202d71aaae3ea13b4975141b3aeb36fd2 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Tue, 2 May 2023 16:43:57 +0200 Subject: [PATCH 014/258] updates to specs based on prototype code --- contribs/railsim/docs/events-specification.md | 31 +++++++++++++ .../railsim/docs/network-specification.md | 46 +++++++++++++++++-- contribs/railsim/docs/train-specification.md | 8 ++++ 3 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 contribs/railsim/docs/events-specification.md diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md new file mode 100644 index 00000000000..b169eb0d2b5 --- /dev/null +++ b/contribs/railsim/docs/events-specification.md @@ -0,0 +1,31 @@ +# Events-Specification + +railsim introduces additional, custom events. This document describes these event types. + +==Note:== I'm not sure we really need this. I was inspired by the fact that the prototyp +has custom events like `trainPathEntersLinkEvent`, `trainEntersLinkEvent` and `trainLeavesLinkEvent`. +Thus the following event types are currently **to be discussed**. + +==To Discuss:== should all event types start with `railsim`? + +## Event Types + +### railsimLinkStateChangeEvent + +Instead of `trainPathEntersLinkEvent`, we could have a generic `railsimLinkStateChangeEvent` +that could include information about the new state of the link (or even the track of a multi-track link). + +Attributes: + +- `state`: `free`, `reserved`, or `blocked` +- `vehicleId`: if `state=reserved|blocked`, the id of the vehicle blocking or reserving this link +- `track`: a number (0-based or 1-based?) if the link has multiple tracks + +### railsimTrainLeavesLinkEvent + +Similar to the existing `trainLeavesLinkEvent`. +One could argue that setting the link state to `free` would imply the same. I (mr) would still +say it makes sense to have it separate, because depending on the implementation, a link could +remain blocked for a longer time even if the train has already passed. + + diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index e831bd8b689..1b289a25e9e 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -15,17 +15,57 @@ a mesoscopic level of modelling, where a link may represent multiple tracks. ### Link Attributes -#### trainCapacity +#### grade TODO +#### trainCapacity + +The number of trains that can be on this link at the same time. +If the attribute is not provided, a default of 1 is used. + #### trainOppositeDirectionLink +The id of a link leading in the opposite direction of this link. + +MATSim uses uni-directional links. While on a road, cars might usually be able to pass each other +even on small roads by going very slow and near the edge, but trains cannot. +In order to correctly simulate the simulation where a train blocks a bi-directional track + +#### maxSpeed + TODO +#### minimumTime + +The minimum time ("minimum train headway time") for the switch at the end of the link (toNode). +If no link attribute is provided, a default of 0 is used. + +#### vehicle type + +The vehicle-specific freespeed on this link. +Please note that the actual vehicle-type must be used as attribute name, see example. + +==Should there be a prefix, like `railsimspeed_`? we might want to add more vehicle-dependent attributes later.== + +Example: +```xml + + + 44.444 + 50.0 + + + +``` + +==Note:== The class `RailsimUtils` has additional getters, e.g. to get the freespeed depending on transit line and route. +Do we keep these? If yes, we should document these as well, and a prefix would make even more sense in this case. + + ### Node Attributes -TODO +Currently none. ## Examples @@ -56,7 +96,7 @@ TODO ### Two tracks that intersect each other -It two tracks cross each other, e.g. like in the form of the letter `X` or a plus `+`, a train driving in one direction +If two tracks cross each other, e.g. like in the form of the letter `X` or a plus `+`, a train driving in one direction effetively also blocks the intersecting tracks, even if they only share a common node, but not a common link. TODO diff --git a/contribs/railsim/docs/train-specification.md b/contribs/railsim/docs/train-specification.md index 0732fc72374..4bb63177413 100644 --- a/contribs/railsim/docs/train-specification.md +++ b/contribs/railsim/docs/train-specification.md @@ -11,6 +11,14 @@ In order to simulate this detailed behavior, additional attributes must be speci TODO, e.g. length and acceleration of a train. +#### maxAcceleration + +The vehicle-specific acceleration. Unit: meters per square-seconds \[m/s²] + +#### maxDeceleration + +The vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] + ## Examples TODO From 861daf731da217d896097a7f8484d651d63127c4 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Thu, 4 May 2023 13:51:13 +0200 Subject: [PATCH 015/258] Problem description of deadlock avoidance --- contribs/railsim/docs/deadlock-avoidance.md | 79 +++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 contribs/railsim/docs/deadlock-avoidance.md diff --git a/contribs/railsim/docs/deadlock-avoidance.md b/contribs/railsim/docs/deadlock-avoidance.md new file mode 100644 index 00000000000..7be40dbd87b --- /dev/null +++ b/contribs/railsim/docs/deadlock-avoidance.md @@ -0,0 +1,79 @@ +# Deadlock avoidance + +Deadlocks can occur on two scales in the mesoscopic approach of railsim: + +- Locally within a route section with constant capacity: *Deadlock in a route section with bidirectionally shared + constant capacity.* +- Globally due to conflicting route selections in the network: *Deadlock due to sequence of multiple route sections with + different bidirectionally shared capacities (bottleneck).* + +## Local case + +The local case seems easier to solve. A possible approach is described below: + +Before a train travels on a section with constant capacity (successive links with constant capacity, where the speed +limit may vary, hereafter called segment), it must check that not all tracks are already occupied from the opposite +direction. If this is the case, the train must wait in front of the segment before entering. If at least one track from +the opposite direction is free, then the train must check whether tracks in its direction are already in use. If this is +the case, it will be checked that the incoming train is not slowed down by the preceding train if there is sufficient +capacity, so that the faster train can overtake the slower train. If the incoming train is slower than the previous +train anyway, it should enter the same track to keep as much capacity as possible free for the opposite direction. + +The tracks of a segment could be represented as an array of free-again times (maximum of the exit times of the trains on +a track) or zero if the track is free. When a train enters a track it overwrites the free-again time, if its exit time +is after the exit time of the preceding train. + +## Global case + +The global case is more complex to solve, therefore only the problem is described below and not yet a solution approach +provided. The description is based on the segments defined in the local case, even though they could possibly be omitted +for this case, depending on the chosen solution approach. + +A train on its route reserves segments ahead as far as possible (see *full path reservation*) or alternatively until the +next station with the assumption that there are enough tracks available in the station to pass each other (see *station +to station path reservation*). +If two reserved paths running in opposite directions meet in a segment and the maximum capacity in the segment is used, +then both trains should travel as far as it is still possible to pass each other. So they have to stop before the +bottleneck, where the capacity still allows for a crossing. The train that arrives first at the bottleneck continues its +path, while the other train remains stationary until it no longer sees the opposite train in its reserved path. Then +the waiting train continues its path into the bottleneck. + +This raises a few questions: + +- What happens when several (not just two) reserved paths overlap? No matter how many trains are oncoming, the segment + must allow **one** free track in the opposite direction. +- In situations with multiple overlaps, how do we find the positions where the trains should wait? Last places on the + path where the equivalent capacity is at least 1? +- How do we solve the problem computationally efficiently? + +### Full path reservation + +In case of reservation of the entire route of a train line, i.e. from the origin station to the destination station of +the route, deadlocks are no longer possible on the route. If we have vehicle circuits where a train is waiting at the +destination station for the next departure (and thus blocks a track), again a deadlock would be possible when another +train tries to enter the destination station and no track is available. + +==To Discuss:== this case is probably rather rare and negligible for the time being? If we model the stations as +segments too, then this case should be automatically solved? + +### Station to station path reservation + +In the case where the trains only reserve the path to the next station, a deadlock is again possible when entering the +station. It is not guaranteed that the train can enter the station, and a train in the station (travelling in the +opposite direction), could be waiting for the incoming train to free its track. + +==To Discuss:== do we want to see this deadlock or should the simulation should never block in general? + +If the simulation never blocks, a lack of capacity in a station should be visible in the delays of the trains. It is +probably not easy to find the initial cause of the delay, as a delay potentially propagates and affects other trains. +Additional events may be needed to signal and document a train's decision to wait at a particular point and thus deviate +from the timetable. This allows to detect the origins and the course of the delays in an event analysis after the +simulation. + +## References + +The deadlock problem has already been solved in the Flatland project and could be used as a starting point or serve as +inspiration: + +- [flatland_solver_policy/policy/heuristic_policy/shortest_path_deadlock_avoidance_policy/deadlock_avoidance_policy.py](https://github.com/aiAdrian/flatland_solver_policy/blob/main/policy/heuristic_policy/shortest_path_deadlock_avoidance_policy/deadlock_avoidance_policy.py) +- [flatland/flatland/contrib/utils/deadlock_checker.py](https://gitlab.aicrowd.com/flatland/flatland/-/blob/master/flatland/contrib/utils/deadlock_checker.py) From fd521f1450ced6fc7dfab5516ceb8a7140cb6cce Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Thu, 4 May 2023 15:56:49 +0200 Subject: [PATCH 016/258] Feedback on discussion points in events, network and train specifications --- contribs/railsim/docs/events-specification.md | 14 +++-- .../railsim/docs/network-specification.md | 59 ++++++++++++------- contribs/railsim/docs/train-specification.md | 29 +++++++-- .../railsim/qsimengine/RailsimQSimEngine.java | 4 +- 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index b169eb0d2b5..2c9214dc45b 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -2,12 +2,16 @@ railsim introduces additional, custom events. This document describes these event types. -==Note:== I'm not sure we really need this. I was inspired by the fact that the prototyp +==Note:== I'm not sure we really need this. I was inspired by the fact that the prototype has custom events like `trainPathEntersLinkEvent`, `trainEntersLinkEvent` and `trainLeavesLinkEvent`. Thus the following event types are currently **to be discussed**. ==To Discuss:== should all event types start with `railsim`? +(ik, mu): We do not insist on an additional trainPathEntersLink events. The combination of `vehicleEntersLinkEvent` plus +`TrainLeavesLinkEvent` and `railsimLinkStateChangeEvent` should be fine. +Cosmetic suggestion: A `trainEntersLinkEvent` instead of a default MATSim `vehicleEntersLinkEvent` would be helpful. + ## Event Types ### railsimLinkStateChangeEvent @@ -19,13 +23,11 @@ Attributes: - `state`: `free`, `reserved`, or `blocked` - `vehicleId`: if `state=reserved|blocked`, the id of the vehicle blocking or reserving this link -- `track`: a number (0-based or 1-based?) if the link has multiple tracks +- ` `: a number (0-based or 1-based?) if the link has multiple tracks ### railsimTrainLeavesLinkEvent Similar to the existing `trainLeavesLinkEvent`. One could argue that setting the link state to `free` would imply the same. I (mr) would still -say it makes sense to have it separate, because depending on the implementation, a link could -remain blocked for a longer time even if the train has already passed. - - +say it makes sense to have it separate, because depending on the implementation, a link could +remain blocked for a longer time even if the train has already passed (e.g. minimum headway). diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index 1b289a25e9e..d3c5c9014d4 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -30,7 +30,7 @@ The id of a link leading in the opposite direction of this link. MATSim uses uni-directional links. While on a road, cars might usually be able to pass each other even on small roads by going very slow and near the edge, but trains cannot. -In order to correctly simulate the simulation where a train blocks a bi-directional track +In order to correctly simulate the simulation where a train blocks a bidirectional track #### maxSpeed @@ -49,54 +49,71 @@ Please note that the actual vehicle-type must be used as attribute name, see exa ==Should there be a prefix, like `railsimspeed_`? we might want to add more vehicle-dependent attributes later.== Example: + ```xml - - - 44.444 - 50.0 - + + + + 44.444 + 50.0 + ``` -==Note:== The class `RailsimUtils` has additional getters, e.g. to get the freespeed depending on transit line and route. +==Note:== The class `RailsimUtils` has additional getters, e.g. to get the freespeed depending on transit line and +route. Do we keep these? If yes, we should document these as well, and a prefix would make even more sense in this case. +(ik, mu): We don't think we have to keep this feature, speed limits per vehicle type should be sufficient. ### Node Attributes Currently none. +(ik, mu): We assume that blocked nodes are somehow identified by the currently blocked links? Otherwise, we need node +states to avoid collisions. Do we really want that? + ## Examples ### Single track with contraflow ```xml - - - 1 - B_A - - - - - 1 - A_B - - + + + + + 1 + B_A + + + + + 1 + A_B + + + ``` +(ik, mu): General remark: If we use a prefix for attributes, should we just go with `railsim` everywhere. + ### Two tracks, each with a single direction TODO ### Three tracks, with contraflow in the middle track -TODO +(ik, mu): Default case, all tracks are open for both directions. For more complex track layout we would tend to use the +microscopic modelling approach. ### Two tracks that intersect each other If two tracks cross each other, e.g. like in the form of the letter `X` or a plus `+`, a train driving in one direction -effetively also blocks the intersecting tracks, even if they only share a common node, but not a common link. +effectively also blocks the intersecting tracks, even if they only share a common node, but not a common link. TODO + +(ik, mu): See discussion about node states. diff --git a/contribs/railsim/docs/train-specification.md b/contribs/railsim/docs/train-specification.md index 4bb63177413..7a2dac70d1b 100644 --- a/contribs/railsim/docs/train-specification.md +++ b/contribs/railsim/docs/train-specification.md @@ -2,8 +2,9 @@ ## Introduction -railsim supports the simulation of specific, train-related behavior, e.g. acceleration based on the total weight of a train. -In order to simulate this detailed behavior, additional attributes must be specified per train, i.e. per vehicle type in MATSim. +railsim supports the simulation of specific, train-related behavior, e.g. acceleration based on the total weight of a +train. In order to simulate this detailed behavior, additional attributes must be specified per train, i.e. per vehicle +type in MATSim. ## Specification @@ -15,10 +16,30 @@ TODO, e.g. length and acceleration of a train. The vehicle-specific acceleration. Unit: meters per square-seconds \[m/s²] +(ik, mu): Suggestion, maybe just `acceleration`? Do we always accelerate at maximum speed? + #### maxDeceleration -The vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] +The vehicle-specific maximum deceleration for emergency braking ("bremsweg"). Unit: meters per square-seconds \[m/s²] + +#### deceleration + +The typical vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] ## Examples -TODO +```xml + + + + 0.5 + 0.5 + 0.7 + + + + + + + +``` diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index 6ac75026081..244ec0139bd 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -41,10 +41,10 @@ public RailsimQSimEngine(QSim queueSimulation, TransitStopHandlerFactory stopHan - length of the train - current speed of the train - current acceleration/deceleration of the train - - additional attributes as required, e.g. required stopping distance ("bremsweg") given the current speed + - additional attributes as required, e.g. required stopping distance ("bremsweg", emergency braking distance, maxDeceleration) given the current speed - in each simStep (may be optimized later to a lower interval), the position of each train is updated - each train tries to block as many links in front of the train to cover the stopping distance - - if not enough links can be blocked, the train must decelerate accordingly + - if not enough links can be blocked, the train must decelerate accordingly (normal braking distance, deceleration) - a train can only accelerate, if the complete train is on links with the higher allowed speed (e.g. train cannot accelerate if only the engine is on a faster link, but the rest of the train are still on links with lower freespeed) From 82141382d57f9489f85afe50685edf0aadc524c6 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Thu, 4 May 2023 16:02:03 +0200 Subject: [PATCH 017/258] Typo --- contribs/railsim/docs/events-specification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index 2c9214dc45b..8391d74f45f 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -23,11 +23,11 @@ Attributes: - `state`: `free`, `reserved`, or `blocked` - `vehicleId`: if `state=reserved|blocked`, the id of the vehicle blocking or reserving this link -- ` `: a number (0-based or 1-based?) if the link has multiple tracks +- `track`: a number (0-based or 1-based?) if the link has multiple tracks ### railsimTrainLeavesLinkEvent Similar to the existing `trainLeavesLinkEvent`. One could argue that setting the link state to `free` would imply the same. I (mr) would still say it makes sense to have it separate, because depending on the implementation, a link could -remain blocked for a longer time even if the train has already passed (e.g. minimum headway). +remain blocked for a longer time even if the train has already passed (e.g. minimum headway time). From 1011f7ae7e8aa25a6ab43db5d78b3ab0520e27d1 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 4 May 2023 17:23:57 +0200 Subject: [PATCH 018/258] Created some classes and structure for railsim, made slight modifications to core to have more flexibility in pt transit engine --- .../matsim/contrib/railsim/RailsimUtils.java | 135 +++++++++++++++++ .../contrib/railsim/RunRailsimExample.java | 33 +++-- .../railsim/config/RailsimConfigGroup.java | 15 +- .../contrib/railsim/qsimengine/RailLink.java | 7 + .../qsimengine/RailsimDriverAgentFactory.java | 41 ++++++ .../railsim/qsimengine/RailsimEngine.java | 136 ++++++++++++++++++ .../railsim/qsimengine/RailsimQSimEngine.java | 115 ++++++++++++++- .../railsim/qsimengine/RailsimQSimModule.java | 10 +- .../railsim/qsimengine/TrackState.java | 7 + .../railsim/qsimengine/TrainState.java | 79 ++++++++++ .../railsim/qsimengine/RailsimEngineTest.java | 7 + .../prototype/RunRailsimTest/test0/config.xml | 14 +- .../qsim/pt/SBBTransitDriverAgentFactory.java | 12 +- .../mobsim/qsim/pt/SBBTransitQSimEngine.java | 14 +- .../qsim/pt/SBBTransitQSimEngineTest.java | 4 +- .../pt/DefaultTransitDriverAgentFactory.java | 11 +- .../qsim/pt/TransitDriverAgentFactory.java | 3 +- .../mobsim/qsim/pt/TransitEngineModule.java | 13 ++ .../mobsim/qsim/pt/TransitQSimEngine.java | 25 ++-- 19 files changed, 612 insertions(+), 69 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java new file mode 100644 index 00000000000..96b5aee9424 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -0,0 +1,135 @@ +package ch.sbb.matsim.contrib.railsim; + +import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; + +/** + * Utility class for working with Railsim and its specific attributes. + * + * @author Ihab Kaddoura + * @author rakow + */ +public final class RailsimUtils { + + // link + public static final String LINK_ATTRIBUTE_GRADE = "grade"; + public static final String LINK_ATTRIBUTE_OPPOSITE_DIRECTION = "trainOppositeDirectionLink"; + public static final String LINK_ATTRIBUTE_CAPACITY = "trainCapacity"; + public static final String LINK_ATTRIBUTE_MAX_SPEED = "maxSpeed"; + public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "minimumTime"; + // vehicle + public static final String VEHICLE_ATTRIBUTE_MAX_DECELERATION = "maxDeceleration"; + public static final String VEHICLE_ATTRIBUTE_MAX_ACCELERATION = "maxAcceleration"; + + private RailsimUtils() { + } + + // TODO: Setter methods + + /** + * @return the train capacity for this link, if no link attribute is provided the default is 1. + */ + public static int getTrainCapacity(Link link) { + int trainCapacity = 1; + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY) != null) { + trainCapacity = (Integer) link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY); + } + return trainCapacity; + } + + /** + * @return the minimum time for the switch at the end of the link (toNode); if no link attribute is provided the default is 0. + */ + public static double getMinimumTrainHeadwayTime(Link link) { + double minimumTime = 0.; + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME) != null) { + minimumTime = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME); + } + return minimumTime; + } + + /** + * @return the default deceleration time or the vehicle-specific value + */ + public static double getTrainDeceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { + double deceleration = railsimConfigGroup.getDecelerationGlobalDefault(); + if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION) != null) { + deceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION); + } + return deceleration; + } + + /** + * @return the default acceleration time or the vehicle-specific value + */ + public static double getTrainAcceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { + double acceleration = railsimConfigGroup.getAccelerationGlobalDefault(); + if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION) != null) { + acceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION); + } + return acceleration; + } + + /** + * @return the default acceleration time or the vehicle-specific value + */ + public static double getGrade(Link link, RailsimConfigGroup railsimConfigGroup) { + double grade = railsimConfigGroup.getGradeGlobalDefault(); + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE) != null) { + grade = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE); + } + return grade; + } + + /** + * @return the vehicle-specific freespeed or 0 if there is no vehicle-specific freespeed provided in the link attributes + */ + public static double getLinkFreespeedForVehicleType(Id type, Link link) { + Object attribute = link.getAttributes().getAttribute(type.toString()); + if (attribute == null) { + return 0.; + } else { + return (double) attribute; + } + } + + /** + * @return the line-specific freespeed or 0 if there is no line-specific freespeed provided in the link attributes + */ + public static double getLinkFreespeedForTransitLine(Id line, Link link) { + Object attribute = link.getAttributes().getAttribute(line.toString()); + if (attribute == null) { + return 0.; + } else { + return (double) attribute; + } + } + + /** + * @return the line- and route-specific freespeed or 0 if there is no line- and route-specific freespeed provided in the link attributes + */ + public static double getLinkFreespeedForTransitLineAndTransitRoute(Id line, Id route, Link link) { + Object attribute = link.getAttributes().getAttribute(line.toString() + "+++" + route.toString()); + if (attribute == null) { + return 0.; + } else { + return (double) attribute; + } + } + + public static Id getOppositeDirectionLink(Link link) { + if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION) == null) { + return null; + } else { + String oppositeLink = (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION); + return Id.createLinkId(oppositeLink); + } + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java index 9f9f1280a48..962a96de921 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java @@ -20,10 +20,12 @@ package ch.sbb.matsim.contrib.railsim; import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; +import ch.sbb.matsim.routing.pt.raptor.SwissRailRaptorModule; import org.matsim.api.core.v01.Scenario; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.Controler; +import org.matsim.core.controler.OutputDirectoryHierarchy; import org.matsim.core.scenario.ScenarioUtils; /** @@ -31,20 +33,27 @@ */ public class RunRailsimExample { - public static void main(String[] args) { - String configFilename = args[0]; - Config config = ConfigUtils.loadConfig(configFilename); + public static void main(String[] args) { - Scenario scenario = ScenarioUtils.loadScenario(config); - Controler controler = new Controler(scenario); + if (args.length == 0) { + System.err.println("Path to config is required as first argument."); + System.exit(2); + } - controler.addOverridingModule(new RailsimModule()); - controler.configureQSimComponents(components -> { - new RailsimQSimModule().configure(components); - // if you have other extensions that provide QSim components, call their configure-method here - }); + String configFilename = args[0]; + Config config = ConfigUtils.loadConfig(configFilename); + config.controler().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists); - controler.run(); - } + Scenario scenario = ScenarioUtils.loadScenario(config); + Controler controler = new Controler(scenario); + + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> { + new RailsimQSimModule().configure(components); + // if you have other extensions that provide QSim components, call their configure-method here + }); + + controler.run(); + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index 688b1097a81..4c33a41f866 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -21,14 +21,27 @@ import org.matsim.core.config.ReflectiveConfigGroup; +import java.util.Set; + +/** + * Config of the Railsim contrib. + */ public class RailsimConfigGroup extends ReflectiveConfigGroup { - public final static String GROUP_NAME = "railsim"; + public static final String GROUP_NAME = "railsim"; + + @Parameter + @Comment("Comma separated set of modes that are handled by the rail simulation. Defaults to 'rail'.") + public String railNetworkModes = "rail"; public RailsimConfigGroup() { super(GROUP_NAME); } + public Set getRailNetworkModes() { + return Set.of(railNetworkModes.split(",")); + } + // TODO: add config parameters // - "railNetworkModes", default "rail" // - "railVehicleModes", default "rail"(or "train"?) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java new file mode 100644 index 00000000000..876f7bea310 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -0,0 +1,7 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +class RailLink { + + // TODO: store needed information, lookup from link attributes is expensive + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java new file mode 100644 index 00000000000..8edb169f632 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java @@ -0,0 +1,41 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.mobsim.qsim.InternalInterface; +import org.matsim.core.mobsim.qsim.pt.AbstractTransitDriverAgent; +import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentFactory; +import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentImpl; +import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; +import org.matsim.pt.Umlauf; + +import javax.inject.Inject; +import java.util.Set; + +/** + * Factory to create specific drivers for the rail engine. + */ +public class RailsimDriverAgentFactory implements TransitDriverAgentFactory { + + private final Set modes; + + @Inject + public RailsimDriverAgentFactory(Config config) { + this.modes = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class).getRailNetworkModes(); + } + + @Override + public AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf, InternalInterface internalInterface, TransitStopAgentTracker transitStopAgentTracker) { + + String mode = umlauf.getUmlaufStuecke().get(0).getRoute().getTransportMode(); + + if (this.modes.contains(mode)) { + // TODO: Can be a specific driver agent later + return new TransitDriverAgentImpl(umlauf, mode, transitStopAgentTracker, internalInterface); + } + + return new TransitDriverAgentImpl(umlauf, TransportMode.car, transitStopAgentTracker, internalInterface); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java new file mode 100644 index 00000000000..98ca23abb0f --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -0,0 +1,136 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import org.matsim.core.mobsim.framework.Steppable; +import org.matsim.core.mobsim.qsim.interfaces.MobsimVehicle; +import org.matsim.core.population.routes.NetworkRoute; + +import java.util.*; + +/** + * Engine to simulate train movement. + */ +final class RailsimEngine implements Steppable { + + private static final Logger log = LogManager.getLogger(RailsimEngine.class); + + /** + * Rail links + */ + private final Map, Link> links; + + private final List activeTrains = new ArrayList<>(); + + private final Queue updateQueue = new PriorityQueue<>(); + + public RailsimEngine(Map, Link> links) { + this.links = links; + } + + @Override + public void doSimStep(double time) { + + UpdateEvent update = updateQueue.peek(); + + // Update loop over all required state updates + while (update != null && update.plannedTime <= time) { + updateQueue.poll(); + updateState(time, update); + update = updateQueue.peek(); + } + } + + /** + * Handle the departure of a train. + */ + public boolean handleDeparture(double now, MobsimDriverAgent agent, Id linkId, NetworkRoute route) { + + log.info("Train {} is departing", agent.getVehicle()); + + + TrainState state = new TrainState(agent, now, linkId, route); + + activeTrains.add(state); + updateQueue.add(new UpdateEvent(state, UpdateType.NEXT_HEAD_LINK)); + + return true; + } + + /** + * Update the current state of all trains, even if no update would be needed. + */ + public void updateAllStates(double time) { + updateQueue.clear(); + for (TrainState train : activeTrains) { + // TODO: needs different arguments +// updateState(time, train); + } + } + + private void updateState(double time, UpdateEvent event) { + + TrainState state = event.state; + MobsimVehicle vehicle = state.driver.getVehicle(); + + state.driver.chooseNextLinkId(); + +// state.driver.getVehicle(). + + + // TODO: fixed update very one second + event.plannedTime += 1; + + updateQueue.add(event); + } + + /** + * Helper class to queue when a state should be updated. + */ + private final static class UpdateEvent implements Comparable { + + final TrainState state; + double plannedTime; + UpdateType type; + + public UpdateEvent(TrainState state, UpdateType type) { + this.state = state; + this.plannedTime = state.timestamp; + this.type = type; + } + + @Override + public int compareTo(UpdateEvent o) { + int compare = Double.compare(plannedTime, o.plannedTime); + + if (compare == 0) + return state.driver.getId().compareTo(o.state.driver.getId()); + + return compare; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UpdateEvent that = (UpdateEvent) o; + return Objects.equals(state, that.state); + } + + @Override + public int hashCode() { + return Objects.hash(state); + } + } + + /** + * TODO: maybe this enum won't be necessary later + */ + private enum UpdateType { + NEXT_HEAD_LINK + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index 6ac75026081..896d052c875 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -19,19 +19,122 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.api.core.v01.population.Route; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.mobsim.framework.DriverAgent; +import org.matsim.core.mobsim.framework.MobsimAgent; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import org.matsim.core.mobsim.framework.PlanAgent; +import org.matsim.core.mobsim.qsim.InternalInterface; import org.matsim.core.mobsim.qsim.QSim; -import org.matsim.core.mobsim.qsim.pt.TransitQSimEngine; -import org.matsim.core.mobsim.qsim.pt.TransitStopHandlerFactory; -import org.matsim.pt.UmlaufBuilder; +import org.matsim.core.mobsim.qsim.interfaces.DepartureHandler; +import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine; +import org.matsim.core.mobsim.qsim.interfaces.NetsimLink; +import org.matsim.core.mobsim.qsim.interfaces.NetsimNetwork; +import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; +import org.matsim.core.mobsim.qsim.qnetsimengine.QLinkI; +import org.matsim.core.population.routes.NetworkRoute; -public class RailsimQSimEngine extends TransitQSimEngine { +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.Set; - public RailsimQSimEngine(QSim queueSimulation, TransitStopHandlerFactory stopHandlerFactory, UmlaufBuilder umlaufBuilder) { - super(queueSimulation, stopHandlerFactory, umlaufBuilder); +/** + * QSim Engine to integrate microscopically simulated train movement. + */ +public class RailsimQSimEngine implements DepartureHandler, MobsimEngine { + + private final QSim qsim; + private final RailsimConfigGroup config; + private final Set modes; + private final TransitStopAgentTracker agentTracker; + private InternalInterface internalInterface; + + private RailsimEngine engine; + + @Inject + public RailsimQSimEngine(QSim qsim, TransitStopAgentTracker agentTracker) { + this.qsim = qsim; + this.config = ConfigUtils.addOrGetModule(qsim.getScenario().getConfig(), RailsimConfigGroup.class); + this.modes = config.getRailNetworkModes(); + this.agentTracker = agentTracker; + } + + @Override + public void setInternalInterface(InternalInterface internalInterface) { + this.internalInterface = internalInterface; + } + + @Override + public void onPrepareSim() { + + Map, Link> links = new IdMap<>(Link.class); + + for (Link link : qsim.getScenario().getNetwork().getLinks().values()) { + if (link.getAllowedModes().stream().anyMatch(modes::contains)) + links.put(link.getId(), link); + } + + engine = new RailsimEngine(links); + } + + @Override + public void afterSim() { + + } + + @Override + public void doSimStep(double time) { + engine.doSimStep(time); + } + + @Override + public boolean handleDeparture(double now, MobsimAgent agent, Id linkId) { + + if (!modes.contains(agent.getMode())) + return false; + + NetsimLink link = qsim.getNetsimNetwork().getNetsimLink(linkId); + + // Lots of implicit type checking here to get the required information from the agent + if (!(agent instanceof MobsimDriverAgent driver)) { + throw new IllegalStateException("Departing agent " + agent.getId() + " is not a DriverAgent"); + } + if (!(agent instanceof PlanAgent plan)) { + throw new IllegalStateException("Agent " + agent + " is not of type PlanAgent and therefore incompatible."); + } + PlanElement el = plan.getCurrentPlanElement(); + if (!(el instanceof Leg leg)) { + throw new IllegalStateException("Plan element of agent " + agent + " is not a leg with a route."); + } + Route route = leg.getRoute(); + if (!(route instanceof NetworkRoute networkRoute)) { + throw new IllegalStateException("A network route is required for agent " + agent + "."); + } + + // Vehicles were inserted into the qsim as parked + // Remove them as soon as we depart + if (link instanceof QLinkI qLink) { + qLink.unregisterAdditionalAgentOnLink(agent.getId()); + qLink.removeParkedVehicle(driver.getVehicle().getId()); + } + + return engine.handleDeparture(now, driver, linkId, networkRoute); } // Get inspiration from SBBTransitQSimEngine on what methods to overwrite + // TODO: Questions + // Is the rail engine a passenger engine in itself ? + // Should maybe be lower level like qsim ? + /* some implementation notes and ideas: - data structure to store a track-state per link (or multiple track-state if a link depicts multiple parallel tracks) - track-state can be `free`, `blocked` (only a single train can be in a blocked track), or `reserved` (multiple trains can reserve the same track) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java index 2b517259965..79e16fd68ac 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java @@ -19,9 +19,12 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import com.google.inject.multibindings.OptionalBinder; import org.matsim.core.mobsim.qsim.AbstractQSimModule; import org.matsim.core.mobsim.qsim.components.QSimComponentsConfig; import org.matsim.core.mobsim.qsim.components.QSimComponentsConfigurator; +import org.matsim.core.mobsim.qsim.pt.DefaultTransitDriverAgentFactory; +import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentFactory; import org.matsim.core.mobsim.qsim.pt.TransitEngineModule; public class RailsimQSimModule extends AbstractQSimModule implements QSimComponentsConfigurator { @@ -30,10 +33,6 @@ public class RailsimQSimModule extends AbstractQSimModule implements QSimCompone @Override public void configure(QSimComponentsConfig components) { - if (components.hasNamedComponent(TransitEngineModule.TRANSIT_ENGINE_NAME)) { - components.removeNamedComponent(TransitEngineModule.TRANSIT_ENGINE_NAME); - } - components.addNamedComponent(COMPONENT_NAME); } @@ -41,5 +40,8 @@ public void configure(QSimComponentsConfig components) { protected void configureQSim() { bind(RailsimQSimEngine.class).asEagerSingleton(); addQSimComponentBinding(COMPONENT_NAME).to(RailsimQSimEngine.class); + + OptionalBinder.newOptionalBinder(binder(), TransitDriverAgentFactory.class) + .setBinding().to( RailsimDriverAgentFactory.class ); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java new file mode 100644 index 00000000000..1c02371c06a --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java @@ -0,0 +1,7 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +class TrackState { + + // TODO + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java new file mode 100644 index 00000000000..78cf528f556 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -0,0 +1,79 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import org.matsim.core.population.routes.NetworkRoute; + +/** + * Stores the mutable current state of a train. + */ +final class TrainState { + + /** + * Driver of the train. + */ + final MobsimDriverAgent driver; + + /** + * Route of this train. + */ + final NetworkRoute route; + + /** + * Current index in the list of route links. + */ + int routeIdx; + + /** + * Time of this state. + */ + double timestamp; + + + /** + * The link the where the head of the train is on. + */ + Id headLink; + + /** + * Current link the very end of the train is on. + */ + Id tailLink; + + /** + * Current allowed speed, which depends on train type, links, but not on other trains or speed needed to stop. + */ + double allowedMaxSpeed; + + /** + * Distance in meters away from the {@code headLink}s {@code fromNode}. + */ + double headPosition; + + /** + * Speed in m/s. + */ + double speed; + + TrainState(MobsimDriverAgent driver, double timestamp, Id linkId, NetworkRoute route) { + this.driver = driver; + this.route = route; + this.timestamp = timestamp; + this.headLink = linkId; + this.tailLink = linkId; + } + + @Override + public String toString() { + return "TrainState{" + + "driver=" + driver.getId() + + ", timestamp=" + timestamp + + ", headLink=" + headLink + + ", tailLink=" + tailLink + + ", allowedMaxSpeed=" + allowedMaxSpeed + + ", headPosition=" + headPosition + + ", speed=" + speed + + '}'; + } +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java new file mode 100644 index 00000000000..4e7ed22695d --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -0,0 +1,7 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import static org.junit.Assert.*; + +public class RailsimEngineTest { + +} diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml index c5050d6d5f7..80d11a1d7ab 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml @@ -15,15 +15,15 @@ - - + + - - + + - + @@ -31,9 +31,9 @@ - + - + diff --git a/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitDriverAgentFactory.java b/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitDriverAgentFactory.java index e5c7ac247af..665e6f50e0c 100644 --- a/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitDriverAgentFactory.java +++ b/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitDriverAgentFactory.java @@ -18,23 +18,19 @@ */ public class SBBTransitDriverAgentFactory implements TransitDriverAgentFactory { - private final InternalInterface internalInterface; - private final TransitStopAgentTracker transitStopAgentTracker; private final Set deterministicModes; - SBBTransitDriverAgentFactory(InternalInterface internalInterface, TransitStopAgentTracker transitStopAgentTracker, Set deterministicModes) { - this.internalInterface = internalInterface; - this.transitStopAgentTracker = transitStopAgentTracker; + SBBTransitDriverAgentFactory(Set deterministicModes) { this.deterministicModes = deterministicModes; } @Override - public AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf) { + public AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf, InternalInterface internalInterface, TransitStopAgentTracker transitStopAgentTracker) { String mode = umlauf.getUmlaufStuecke().get(0).getRoute().getTransportMode(); if (this.deterministicModes.contains(mode)) { - return new SBBTransitDriverAgent(umlauf, mode, this.transitStopAgentTracker, this.internalInterface); + return new SBBTransitDriverAgent(umlauf, mode,transitStopAgentTracker, internalInterface); } - return new TransitDriverAgentImpl(umlauf, TransportMode.car, this.transitStopAgentTracker, this.internalInterface); + return new TransitDriverAgentImpl(umlauf, TransportMode.car, transitStopAgentTracker, internalInterface); } } diff --git a/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngine.java b/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngine.java index e1f9210c14c..38bd400eb2d 100644 --- a/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngine.java +++ b/contribs/sbb-extensions/src/main/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngine.java @@ -83,14 +83,14 @@ public class SBBTransitQSimEngine extends TransitQSimEngine /*implements Departu private boolean createLinkEvents = false; @Inject - public SBBTransitQSimEngine(QSim qSim, ReplanningContext context) { - super(qSim, new SimpleTransitStopHandlerFactory(), new ReconstructingUmlaufBuilder(qSim.getScenario())); + public SBBTransitQSimEngine(QSim qSim, ReplanningContext context, TransitStopAgentTracker agentTracker, TransitDriverAgentFactory networkDriverFactory) { + super(qSim, new SimpleTransitStopHandlerFactory(), new ReconstructingUmlaufBuilder(qSim.getScenario()), agentTracker, networkDriverFactory); this.qSim = qSim; this.context = context; this.config = ConfigUtils.addOrGetModule(qSim.getScenario().getConfig(), SBBTransitConfigGroup.GROUP_NAME, SBBTransitConfigGroup.class); this.ptConfig = qSim.getScenario().getConfig().transit(); this.schedule = qSim.getScenario().getTransitSchedule(); - this.agentTracker = new TransitStopAgentTracker(qSim.getEventsManager()); + this.agentTracker = agentTracker; if (this.config.getCreateLinkEventsInterval() > 0) { this.linkEventQueue = new PriorityQueue<>(); this.linksCache = new ConcurrentHashMap<>(); @@ -116,8 +116,8 @@ public void setTransitStopHandlerFactory(final TransitStopHandlerFactory stopHan @Override public void setInternalInterface(InternalInterface internalInterface) { this.internalInterface = internalInterface; - this.deterministicDriverFactory = new SBBTransitDriverAgentFactory(internalInterface, this.agentTracker, this.config.getDeterministicServiceModes()); - this.networkDriverFactory = new DefaultTransitDriverAgentFactory(internalInterface, this.agentTracker); + this.deterministicDriverFactory = new SBBTransitDriverAgentFactory(this.config.getDeterministicServiceModes()); + this.networkDriverFactory = new DefaultTransitDriverAgentFactory(); } @Override @@ -230,9 +230,9 @@ private void createVehiclesAndDrivers() { private void createAndScheduleDriver(Vehicle veh, Umlauf umlauf, boolean isDeterministic) { AbstractTransitDriverAgent driver; if (isDeterministic) { - driver = this.deterministicDriverFactory.createTransitDriver(umlauf); + driver = this.deterministicDriverFactory.createTransitDriver(umlauf, internalInterface, agentTracker); } else { - driver = this.networkDriverFactory.createTransitDriver(umlauf); + driver = this.networkDriverFactory.createTransitDriver(umlauf, internalInterface, agentTracker); } TransitQVehicle qVeh = new TransitQVehicle(veh); qVeh.setDriver(driver); diff --git a/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngineTest.java b/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngineTest.java index 1fb69377aee..a3c596481a3 100644 --- a/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngineTest.java +++ b/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/SBBTransitQSimEngineTest.java @@ -34,6 +34,8 @@ import org.matsim.core.mobsim.qsim.PopulationModule; import org.matsim.core.mobsim.qsim.QSim; import org.matsim.core.mobsim.qsim.QSimBuilder; +import org.matsim.core.mobsim.qsim.pt.DefaultTransitDriverAgentFactory; +import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; import org.matsim.core.utils.collections.CollectionUtils; import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.pt.transitSchedule.api.TransitRouteStop; @@ -58,7 +60,7 @@ public void testDriver() { EventsManager eventsManager = EventsUtils.createEventsManager(f.config); QSim qSim = new QSimBuilder(f.config).useDefaults().build(f.scenario, eventsManager); - SBBTransitQSimEngine trEngine = new SBBTransitQSimEngine(qSim, null); + SBBTransitQSimEngine trEngine = new SBBTransitQSimEngine(qSim, null, new TransitStopAgentTracker(eventsManager), new DefaultTransitDriverAgentFactory()); qSim.addMobsimEngine(trEngine); trEngine.insertAgentsIntoMobsim(); diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/DefaultTransitDriverAgentFactory.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/DefaultTransitDriverAgentFactory.java index 07a6a5eb3de..f38e55d3195 100644 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/DefaultTransitDriverAgentFactory.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/DefaultTransitDriverAgentFactory.java @@ -28,17 +28,8 @@ */ public class DefaultTransitDriverAgentFactory implements TransitDriverAgentFactory { - private final InternalInterface internalInterface; - private final TransitStopAgentTracker transitStopAgentTracker; - - public DefaultTransitDriverAgentFactory(InternalInterface internalInterface, TransitStopAgentTracker transitStopAgentTracker) { - this.internalInterface = internalInterface; - this.transitStopAgentTracker = transitStopAgentTracker; - } - - @Override - public AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf) { + public AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf, InternalInterface internalInterface, TransitStopAgentTracker transitStopAgentTracker) { return new TransitDriverAgentImpl(umlauf, TransportMode.car, transitStopAgentTracker, internalInterface); } diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitDriverAgentFactory.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitDriverAgentFactory.java index 2ea71d38b27..2268fd5d0ff 100644 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitDriverAgentFactory.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitDriverAgentFactory.java @@ -20,6 +20,7 @@ package org.matsim.core.mobsim.qsim.pt; import org.matsim.core.api.internal.MatsimFactory; +import org.matsim.core.mobsim.qsim.InternalInterface; import org.matsim.pt.Umlauf; /** @@ -27,6 +28,6 @@ */ public interface TransitDriverAgentFactory extends MatsimFactory { - AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf); + AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf, InternalInterface internalInterface, TransitStopAgentTracker transitStopAgentTracker); } diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitEngineModule.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitEngineModule.java index 02e3ff6b9e0..daf57e51e3f 100644 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitEngineModule.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitEngineModule.java @@ -22,9 +22,14 @@ package org.matsim.core.mobsim.qsim.pt; import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.multibindings.OptionalBinder; import org.matsim.core.config.Config; import org.matsim.core.gbl.Gbl; import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import org.matsim.core.mobsim.qsim.QSim; import org.matsim.pt.ReconstructingUmlaufBuilder; import org.matsim.pt.UmlaufBuilder; @@ -45,5 +50,13 @@ protected void configureQSim() { bind( UmlaufBuilder.class ).to( ReconstructingUmlaufBuilder.class ); + OptionalBinder.newOptionalBinder(binder(), TransitDriverAgentFactory.class) + .setDefault().to( DefaultTransitDriverAgentFactory.class ); + } + + @Provides + @Singleton + public TransitStopAgentTracker transitStopAgentTracker(QSim qSim) { + return new TransitStopAgentTracker(qSim.getEventsManager()); } } diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitQSimEngine.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitQSimEngine.java index 7a9862a6b00..607f7bb28f5 100644 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitQSimEngine.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/TransitQSimEngine.java @@ -54,7 +54,7 @@ * @author mrieser * @author mzilske */ -public class TransitQSimEngine implements DepartureHandler, MobsimEngine, AgentSource, HasAgentTracker { +public class TransitQSimEngine implements DepartureHandler, MobsimEngine, AgentSource, HasAgentTracker { private Collection ptDrivers; @@ -80,27 +80,32 @@ public TransitAgentTriesToTeleportException(String message) { private TransitStopHandlerFactory stopHandlerFactory = new SimpleTransitStopHandlerFactory(); - private TransitDriverAgentFactory transitDriverFactory; + private final TransitDriverAgentFactory transitDriverFactory; private InternalInterface internalInterface = null ; @Override public void setInternalInterface( InternalInterface internalInterface ) { this.internalInterface = internalInterface ; - transitDriverFactory = new DefaultTransitDriverAgentFactory(internalInterface, agentTracker); } TransitQSimEngine(QSim queueSimulation) { - this(queueSimulation, new SimpleTransitStopHandlerFactory(), new ReconstructingUmlaufBuilder(queueSimulation.getScenario()) ); + this(queueSimulation, new SimpleTransitStopHandlerFactory(), + new ReconstructingUmlaufBuilder(queueSimulation.getScenario()), + new TransitStopAgentTracker(queueSimulation.getEventsManager()), + new DefaultTransitDriverAgentFactory()); } - + @Inject - public TransitQSimEngine(QSim queueSimulation, TransitStopHandlerFactory stopHandlerFactory, UmlaufBuilder umlaufBuilder) { + public TransitQSimEngine(QSim queueSimulation, TransitStopHandlerFactory stopHandlerFactory, + UmlaufBuilder umlaufBuilder, TransitStopAgentTracker tracker, + TransitDriverAgentFactory transitDriverFactory) { this.qSim = queueSimulation; this.schedule = queueSimulation.getScenario().getTransitSchedule(); this.umlaufBuilder = umlaufBuilder; - this.agentTracker = new TransitStopAgentTracker(this.qSim.getEventsManager()); + this.agentTracker = tracker; this.stopHandlerFactory = stopHandlerFactory; + this.transitDriverFactory = transitDriverFactory; } // For tests (which create an Engine, and externally create Agents as well). @@ -154,7 +159,7 @@ private UmlaufCache getOrCreateUmlaufCache(final Scenario scenario) { private AbstractTransitDriverAgent createAndScheduleVehicleAndDriver(Umlauf umlauf, Vehicle vehicle) { TransitQVehicle veh = new TransitQVehicle(vehicle); - AbstractTransitDriverAgent driver = this.transitDriverFactory.createTransitDriver(umlauf); + AbstractTransitDriverAgent driver = this.transitDriverFactory.createTransitDriver(umlauf, internalInterface, agentTracker); veh.setDriver(driver); veh.setStopHandler(this.stopHandlerFactory.createTransitStopHandler(veh.getVehicle())); driver.setVehicle(veh); @@ -205,10 +210,6 @@ public void setTransitStopHandlerFactory(final TransitStopHandlerFactory stopHan this.stopHandlerFactory = stopHandlerFactory; } - public void setAbstractTransitDriverFactory(final TransitDriverAgentFactory abstractTransitDriverFactory) { - this.transitDriverFactory = abstractTransitDriverFactory; - } - @Override public void doSimStep(double time) { // Nothing to do here. From 38196f36c17d108eb4e9c12a5281c36c69152a32 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 5 May 2023 17:49:49 +0200 Subject: [PATCH 019/258] Added Test infrastructure, implemented first events and some logic for track states and state updates --- contribs/railsim/pom.xml | 13 ++ .../matsim/contrib/railsim/RailsimUtils.java | 62 +++------ .../railsim/config/RailsimConfigGroup.java | 9 ++ .../events/RailsimLinkStateChangeEvent.java | 48 +++++++ .../events/RailsimTrainLeavesLinkEvent.java | 50 +++++++ .../contrib/railsim/qsimengine/RailLink.java | 108 ++++++++++++++- .../railsim/qsimengine/RailsimEngine.java | 124 +++++++++++------- .../railsim/qsimengine/RailsimQSimEngine.java | 3 +- .../railsim/qsimengine/TrackState.java | 11 +- .../contrib/railsim/qsimengine/TrainInfo.java | 30 +++++ .../railsim/qsimengine/TrainState.java | 21 ++- .../railsim/qsimengine/UpdateEvent.java | 59 +++++++++ .../railsim/qsimengine/RailsimEngineTest.java | 49 ++++++- .../railsim/qsimengine/RailsimTest.java | 75 +++++++++++ .../contrib/railsim/qsimengine/network0.xml | 57 ++++++++ 15 files changed, 614 insertions(+), 105 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml diff --git a/contribs/railsim/pom.xml b/contribs/railsim/pom.xml index 959ebfe9106..3f184d39bb0 100644 --- a/contribs/railsim/pom.xml +++ b/contribs/railsim/pom.xml @@ -23,5 +23,18 @@ 16.0-SNAPSHOT compile + + + org.assertj + assertj-core + test + + + + org.mockito + mockito-core + test + + diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 96b5aee9424..8155c6195f5 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -1,12 +1,10 @@ package ch.sbb.matsim.contrib.railsim; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Network; import org.matsim.pt.transitSchedule.api.TransitLine; import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.VehicleType; /** @@ -18,14 +16,14 @@ public final class RailsimUtils { // link - public static final String LINK_ATTRIBUTE_GRADE = "grade"; - public static final String LINK_ATTRIBUTE_OPPOSITE_DIRECTION = "trainOppositeDirectionLink"; - public static final String LINK_ATTRIBUTE_CAPACITY = "trainCapacity"; - public static final String LINK_ATTRIBUTE_MAX_SPEED = "maxSpeed"; - public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "minimumTime"; + public static final String LINK_ATTRIBUTE_GRADE = "railsimGrade"; + public static final String LINK_ATTRIBUTE_OPPOSITE_DIRECTION = "railsimTrainOppositeDirectionLink"; + public static final String LINK_ATTRIBUTE_CAPACITY = "railsimTrainCapacity"; + public static final String LINK_ATTRIBUTE_MAX_SPEED = "railsimMaxSpeed"; + public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "railsimMinimumTime"; // vehicle - public static final String VEHICLE_ATTRIBUTE_MAX_DECELERATION = "maxDeceleration"; - public static final String VEHICLE_ATTRIBUTE_MAX_ACCELERATION = "maxAcceleration"; + public static final String VEHICLE_ATTRIBUTE_MAX_DECELERATION = "railsimMaxDeceleration"; + public static final String VEHICLE_ATTRIBUTE_MAX_ACCELERATION = "railsimMaxAcceleration"; private RailsimUtils() { } @@ -36,55 +34,35 @@ private RailsimUtils() { * @return the train capacity for this link, if no link attribute is provided the default is 1. */ public static int getTrainCapacity(Link link) { - int trainCapacity = 1; - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY) != null) { - trainCapacity = (Integer) link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY); - } - return trainCapacity; + Object attr = link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY); + return attr != null ? (int) attr: 1; } /** * @return the minimum time for the switch at the end of the link (toNode); if no link attribute is provided the default is 0. */ public static double getMinimumTrainHeadwayTime(Link link) { - double minimumTime = 0.; - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME) != null) { - minimumTime = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME); - } - return minimumTime; + Object attr = link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME); + return attr != null ? (double) attr : 0; } /** * @return the default deceleration time or the vehicle-specific value */ - public static double getTrainDeceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { - double deceleration = railsimConfigGroup.getDecelerationGlobalDefault(); - if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION) != null) { - deceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION); - } - return deceleration; + public static double getTrainDeceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { + double deceleration = railsimConfigGroup.maxDecelerationGlobalDefault; + Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION); + return attr != null ? (double) attr : deceleration; } /** * @return the default acceleration time or the vehicle-specific value */ - public static double getTrainAcceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { - double acceleration = railsimConfigGroup.getAccelerationGlobalDefault(); - if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION) != null) { - acceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION); - } - return acceleration; - } + public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { + double acceleration = railsimConfigGroup.accelerationGlobalDefault; + Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION); + return attr != null ? (double) attr : acceleration; - /** - * @return the default acceleration time or the vehicle-specific value - */ - public static double getGrade(Link link, RailsimConfigGroup railsimConfigGroup) { - double grade = railsimConfigGroup.getGradeGlobalDefault(); - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE) != null) { - grade = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE); - } - return grade; } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index 4c33a41f866..471a6650cb2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -19,6 +19,7 @@ package ch.sbb.matsim.contrib.railsim.config; +import ch.sbb.matsim.contrib.railsim.RailsimUtils; import org.matsim.core.config.ReflectiveConfigGroup; import java.util.Set; @@ -34,6 +35,14 @@ public class RailsimConfigGroup extends ReflectiveConfigGroup { @Comment("Comma separated set of modes that are handled by the rail simulation. Defaults to 'rail'.") public String railNetworkModes = "rail"; + @Parameter + @Comment("Global acceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_ACCELERATION + ");" + " used to compute the train velocity per link.") + public double accelerationGlobalDefault = 0.5; + + @Parameter + @Comment("Maximum Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link.") + public double maxDecelerationGlobalDefault = 0.5; + public RailsimConfigGroup() { super(GROUP_NAME); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java new file mode 100644 index 00000000000..e9bcad7fbbb --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java @@ -0,0 +1,48 @@ +package ch.sbb.matsim.contrib.railsim.events; + +import ch.sbb.matsim.contrib.railsim.qsimengine.TrackState; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasLinkId; +import org.matsim.api.core.v01.network.Link; + +import java.util.Map; + +/** + * Event thrown when the {@link ch.sbb.matsim.contrib.railsim.qsimengine.TrackState} of a {@link Link} changes. + */ +public class RailsimLinkStateChangeEvent extends Event implements HasLinkId { + + public static final String EVENT_TYPE = "railsimLinkStateChangeEvent"; + + private final Id linkId; + private final TrackState state; + private final int track; + + public RailsimLinkStateChangeEvent(double time, Id linkId, TrackState state, int track) { + super(time); + this.linkId = linkId; + this.state = state; + this.track = track; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getLinkId() { + return linkId; + } + + @Override + public Map getAttributes() { + Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_LINK, this.linkId.toString()); + attr.put("state", this.state.toString()); + attr.put("track", String.valueOf(track)); + return attr; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java new file mode 100644 index 00000000000..47841cf090c --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java @@ -0,0 +1,50 @@ +package ch.sbb.matsim.contrib.railsim.events; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasLinkId; +import org.matsim.api.core.v01.events.HasVehicleId; +import org.matsim.api.core.v01.network.Link; +import org.matsim.vehicles.Vehicle; + +import java.util.Map; + +/** + * Event thrown when the very end of a train left a link. + */ +public class RailsimTrainLeavesLinkEvent extends Event implements HasLinkId, HasVehicleId { + + public static final String EVENT_TYPE = "railsimTrainLeavesLinkEvent"; + + private final Id linkId; + private final Id vehicleId; + + public RailsimTrainLeavesLinkEvent(double time, Id linkId, Id vehicleId) { + super(time); + this.linkId = linkId; + this.vehicleId = vehicleId; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getLinkId() { + return linkId; + } + + @Override + public Id getVehicleId() { + return vehicleId; + } + + @Override + public Map getAttributes() { + Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); + attr.put(ATTRIBUTE_LINK, this.linkId.toString()); + return attr; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index 876f7bea310..6c3061237ee 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -1,7 +1,113 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.RailsimUtils; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; + +import java.util.Arrays; + +/** + * Rail tracks in railsim, which corresponds to a MATSim link, but with additional states. + */ class RailLink { - // TODO: store needed information, lookup from link attributes is expensive + private final Id id; + + /** + * States per track. + */ + private final TrackState[] state; + + /** + * Reservations held for each track. + */ + private final MobsimDriverAgent[] reservations; + + final double length; + final double minimumHeadwayTime; + + public RailLink(Link link) { + id = link.getId(); + state = new TrackState[RailsimUtils.getTrainCapacity(link)]; + Arrays.fill(state, TrackState.FREE); + reservations = new MobsimDriverAgent[state.length]; + length = link.getLength(); + minimumHeadwayTime = RailsimUtils.getMinimumTrainHeadwayTime(link); + } + + public Id getId() { + return id; + } + + /** + * Number of tracks on this segment. + */ + public int getTracks() { + return state.length; + } + + + /** + * Returns the allowed freespeed, depending on the context, which is given via driver. + */ + public double getAllowedFreespeed(MobsimDriverAgent driver) { + + // TODO: every context information such as vehicle, or transit line is stored in the driver + // can be retrieved here using the utils + + return 30; + } + + /** + * Whether at least one track is free. + */ + public boolean hasFreeTrack() { + for (TrackState trackState : state) { + if (trackState == TrackState.FREE) + return true; + } + return false; + } + + /** + * Reserve a track for a specific driver. + */ + public int reserveTrack(MobsimDriverAgent driver) { + for (int i = 0; i < state.length; i++) { + if (state[i] == TrackState.FREE) { + state[i] = TrackState.RESERVED; + reservations[i] = driver; + return i; + } + } + throw new IllegalStateException("No track was free."); + } + + /** + * Block a track that was previously reserved. + */ + public int blockTrack(MobsimDriverAgent driver) { + for (int i = 0; i < state.length; i++) { + if (reservations[i] == driver) { + state[i] = TrackState.BLOCKED; + return i; + } + } + throw new IllegalStateException("No track was reserved to be blocked."); + } + /** + * Release a non-free track to be free again. + */ + public void releaseTrack(MobsimDriverAgent driver) { + for (int i = 0; i < state.length; i++) { + if (reservations[i] == driver) { + state[i] = TrackState.FREE; + reservations[i] = null; + return; + } + } + throw new IllegalStateException("Driver " + driver + " has not reserved the track."); + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 98ca23abb0f..f4153f4b895 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -1,15 +1,20 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.events.LinkEnterEvent; import org.matsim.api.core.v01.network.Link; +import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimDriverAgent; import org.matsim.core.mobsim.framework.Steppable; -import org.matsim.core.mobsim.qsim.interfaces.MobsimVehicle; import org.matsim.core.population.routes.NetworkRoute; import java.util.*; +import java.util.stream.Collectors; /** * Engine to simulate train movement. @@ -18,17 +23,25 @@ final class RailsimEngine implements Steppable { private static final Logger log = LogManager.getLogger(RailsimEngine.class); + private final EventsManager eventsManager; + private final RailsimConfigGroup config; + /** * Rail links */ - private final Map, Link> links; + private final Map, RailLink> links; private final List activeTrains = new ArrayList<>(); private final Queue updateQueue = new PriorityQueue<>(); - public RailsimEngine(Map, Link> links) { - this.links = links; + public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, Map, ? extends Link> network) { + this.eventsManager = eventsManager; + this.config = config; + this.links = new IdMap<>(Link.class, network.size()); + for (Map.Entry, ? extends Link> e : network.entrySet()) { + this.links.put(e.getKey(), new RailLink(e.getValue())); + } } @Override @@ -51,11 +64,16 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin log.info("Train {} is departing", agent.getVehicle()); + List list = route.getLinkIds().stream().map(links::get).collect(Collectors.toList()); + list.add(0, links.get(linkId)); + list.add(links.get(route.getEndLinkId())); - TrainState state = new TrainState(agent, now, linkId, route); + TrainState state = new TrainState(agent, new TrainInfo(agent.getVehicle().getVehicle().getType(), config), + now, null, list); activeTrains.add(state); - updateQueue.add(new UpdateEvent(state, UpdateType.NEXT_HEAD_LINK)); + + updateQueue.add(new UpdateEvent(state, UpdateEvent.Type.DEPARTURE)); return true; } @@ -64,73 +82,81 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin * Update the current state of all trains, even if no update would be needed. */ public void updateAllStates(double time) { - updateQueue.clear(); + + // Process all waiting events first + doSimStep(time); + for (TrainState train : activeTrains) { - // TODO: needs different arguments -// updateState(time, train); + updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); } } private void updateState(double time, UpdateEvent event) { + // Do different updates depending on the type + switch (event.type) { + case DEPARTURE -> updateDeparture(time, event); + case POSITION -> updatePosition(time, event); + case ENTER_LINK -> enterLink(time, event); + } + + if (event.type != UpdateEvent.Type.IDLE) { + updateQueue.add(event); + } + } + + private void enterLink(double time, UpdateEvent event) { + TrainState state = event.state; - MobsimVehicle vehicle = state.driver.getVehicle(); - state.driver.chooseNextLinkId(); + // Get link and increment + state.headLink = state.route.get(state.routeIdx++).getId(); -// state.driver.getVehicle(). + state.driver.notifyMoveOverNode(state.headLink); + eventsManager.processEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); + RailLink link = links.get(state.headLink); - // TODO: fixed update very one second - event.plannedTime += 1; + int track = link.blockTrack(state.driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.headLink, TrackState.BLOCKED, track)); - updateQueue.add(event); - } + // TODO: now block links in advance, check when tracks in the back can be released - /** - * Helper class to queue when a state should be updated. - */ - private final static class UpdateEvent implements Comparable { - final TrainState state; - double plannedTime; - UpdateType type; + // TODO: smarter position updates + event.type = UpdateEvent.Type.POSITION; + event.plannedTime += 1; + } - public UpdateEvent(TrainState state, UpdateType type) { - this.state = state; - this.plannedTime = state.timestamp; - this.type = type; - } + private void updateDeparture(double time, UpdateEvent event) { - @Override - public int compareTo(UpdateEvent o) { - int compare = Double.compare(plannedTime, o.plannedTime); + TrainState state = event.state; + state.timestamp = time; - if (compare == 0) - return state.driver.getId().compareTo(o.state.driver.getId()); + RailLink firstLink = state.route.get(0); - return compare; - } + // for departure only the track has to be free and no tracks in advance + if (firstLink.hasFreeTrack()) { + int track = firstLink.reserveTrack(state.driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, firstLink.getId(), TrackState.RESERVED, track)); - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UpdateEvent that = (UpdateEvent) o; - return Objects.equals(state, that.state); + // Call enter link logic immediately + enterLink(time, event); } - @Override - public int hashCode() { - return Objects.hash(state); - } + // vehicle will wait implicitly + // TODO: should be done via callback later + event.plannedTime += 1; } /** - * TODO: maybe this enum won't be necessary later + * Update position within a link. */ - private enum UpdateType { - NEXT_HEAD_LINK - } + private void updatePosition(double time, UpdateEvent event) { + // TODO: calculate the new position depending on the last and time gone by + + // TODO: fixed update every one second + event.plannedTime += 1; + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index 3dae7b58d5c..eec01498b11 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -76,13 +76,12 @@ public void setInternalInterface(InternalInterface internalInterface) { public void onPrepareSim() { Map, Link> links = new IdMap<>(Link.class); - for (Link link : qsim.getScenario().getNetwork().getLinks().values()) { if (link.getAllowedModes().stream().anyMatch(modes::contains)) links.put(link.getId(), link); } - engine = new RailsimEngine(links); + engine = new RailsimEngine(qsim.getEventsManager(), config, links); } @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java index 1c02371c06a..61a126fa488 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java @@ -1,7 +1,10 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; -class TrackState { - - // TODO - +/** + * Current state of a track (link) + */ +public enum TrackState { + FREE, + BLOCKED, + RESERVED } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java new file mode 100644 index 00000000000..0ac7a6088e3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java @@ -0,0 +1,30 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + + +import ch.sbb.matsim.contrib.railsim.RailsimUtils; +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import org.matsim.vehicles.VehicleType; + +/** + * Non-mutable static information for a single train. + */ +record TrainInfo( + double length, + double maxVelocity, + double acceleration, + double deceleration, + double maxDeceleration + ) { + + public TrainInfo(VehicleType vehicle, RailsimConfigGroup config) { + // TODO: + this( + vehicle.getLength(), + vehicle.getMaximumVelocity(), + RailsimUtils.getTrainAcceleration(vehicle, config), + RailsimUtils.getTrainDeceleration(vehicle, config), + RailsimUtils.getTrainDeceleration(vehicle, config) + ); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 78cf528f556..23842c179d7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -3,7 +3,9 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.core.mobsim.framework.MobsimDriverAgent; -import org.matsim.core.population.routes.NetworkRoute; + +import javax.annotation.Nullable; +import java.util.List; /** * Stores the mutable current state of a train. @@ -15,10 +17,15 @@ final class TrainState { */ final MobsimDriverAgent driver; + /** + * Train specific parameters. + */ + final TrainInfo info; + /** * Route of this train. */ - final NetworkRoute route; + final List route; /** * Current index in the list of route links. @@ -30,15 +37,16 @@ final class TrainState { */ double timestamp; - /** - * The link the where the head of the train is on. + * The link the where the head of the train is on. Can be null if not yet departed. */ + @Nullable Id headLink; /** - * Current link the very end of the train is on. + * Current link the very end of the train is on. Can be null if not yet departed. */ + @Nullable Id tailLink; /** @@ -56,8 +64,9 @@ final class TrainState { */ double speed; - TrainState(MobsimDriverAgent driver, double timestamp, Id linkId, NetworkRoute route) { + TrainState(MobsimDriverAgent driver, TrainInfo info, double timestamp, @Nullable Id linkId, List route) { this.driver = driver; + this.info = info; this.route = route; this.timestamp = timestamp; this.headLink = linkId; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java new file mode 100644 index 00000000000..937e868007c --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -0,0 +1,59 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import java.util.Objects; + +/** + * Internal event that describes, when a {@link TrainState} is supposed to be updated. + */ +final class UpdateEvent implements Comparable { + + final TrainState state; + double plannedTime; + Type type; + + public UpdateEvent(TrainState state, Type type) { + this.state = state; + this.plannedTime = state.timestamp; + this.type = type; + } + + @Override + public int compareTo(UpdateEvent o) { + int compare = Double.compare(plannedTime, o.plannedTime); + + if (compare == 0) + return state.driver.getId().compareTo(o.state.driver.getId()); + + return compare; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UpdateEvent that = (UpdateEvent) o; + return Objects.equals(state, that.state); + } + + @Override + public int hashCode() { + return Objects.hash(state); + } + + /** + * The type of the requested update. + */ + enum Type { + + IDLE, + + DEPARTURE, + + POSITION, + + ENTER_LINK, + + TRACK_UPDATE + } + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 4e7ed22695d..1c3291838de 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -1,7 +1,54 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; -import static org.junit.Assert.*; +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.network.NetworkUtils; +import org.matsim.testcases.MatsimTestUtils; + +import java.io.File; public class RailsimEngineTest { + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + private EventsManager eventsManager; + private RailsimTest.EventCollector collector; + + @Before + public void setUp() throws Exception { + eventsManager = EventsUtils.createEventsManager(); + collector = new RailsimTest.EventCollector(); + + eventsManager.addHandler(collector); + eventsManager.initProcessing(); + + } + + private RailsimTest.Holder getTestEngine(String network) { + Network net = NetworkUtils.readNetwork(new File(utils.getPackageInputDirectory(), network).toString()); + + return new RailsimTest.Holder(new RailsimEngine(eventsManager, new RailsimConfigGroup(), net.getLinks()), net); + } + + @Test + public void simple() { + + RailsimTest.Holder test = getTestEngine("network0.xml"); + + RailsimTest.createDeparture(test, 0, "t1_OUT-t2_IN", "t3_IN-t3_OUT"); + + test.engine().doSimStep(0); + + System.out.println(collector.events); + + // TODO: Add assertions when more logic is implemented + + } + } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java new file mode 100644 index 00000000000..647906b81fc --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java @@ -0,0 +1,75 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.events.handler.BasicEventHandler; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import org.matsim.core.mobsim.qsim.interfaces.MobsimVehicle; +import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.core.population.routes.RouteUtils; +import org.matsim.core.router.Dijkstra; +import org.matsim.core.router.DijkstraFactory; +import org.matsim.core.router.costcalculators.FreespeedTravelTimeAndDisutility; +import org.matsim.core.router.util.LeastCostPathCalculator; +import org.matsim.core.router.util.TravelDisutility; +import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; +import org.matsim.vehicles.VehicleUtils; +import org.mockito.Answers; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for test cases. + */ +public class RailsimTest { + + /** + * Create a departure within the engine. Route will be determined automatically. + */ + public static void createDeparture(Holder test, double time, String from, String to) { + + DijkstraFactory f = new DijkstraFactory(); + LeastCostPathCalculator lcp = f.createPathCalculator(test.network(), Mockito.mock(TravelDisutility.class), new FreeSpeedTravelTime()); + + Link fromLink = test.network.getLinks().get(Id.createLinkId(from)); + Link toLink = test.network.getLinks().get(Id.createLinkId(to)); + + LeastCostPathCalculator.Path path = lcp.calcLeastCostPath(fromLink.getFromNode(), toLink.getToNode(), 0, null, null); + NetworkRoute route = RouteUtils.createNetworkRoute(path.links.stream().map(Link::getId).toList()); + + // Setup mocks for driver and vehicle + MobsimDriverAgent driver = Mockito.mock(MobsimDriverAgent.class, Answers.RETURNS_MOCKS); + MobsimVehicle mobVeh = Mockito.mock(MobsimVehicle.class, Answers.RETURNS_MOCKS); + + VehicleType type = VehicleUtils.createVehicleType(Id.create("vehicle type", VehicleType.class)); + Vehicle vehicle = VehicleUtils.createVehicle(Id.createVehicleId("vehicle"), type); + + Mockito.when(mobVeh.getVehicle()).thenReturn(vehicle); + Mockito.when(driver.getVehicle()).thenReturn(mobVeh); + + + test.engine.handleDeparture(time, driver, route.getStartLinkId(), route); + } + + /** + * Collect events during testing + */ + public static class EventCollector implements BasicEventHandler { + + List events = new ArrayList<>(); + + @Override + public void handleEvent(Event event) { + events.add(event); + } + } + + public record Holder(RailsimEngine engine, Network network) { + } +} diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml new file mode 100644 index 00000000000..89d2e617067 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml @@ -0,0 +1,57 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + 2 + + + + + 999 + + + + + 5 + + + + + 999 + + + + + + + From 93bf6f8b2a755c620aa2c1fbf8642e0213033455 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Tue, 9 May 2023 11:51:10 +0200 Subject: [PATCH 020/258] Add description of the full path deadlock avoidance approach from the Flatland project. --- contribs/railsim/docs/deadlock-avoidance.md | 71 ++++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/contribs/railsim/docs/deadlock-avoidance.md b/contribs/railsim/docs/deadlock-avoidance.md index 7be40dbd87b..1f2dd9b5520 100644 --- a/contribs/railsim/docs/deadlock-avoidance.md +++ b/contribs/railsim/docs/deadlock-avoidance.md @@ -25,9 +25,9 @@ is after the exit time of the preceding train. ## Global case -The global case is more complex to solve, therefore only the problem is described below and not yet a solution approach -provided. The description is based on the segments defined in the local case, even though they could possibly be omitted -for this case, depending on the chosen solution approach. +The global case is more complex to solve, and the problem is described below. The description is based on the segments +defined in the local case, even though they could possibly be omitted for this case, depending on the chosen solution +approach. A train on its route reserves segments ahead as far as possible (see *full path reservation*) or alternatively until the next station with the assumption that there are enough tracks available in the station to pass each other (see *station @@ -56,6 +56,71 @@ train tries to enter the destination station and no track is available. ==To Discuss:== this case is probably rather rare and negligible for the time being? If we model the stations as segments too, then this case should be automatically solved? +#### Solution approach + +**Key idea** +We have agents who want to navigate through resources. In the context of railsim, agents are individual vehicles, +resources are `RailLink`s (?), and the full path of an agent is the transit route it takes from the origin to the +destination station. A vehicle can have several transit routes on a simulation day. +Assuming that the current simulation is in a deadlock-free state, the goal is to ensure that the simulation will also be +deadlock-free after a next simulation step. This requires a deadlock avoidance algorithm. The algorithm shall prevent +deadlock state from occurring. + +**Algorithm** + +1. Setup: + - Create a constant 1D-array with total train capacity per resource **CAP** (dim: 1 x n_resources) + - Create a binary matrix for global positions **POS** (dim: n_agents x n_resources) and initialize it with zero. + - Create a binary matrix for all path masks **PATH** (dim: n_agents x n_resources) and calculate for each + agent **a_i** the binary mask for the full path with resources needed to reach the destination. Write mask into + the paths mask matrix **PATH[a_i, ]**. + - Create a set with moving agents **MOV** and a set with agents on a decision point resource **DEC**. Add all agents + to both sets. +2. Start railsim simulation, in each iteration: + 1. Handle moving set agents: + - For each agent in the moving set **MOV** enter the currently occupied resource into the global position + mask **POS[a_i, ]**. + 2. Handle decision set agents: + - Mask the path of each agent in the decision set **DEC** with the global position mask and identify all + potentially conflicting agents. + - Create a new 1D-array (dim: 1 x n_resources) with the sum of paths of all conflicting agents per agent (zero + out non-conflicting agents in **PATH** and sum the columns). + - Subtract the summed path array from the constant available capacity layer **CAP** for each agent. + - If the next resource an agent wants to allocate has at least one free capacity, the agent is added to the + moving set **MOV**, otherwise the agent is removed from the moving set and has to wait on the current resource + for this iteration. + 3. The steps 1 and 2 are repeated for **the situation where the next step is already moved virtually** of all agents + in the moving agent set. If there is a conflict, randomly chose one agent of the conflicting pair and remove it + from the moving set (has to wait in this interation). Do not save the actual movement from this step! + 4. For all agents in the moving set **MOV**, set the current resource in the path matrix to zero + **PATH[a_i, r_c]=0**. and check if the next resource they occupy is a decision point. If it is a decision point, + add the agent to the decision set *DEC*. + 5. Continue railsim iteration (here the actual movement of the agent happens) and repeat. + +**Key benefits** +The algorithm described above can be fully parallelized. The computation time depends only on the number of agents (n) +and number of resources (m). The computation complexity is O(n2 * m) in the worst case. + +**References** +This algorithm was designed and implemented by Adrian Egli (SBB) as part of the Flatland project. + +**Railsim context** + +- Resources have to be smaller entities (e.g. `RailLink`) than segments, since otherwise two agents are not allowed to + travel in the same direction. +- Not all resources should trigger the deadlock avoidance algorithm for the agent. For better performance, only when an + agent is on a decision point (decision set of agents), the algorithm is triggered (e.g. first / entering `RailLink` of + a segment with constant capacity). +- The former two points could be combined: + - Each segment consists of the following **directed** `RailLink`s for both directions (2x): An *entering* `RailLink` + , *intermediate* `RailLink`s for each change in VMax (with same capacity), a *leaving* `RailLink`. + - Every `RailLink` has an opposite direction `RailLink`, where the entering link is the leaving link and vice versa. + - The algorithm is only triggered on the **leaving** `RailLink`s. + - Stations could also be modelled as segments (entering, intermediate, leaving links). +- I (mu) am not sure if we still need the combination with the local deadlock case, but I suspect that this is necessary + if the capacity of a segment is greater than 1. We then have to determine which train is on which track to see if the + train can overtake or has to brake. + ### Station to station path reservation In the case where the trains only reserve the path to the next station, a deadlock is again possible when entering the From 95b792926aa327472424d2b083eed8a040955e06 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Tue, 9 May 2023 11:57:37 +0200 Subject: [PATCH 021/258] Format --- contribs/railsim/docs/deadlock-avoidance.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contribs/railsim/docs/deadlock-avoidance.md b/contribs/railsim/docs/deadlock-avoidance.md index 1f2dd9b5520..c72fe03b4b0 100644 --- a/contribs/railsim/docs/deadlock-avoidance.md +++ b/contribs/railsim/docs/deadlock-avoidance.md @@ -56,9 +56,8 @@ train tries to enter the destination station and no track is available. ==To Discuss:== this case is probably rather rare and negligible for the time being? If we model the stations as segments too, then this case should be automatically solved? -#### Solution approach - **Key idea** + We have agents who want to navigate through resources. In the context of railsim, agents are individual vehicles, resources are `RailLink`s (?), and the full path of an agent is the transit route it takes from the origin to the destination station. A vehicle can have several transit routes on a simulation day. @@ -98,10 +97,12 @@ deadlock state from occurring. 5. Continue railsim iteration (here the actual movement of the agent happens) and repeat. **Key benefits** + The algorithm described above can be fully parallelized. The computation time depends only on the number of agents (n) and number of resources (m). The computation complexity is O(n2 * m) in the worst case. **References** + This algorithm was designed and implemented by Adrian Egli (SBB) as part of the Flatland project. **Railsim context** From c088f33a07358089f95620a856aff476106bde26 Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Tue, 9 May 2023 14:30:52 +0200 Subject: [PATCH 022/258] Created ChargerPowerTimeProfileCalculator and ProfileView --- contribs/ev/pom.xml | 12 ++ .../ChargerPowerTimeProfileCalculator.java | 137 ++++++++++++++++++ ...rgerPowerTimeProfileCollectorProvider.java | 111 -------------- .../ev/stats/ChargerPowerTimeProfileView.java | 49 +++++++ .../contrib/ev/stats/EvStatsModule.java | 9 +- 5 files changed, 204 insertions(+), 114 deletions(-) create mode 100644 contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java delete mode 100644 contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java create mode 100644 contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java diff --git a/contribs/ev/pom.xml b/contribs/ev/pom.xml index f2c94d726b6..ba150a34a5b 100644 --- a/contribs/ev/pom.xml +++ b/contribs/ev/pom.xml @@ -30,5 +30,17 @@ commons-csv 1.10.0 + + one.util + streamex + 0.8.1 + compile + + + one.util + streamex + 0.8.1 + compile + diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java new file mode 100644 index 00000000000..be977ce414e --- /dev/null +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java @@ -0,0 +1,137 @@ +package org.matsim.contrib.ev.stats; + + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.matsim.api.core.v01.Id; +import org.matsim.contrib.common.timeprofile.TimeDiscretizer; +import org.matsim.contrib.ev.EvConfigGroup; +import org.matsim.contrib.ev.EvUnits; +import org.matsim.contrib.ev.charging.ChargingEndEvent; +import org.matsim.contrib.ev.charging.ChargingEndEventHandler; +import org.matsim.contrib.ev.charging.ChargingStartEvent; +import org.matsim.contrib.ev.charging.ChargingStartEventHandler; +import org.matsim.contrib.ev.fleet.ElectricFleet; +import org.matsim.contrib.ev.infrastructure.Charger; +import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.QSimConfigGroup; +import org.matsim.core.controler.MatsimServices; +import org.matsim.core.events.MobsimScopeEventHandler; +import org.matsim.core.mobsim.framework.events.MobsimAfterSimStepEvent; +import org.matsim.core.mobsim.framework.listeners.MobsimAfterSimStepListener; +import org.matsim.core.mobsim.framework.listeners.MobsimListener; +import org.matsim.vehicles.Vehicle; + +import java.util.*; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; + + +public class ChargerPowerTimeProfileCalculator implements ChargingStartEventHandler,ChargingEndEventHandler, + MobsimAfterSimStepListener { + + private Map, List> chargerProfiles; + private final TimeDiscretizer timeDiscretizer; + private final ElectricFleet evFleet; + private final ChargingInfrastructure chargingInfrastructure; + private final MatsimServices matsimServices; + private final int chargeTimeStep; + + @Inject + public ChargerPowerTimeProfileCalculator(ElectricFleet evFleet, ChargingInfrastructure chargingInfrastructure, + MatsimServices matsimServices, Config config) { + this.evFleet = evFleet; + this.chargingInfrastructure = chargingInfrastructure; + this.matsimServices = matsimServices; + chargeTimeStep = ConfigUtils.addOrGetModule(config, EvConfigGroup.class).chargeTimeStep; + double qsimEndTime = ConfigUtils.addOrGetModule(config, QSimConfigGroup.class).getEndTime().orElse(0.0); + timeDiscretizer = new TimeDiscretizer((int)Math.ceil(qsimEndTime), chargeTimeStep); + } + + private static final Map, Double> chargerEnergy = new HashMap<>(); + + private static final Map, List>> vehiclesAtCharger = new HashMap<>(); + private static final Map, Double> vehiclesEnergyPreviousTimeStep = new HashMap<>(); + + //public static ProfileCalculator createChargerEnergyCalculator(final ChargingInfrastructure chargingInfrastructure) { + // List allChargers = new ArrayList<>(chargingInfrastructure.getChargers().values()); + + // ImmutableList header = allChargers.stream().map(charger -> charger.getId() + "").collect(toImmutableList()); + + // return TimeProfiles.createProfileCalculator(header, () -> allChargers.stream() + // .collect(toImmutableMap(charger -> charger.getId() + "", + // charger -> chargerEnergy.getOrDefault(charger.getId(), 0.0) + // ))); + //} + private void normalizeProfile(double[] profile) { + for (int i = 0; i < profile.length; i++) { + profile[i] /= timeDiscretizer.getTimeInterval(); + } + } + public Map, double[]> getChargerProfiles() { + Map,double[]> chargerProfilesArray = new HashMap<>(); + this.chargerProfiles.forEach((chargerId, doubles) -> { + double[] doubleArray = doubles.stream().mapToDouble(Double::doubleValue).toArray(); + chargerProfilesArray.put(chargerId,doubleArray); + }); + chargerProfilesArray.values().forEach(this::normalizeProfile); + return chargerProfilesArray; + } +// @Override +// public MobsimListener get() { +// ProfileCalculator calc = createChargerEnergyCalculator(chargingInfrastructure); +// return new TimeProfileCollector(calc, 300, "individual_chargers_charge_time_profiles", matsimServices); +// } + + @Override + public void handleEvent(ChargingStartEvent event) { + vehiclesEnergyPreviousTimeStep.put(event.getVehicleId(), event.getCharge()); + List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); + if (presentVehicles == null) { + ArrayList> firstVehicle = new ArrayList<>(); + firstVehicle.add(event.getVehicleId()); + vehiclesAtCharger.put(event.getChargerId(), firstVehicle); + } else { + presentVehicles.add(event.getVehicleId()); + vehiclesAtCharger.put(event.getChargerId(), presentVehicles); + } + } + + @Override + public void handleEvent(ChargingEndEvent event) { + List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); + presentVehicles.remove(event.getVehicleId()); + vehiclesEnergyPreviousTimeStep.remove(event.getVehicleId()); + vehiclesAtCharger.put(event.getChargerId(), presentVehicles); + } + + + public void notifyMobsimAfterSimStep(MobsimAfterSimStepEvent event) { + if ((event.getSimulationTime() + 1) % chargeTimeStep == 0) { + vehiclesAtCharger.forEach((charger, vehicleList) -> { + List previousValues = chargerProfiles.get(charger); + if (!vehicleList.isEmpty()) { + double energy = vehicleList.stream().mapToDouble(vehicleId -> EvUnits.J_to_kWh((Objects.requireNonNull(evFleet.getElectricVehicles().get(vehicleId)).getBattery() + .getCharge() - vehiclesEnergyPreviousTimeStep.get(vehicleId)) * (3600.0 / chargeTimeStep))).sum(); + if (!Double.isNaN(energy) && !(energy == 0.0)) { + previousValues.add(energy); + chargerProfiles.put(charger, previousValues); + vehicleList.forEach(vehicleId -> vehiclesEnergyPreviousTimeStep.put(vehicleId, Objects.requireNonNull(evFleet.getElectricVehicles().get(vehicleId)).getBattery().getCharge())); + } else { + previousValues.add(0.0); + chargerProfiles.put(charger, previousValues); + } + } else { + previousValues.add(0.0); + chargerProfiles.put(charger,previousValues); + } + }); + } + } + + public TimeDiscretizer getTimeDiscretizer() { + return timeDiscretizer; + } +} diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java deleted file mode 100644 index df2fd19ed7d..00000000000 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCollectorProvider.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.matsim.contrib.ev.stats; - - -import com.google.common.collect.ImmutableList; -import com.google.inject.Inject; -import com.google.inject.Provider; -import org.matsim.api.core.v01.Id; -import org.matsim.contrib.common.timeprofile.TimeProfileCollector; -import org.matsim.contrib.ev.charging.ChargingEndEvent; -import org.matsim.contrib.ev.charging.ChargingEndEventHandler; -import org.matsim.contrib.ev.charging.ChargingStartEvent; -import org.matsim.contrib.ev.charging.ChargingStartEventHandler; -import org.matsim.contrib.ev.fleet.ElectricFleet; -import org.matsim.contrib.ev.infrastructure.Charger; -import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure; -import org.matsim.core.controler.MatsimServices; -import org.matsim.core.events.MobsimScopeEventHandler; -import org.matsim.core.mobsim.framework.listeners.MobsimListener; -import org.matsim.contrib.common.timeprofile.TimeProfileCollector.ProfileCalculator; -import org.matsim.contrib.common.timeprofile.TimeProfiles; -import org.matsim.vehicles.Vehicle; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; - - -public class ChargerPowerTimeProfileCollectorProvider implements Provider, ChargingStartEventHandler,ChargingEndEventHandler, - MobsimScopeEventHandler { - - private final ElectricFleet evFleet; - private final ChargingInfrastructure chargingInfrastructure; - private final MatsimServices matsimServices; - - @Inject - public ChargerPowerTimeProfileCollectorProvider(ElectricFleet evFleet, ChargingInfrastructure chargingInfrastructure, MatsimServices matsimServices) { - this.evFleet = evFleet; - this.chargingInfrastructure = chargingInfrastructure; - this.matsimServices = matsimServices; - } - - private static final Map,Double> chargerEnergy = new HashMap<>(); - - private static final Map, List>> vehiclesAtCharger = new HashMap<>(); - private static final Map, Double> vehiclesEnergyPreviousTimeStep = new HashMap<>(); - - public static ProfileCalculator createChargerEnergyCalculator (final ChargingInfrastructure chargingInfrastructure) { - List allChargers = new ArrayList<>(chargingInfrastructure.getChargers().values()); - - ImmutableList header = allChargers.stream().map(charger -> charger.getId() + "").collect(toImmutableList()); - - return TimeProfiles.createProfileCalculator(header, () -> allChargers.stream() - .collect(toImmutableMap(charger -> charger.getId() + "", - charger -> chargerEnergy.getOrDefault(charger.getId(), 0.0) - ))); - } - - @Override - public MobsimListener get() { - ProfileCalculator calc = createChargerEnergyCalculator(chargingInfrastructure); - return new TimeProfileCollector(calc,300,"individual_chargers_charge_time_profiles",matsimServices); - } - @Override - public void handleEvent(ChargingStartEvent event) { - vehiclesEnergyPreviousTimeStep.put(event.getVehicleId(), event.getCharge()); - List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); - if (presentVehicles == null) { - ArrayList> firstVehicle = new ArrayList<>(); - firstVehicle.add(event.getVehicleId()); - vehiclesAtCharger.put(event.getChargerId(),firstVehicle); - } else { - presentVehicles.add(event.getVehicleId()); - vehiclesAtCharger.put(event.getChargerId(),presentVehicles); - } - } - @Override - public void handleEvent(ChargingEndEvent event) { - List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); - presentVehicles.remove(event.getVehicleId()); - vehiclesEnergyPreviousTimeStep.remove(event.getVehicleId()); - vehiclesAtCharger.put(event.getChargerId(),presentVehicles); - } - - - - - - - /** Setters and getters to be able to communicate with EvMobsimListener*/ - public Map, List>> getVehiclesAtCharger(){ - return vehiclesAtCharger; - } - public ElectricFleet getEvFleet(){ - return evFleet; - } - public void setChargerEnergy(Id chargerId, double energy){ - chargerEnergy.put(chargerId, energy); - } - - public Map, Double> getVehiclesEnergyPreviousTimeStep() { - return vehiclesEnergyPreviousTimeStep; - } - - public void setVehiclesEnergyPreviousTimeStep(Id vehicleId, double newEnergy) { - vehiclesEnergyPreviousTimeStep.put(vehicleId,newEnergy); - } -} diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java new file mode 100644 index 00000000000..7301d2562a0 --- /dev/null +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java @@ -0,0 +1,49 @@ +package org.matsim.contrib.ev.stats; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.tuple.Pair; +import org.matsim.api.core.v01.Id; +import org.matsim.contrib.common.timeprofile.ProfileWriter; +import org.matsim.contrib.ev.infrastructure.Charger; + +import java.awt.*; +import java.util.Comparator; +import java.util.Map; + +import one.util.streamex.EntryStream; + +public class ChargerPowerTimeProfileView implements ProfileWriter.ProfileView { + private final ChargerPowerTimeProfileCalculator calculator; + private final Comparator> comparator; + + private final Map seriesPaints; + + public ChargerPowerTimeProfileView(ChargerPowerTimeProfileCalculator calculator, Comparator> comparator, + Map, Paint> chargerPaint) { + this.calculator = calculator; + this.comparator = comparator; + seriesPaints = EntryStream.of(chargerPaint).mapKeys(Id::toString).toMap(); + + } + @Override + public int[] times() { + return calculator.getTimeDiscretizer().getTimes(); + } + + @Override + public ImmutableMap profiles() { + return calculator.getChargerProfiles() + .entrySet() + .stream() + .sorted(Map.Entry.comparingByKey(comparator)) + .map(e -> Pair.of(e.getKey().toString(), e.getValue())) + .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public Map seriesPaints() { + return seriesPaints; + } + + +} diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java index baa3a30f827..094afcb97d7 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java @@ -20,10 +20,13 @@ package org.matsim.contrib.ev.stats; +import com.google.inject.Provider; import org.matsim.contrib.ev.EvConfigGroup; import org.matsim.contrib.ev.EvModule; import org.matsim.contrib.ev.charging.ChargingEventSequenceCollector; import org.matsim.core.controler.AbstractModule; +import org.matsim.core.controler.MatsimServices; +import org.matsim.core.controler.listener.ControlerListener; import org.matsim.core.mobsim.qsim.AbstractQSimModule; import com.google.inject.Inject; @@ -58,9 +61,9 @@ protected void configureQSim() { addMobsimScopeEventHandlerBinding().to(EnergyConsumptionCollector.class); addQSimComponentBinding(EvModule.EV_COMPONENT).to(EnergyConsumptionCollector.class); // add more time profiles if necessary - bind(ChargerPowerTimeProfileCollectorProvider.class).asEagerSingleton(); - addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider(ChargerPowerTimeProfileCollectorProvider.class); - addMobsimScopeEventHandlerBinding().to(ChargerPowerTimeProfileCollectorProvider.class); + bind(ChargerPowerTimeProfileCalculator.class).asEagerSingleton(); + addMobsimListenerBinding().to(ChargerPowerTimeProfileCalculator.class); + addQSimComponentBinding(EvModule.EV_COMPONENT).to(ChargerPowerTimeProfileCalculator.class); } } }); From e554ca5a737aba8576df5b3fd6b78d389ddf11d2 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 11 May 2023 12:16:36 +0200 Subject: [PATCH 023/258] Added RailsimTrainStateEvent, improved test infrastructure, very simple train movement working on single link --- contribs/railsim/docs/events-specification.md | 5 + .../matsim/contrib/railsim/RailsimUtils.java | 16 +- .../events/RailsimLinkStateChangeEvent.java | 15 +- .../events/RailsimTrainStateEvent.java | 72 +++++ .../contrib/railsim/qsimengine/RailLink.java | 51 ++-- .../railsim/qsimengine/RailsimEngine.java | 255 ++++++++++++++++-- .../contrib/railsim/qsimengine/TrainInfo.java | 2 +- .../railsim/qsimengine/TrainState.java | 32 ++- .../railsim/qsimengine/UpdateEvent.java | 6 +- .../railsim/qsimengine/EventAssert.java | 10 + .../railsim/qsimengine/EventsAssert.java | 38 +++ .../railsim/qsimengine/RailsimEngineTest.java | 12 +- .../railsim/qsimengine/RailsimStateTest.java | 46 ++++ .../railsim/qsimengine/RailsimTest.java | 41 ++- .../railsim/qsimengine/TestVehicle.java | 11 + .../src/test/resources/trainVehicleTypes.xml | 50 ++++ .../contrib/railsim/qsimengine/network0.xml | 42 ++- .../contrib/railsim/qsimengine/network1.xml | 73 +++++ 18 files changed, 685 insertions(+), 92 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java create mode 100644 contribs/railsim/src/test/resources/trainVehicleTypes.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index 8391d74f45f..3752617b514 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -31,3 +31,8 @@ Similar to the existing `trainLeavesLinkEvent`. One could argue that setting the link state to `free` would imply the same. I (mr) would still say it makes sense to have it separate, because depending on the implementation, a link could remain blocked for a longer time even if the train has already passed (e.g. minimum headway time). + +### railsimTrainStateEvent + +This event is emitted every time there is a position update for a train. +This event contains detailed information about the trains position on a single link. \ No newline at end of file diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 8155c6195f5..4bd8921e8cf 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -62,14 +62,13 @@ public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGrou double acceleration = railsimConfigGroup.accelerationGlobalDefault; Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION); return attr != null ? (double) attr : acceleration; - } /** * @return the vehicle-specific freespeed or 0 if there is no vehicle-specific freespeed provided in the link attributes */ - public static double getLinkFreespeedForVehicleType(Id type, Link link) { - Object attribute = link.getAttributes().getAttribute(type.toString()); + public static double getLinkFreespeedForVehicleType(VehicleType type, Link link) { + Object attribute = link.getAttributes().getAttribute(type.getId().toString()); if (attribute == null) { return 0.; } else { @@ -101,13 +100,12 @@ public static double getLinkFreespeedForTransitLineAndTransitRoute(Id getOppositeDirectionLink(Link link) { - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION) == null) { - return null; - } else { - String oppositeLink = (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION); - return Id.createLinkId(oppositeLink); - } + String oppositeLink = (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION); + return oppositeLink != null ? Id.createLinkId(oppositeLink) : null; } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java index e9bcad7fbbb..939848dd5cf 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java @@ -4,24 +4,28 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.events.Event; import org.matsim.api.core.v01.events.HasLinkId; +import org.matsim.api.core.v01.events.HasVehicleId; import org.matsim.api.core.v01.network.Link; +import org.matsim.vehicles.Vehicle; import java.util.Map; /** * Event thrown when the {@link ch.sbb.matsim.contrib.railsim.qsimengine.TrackState} of a {@link Link} changes. */ -public class RailsimLinkStateChangeEvent extends Event implements HasLinkId { +public class RailsimLinkStateChangeEvent extends Event implements HasLinkId, HasVehicleId { public static final String EVENT_TYPE = "railsimLinkStateChangeEvent"; private final Id linkId; + private final Id vehicleId; private final TrackState state; private final int track; - public RailsimLinkStateChangeEvent(double time, Id linkId, TrackState state, int track) { + public RailsimLinkStateChangeEvent(double time, Id linkId, Id vehicleId, TrackState state, int track) { super(time); this.linkId = linkId; + this.vehicleId = vehicleId; this.state = state; this.track = track; } @@ -36,13 +40,18 @@ public Id getLinkId() { return linkId; } + @Override + public Id getVehicleId() { + return null; + } + @Override public Map getAttributes() { Map attr = super.getAttributes(); attr.put(ATTRIBUTE_LINK, this.linkId.toString()); + attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); attr.put("state", this.state.toString()); attr.put("track", String.valueOf(track)); return attr; } - } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java new file mode 100644 index 00000000000..301f4dd69ac --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -0,0 +1,72 @@ +package ch.sbb.matsim.contrib.railsim.events; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasVehicleId; +import org.matsim.vehicles.Vehicle; + +import java.util.Map; + +/** + * Event for currents train position. + */ +public class RailsimTrainStateEvent extends Event implements HasVehicleId { + + public static final String EVENT_TYPE = "railsimTrainStateEvent"; + + private final Id vehicleId; + private final double headPosition; + private final double tailPosition; + private final double speed; + private final double acceleration; + private final double targetSpeed; + + public RailsimTrainStateEvent(double time, Id vehicleId, double headPosition, double tailPosition, + double speed, double acceleration, double targetSpeed) { + super(time); + this.vehicleId = vehicleId; + this.headPosition = headPosition; + this.tailPosition = tailPosition; + this.speed = speed; + this.acceleration = acceleration; + this.targetSpeed = targetSpeed; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getVehicleId() { + return vehicleId; + } + + public double getHeadPosition() { + return headPosition; + } + + public double getTailPosition() { + return tailPosition; + } + + public double getSpeed() { + return speed; + } + + public double getAcceleration() { + return acceleration; + } + + @Override + public Map getAttributes() { + Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); + attr.put("headPosition", Double.toString(headPosition)); + attr.put("tailPosition", Double.toString(tailPosition)); + attr.put("speed", Double.toString(speed)); + attr.put("acceleration", Double.toString(acceleration)); + attr.put("targetSpeed", Double.toString(targetSpeed)); + return attr; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index 6c3061237ee..468207cc1e1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -2,18 +2,24 @@ import ch.sbb.matsim.contrib.railsim.RailsimUtils; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.HasLinkId; import org.matsim.api.core.v01.network.Link; import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import javax.annotation.Nullable; import java.util.Arrays; /** - * Rail tracks in railsim, which corresponds to a MATSim link, but with additional states. + * Rail tracks in railsim, which may corresponds to multiple MATSim links (if there are opposing links). + * Therefor this kind of track is bidirectional. */ -class RailLink { +final class RailLink implements HasLinkId { private final Id id; + @Nullable + private final Id oppositeId; + /** * States per track. */ @@ -25,38 +31,37 @@ class RailLink { private final MobsimDriverAgent[] reservations; final double length; + final double freeSpeed; final double minimumHeadwayTime; - public RailLink(Link link) { + // TODO: from and to node most likely needed at some point + // A node can be blocked if a train is crossing the path + + public RailLink(Link link, @Nullable Id opposite) { id = link.getId(); + oppositeId = opposite; state = new TrackState[RailsimUtils.getTrainCapacity(link)]; Arrays.fill(state, TrackState.FREE); reservations = new MobsimDriverAgent[state.length]; length = link.getLength(); + freeSpeed = link.getFreespeed(); minimumHeadwayTime = RailsimUtils.getMinimumTrainHeadwayTime(link); } - public Id getId() { + @Override + public Id getLinkId() { return id; } - /** - * Number of tracks on this segment. - */ - public int getTracks() { - return state.length; - } - - /** * Returns the allowed freespeed, depending on the context, which is given via driver. */ public double getAllowedFreespeed(MobsimDriverAgent driver) { - // TODO: every context information such as vehicle, or transit line is stored in the driver - // can be retrieved here using the utils + // TODO: additional context information such as transit line is stored in the driver + // TODO: speed depending on vehicle type - return 30; + return Math.min(freeSpeed, driver.getVehicle().getVehicle().getType().getMaximumVelocity()); } /** @@ -72,6 +77,8 @@ public boolean hasFreeTrack() { /** * Reserve a track for a specific driver. + * + * @return -1 if not track was free, otherwise track number. */ public int reserveTrack(MobsimDriverAgent driver) { for (int i = 0; i < state.length; i++) { @@ -81,7 +88,19 @@ public int reserveTrack(MobsimDriverAgent driver) { return i; } } - throw new IllegalStateException("No track was free."); + + return -1; + } + + /** + * Check if driver has already reserved this link. + */ + public boolean isReserved(MobsimDriverAgent driver) { + for (MobsimDriverAgent reservation : reservations) { + if (reservation == driver) + return true; + } + return false; } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index f4153f4b895..37ca11c7130 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -1,7 +1,9 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; @@ -35,12 +37,21 @@ final class RailsimEngine implements Steppable { private final Queue updateQueue = new PriorityQueue<>(); - public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, Map, ? extends Link> network) { + public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, Map, ? extends Link> network) { this.eventsManager = eventsManager; this.config = config; this.links = new IdMap<>(Link.class, network.size()); for (Map.Entry, ? extends Link> e : network.entrySet()) { - this.links.put(e.getKey(), new RailLink(e.getValue())); + + // This link and the opposite need to have the same attributes + + Id opposite = RailsimUtils.getOppositeDirectionLink(e.getValue()); + // Use existing instead of creating a new one + if (links.containsKey(opposite)) { + this.links.put(e.getKey(), links.get(opposite)); + } else { + this.links.put(e.getKey(), new RailLink(e.getValue(), opposite)); + } } } @@ -85,9 +96,11 @@ public void updateAllStates(double time) { // Process all waiting events first doSimStep(time); + updateQueue.clear(); for (TrainState train : activeTrains) { - updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); + if (train.timestamp < time) + updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); } } @@ -98,6 +111,8 @@ private void updateState(double time, UpdateEvent event) { case DEPARTURE -> updateDeparture(time, event); case POSITION -> updatePosition(time, event); case ENTER_LINK -> enterLink(time, event); + case TRACK_RESERVATION -> reserveTrack(time, event); + default -> throw new IllegalStateException("Unhandled update type " + event.type); } if (event.type != UpdateEvent.Type.IDLE) { @@ -105,27 +120,26 @@ private void updateState(double time, UpdateEvent event) { } } - private void enterLink(double time, UpdateEvent event) { + private void reserveTrack(double time, UpdateEvent event) { TrainState state = event.state; - // Get link and increment - state.headLink = state.route.get(state.routeIdx++).getId(); + RailLink link = state.route.get(state.routeIdx); - state.driver.notifyMoveOverNode(state.headLink); - eventsManager.processEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); + int track = link.reserveTrack(state.driver); + if (track >= 0) { - RailLink link = links.get(state.headLink); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), + state.driver.getVehicle().getId(), TrackState.RESERVED, track)); - int track = link.blockTrack(state.driver); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.headLink, TrackState.BLOCKED, track)); - - // TODO: now block links in advance, check when tracks in the back can be released + } else { + // Brake + state.targetSpeed = 0; + // TODO: continue when track is released + } - // TODO: smarter position updates - event.type = UpdateEvent.Type.POSITION; - event.plannedTime += 1; + updatePosition(time, event); } private void updateDeparture(double time, UpdateEvent event) { @@ -138,7 +152,8 @@ private void updateDeparture(double time, UpdateEvent event) { // for departure only the track has to be free and no tracks in advance if (firstLink.hasFreeTrack()) { int track = firstLink.reserveTrack(state.driver); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, firstLink.getId(), TrackState.RESERVED, track)); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, firstLink.getLinkId(), + state.driver.getVehicle().getId(), TrackState.RESERVED, track)); // Call enter link logic immediately enterLink(time, event); @@ -149,14 +164,212 @@ private void updateDeparture(double time, UpdateEvent event) { event.plannedTime += 1; } + private void enterLink(double time, UpdateEvent event) { + + TrainState state = event.state; + + // Get link and increment + state.headPosition = 0; + state.headLink = state.route.get(state.routeIdx++).getLinkId(); + + // On departure tail link is the same head link + if (state.tailLink == null) { + state.tailLink = state.headLink; + state.tailPosition = links.get(state.tailLink).length + state.train.length(); + } + + state.driver.notifyMoveOverNode(state.headLink); + eventsManager.processEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); + + RailLink link = links.get(state.headLink); + + int track = link.blockTrack(state.driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.headLink, state.driver.getVehicle().getId(), + TrackState.BLOCKED, track)); + + state.acceleration = state.train.acceleration(); + state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); + state.targetSpeed = state.allowedMaxSpeed; + state.timestamp = time; + + event.type = UpdateEvent.Type.POSITION; + + updatePosition(time, event); + } + /** - * Update position within a link. + * Update position within a link and decides on next update. */ private void updatePosition(double time, UpdateEvent event) { - // TODO: calculate the new position depending on the last and time gone by + TrainState state = event.state; - // TODO: fixed update every one second - event.plannedTime += 1; + double elapsed = time - state.timestamp; + + if (elapsed > 0) { + + advancePosition(state, elapsed); + state.timestamp = time; + + assert state.tailPosition >= 0 : "Illegal state update. Tail position should not be negative"; + assert state.headPosition <= links.get(state.headLink).length : "Illegal state update. Head position must be smaller than link length"; + } + + // Decide when the next update is necessary + + // (1) start deceleration + double deccelDist = Double.POSITIVE_INFINITY; + double nextSpeed = retrieveNextSpeed(state); + if (nextSpeed < state.targetSpeed) { + double timeDeccel = (state.targetSpeed - nextSpeed) / state.train.deceleration(); + + deccelDist = calcTraveledDist(state.targetSpeed, timeDeccel, -state.train.deceleration()); + } + + // (2) next link needs reservation + double reserveDist = Double.POSITIVE_INFINITY; + if (!state.route.get(state.routeIdx).isReserved(state.driver)) { + // time needed for full stop + double stopTime = state.allowedMaxSpeed / state.train.deceleration(); + // safety distance + reserveDist = calcTraveledDist(state.speed, stopTime, -state.train.deceleration()); + } + + // (3) tail link changes + double tailDist = state.tailPosition; + // (4) head link changes + double headDist = links.get(state.headLink).length - state.tailPosition; + + decideNextUpdate(event, time, deccelDist, reserveDist, tailDist, headDist); + + eventsManager.processEvent(new RailsimTrainStateEvent(time, state.driver.getVehicle().getId(), + state.headPosition, state.tailPosition, state.speed, state.acceleration, state.targetSpeed)); } + + /** + * Calculated updated state based on elapsed time. + */ + private static void advancePosition(TrainState state, double elapsed) { + double accelTime = state.acceleration >= 0 + // Time to reach full speed + ? (state.targetSpeed - state.speed) / state.acceleration + // Time to reach 0 + : (state.speed) / state.acceleration; + + double dist; + if (accelTime < elapsed) { + + // Travelled distance under constant acceleration + dist = calcTraveledDist(state.speed, accelTime, state.acceleration); + + // Remaining time at constant speed + if (state.acceleration >= 0) + dist += calcTraveledDist(state.allowedMaxSpeed, accelTime - elapsed, 0); + + // Reach either max speed or 0 + state.speed = state.acceleration >= 0 ? state.allowedMaxSpeed : 0; + state.acceleration = 0; + + } else { + + // Acceleration was constant the whole time + dist = calcTraveledDist(state.speed, elapsed, state.acceleration); + state.speed = state.speed + elapsed * state.acceleration; + + } + + state.headPosition += dist; + state.tailPosition -= dist; + } + + /** + * Decide which update is the earliest and needs to be the next. + */ + private void decideNextUpdate(UpdateEvent event, double time, + double deccelDist, double reserveDist, + double tailDist, double headDist) { + + double dist; + if (deccelDist <= reserveDist && deccelDist <= tailDist && deccelDist <= headDist) { + dist = deccelDist; + event.type = UpdateEvent.Type.SPEED_CHANGE; + } else if (reserveDist <= deccelDist && reserveDist <= tailDist && reserveDist <= headDist) { + dist = reserveDist; + event.type = UpdateEvent.Type.TRACK_RESERVATION; + } else if (tailDist <= deccelDist && tailDist <= reserveDist && tailDist <= headDist) { + dist = tailDist; + event.type = UpdateEvent.Type.LEAVE_LINK; + } else { + dist = headDist; + event.type = UpdateEvent.Type.ENTER_LINK; + } + + // dist is the minimum of all supplied distances + event.plannedTime = time + calcRequiredTime(event.state, dist); + } + + /** + * Calculate time needed to advance distance {@code dist}. Depending on acceleration and max speed. + */ + private static double calcRequiredTime(TrainState state, double dist) { + + if (state.acceleration == 0) + return state.speed == 0 ? Double.POSITIVE_INFINITY : dist / state.speed; + + if (state.acceleration > 0) { + + double accelTime = (state.targetSpeed - state.speed) / state.acceleration; + + double d = calcTraveledDist(state.speed, accelTime, state.acceleration); + + // The required distance is reached during acceleration + if (d > dist) { + return solveTraveledDist(state.speed, d, state.acceleration); + + } else + // Time for accel plus remaining dist at max speed + return accelTime + (dist - d) / state.targetSpeed; + + } else { + throw new IllegalStateException("Not needed / implemented yet"); + } + } + + + /** + * Calculate traveled distance given initial speed and constant acceleration. + */ + static double calcTraveledDist(double speed, double elapsedTime, double acceleration) { + return speed * elapsedTime + (elapsedTime * elapsedTime * acceleration / 2); + } + + /** + * Inverse of {@link #calcTraveledDist(double, double, double)}, solves for distance. + */ + static double solveTraveledDist(double speed, double dist, double acceleration) { + if (acceleration == 0) + return dist / speed; + + return (Math.sqrt(2 * acceleration * dist + speed * speed) - speed) / acceleration; + } + + /** + * Allowed speed for the train. + */ + private double retrieveAllowedMaxSpeed(TrainState state) { + // TODO: needs to check the whole part of current route + + return Math.min( + links.get(state.headLink).getAllowedFreespeed(state.driver), + links.get(state.tailLink).getAllowedFreespeed(state.driver) + ); + } + + private double retrieveNextSpeed(TrainState state) { + + // TODO: next link could be a transit stop, then the speed would be 0 + + return state.route.get(state.routeIdx).getAllowedFreespeed(state.driver); + } + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java index 0ac7a6088e3..896b6f5436a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java @@ -14,7 +14,7 @@ record TrainInfo( double acceleration, double deceleration, double maxDeceleration - ) { +) { public TrainInfo(VehicleType vehicle, RailsimConfigGroup config) { // TODO: diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 23842c179d7..eff26b06581 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -3,6 +3,7 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import org.matsim.core.mobsim.qsim.pt.TransitDriverAgent; import javax.annotation.Nullable; import java.util.List; @@ -17,10 +18,16 @@ final class TrainState { */ final MobsimDriverAgent driver; + /** + * Transit agent, if this is a pt transit. + */ + @Nullable + final TransitDriverAgent pt; + /** * Train specific parameters. */ - final TrainInfo info; + final TrainInfo train; /** * Route of this train. @@ -49,6 +56,11 @@ final class TrainState { @Nullable Id tailLink; + /** + * Trains target speed. + */ + double targetSpeed; + /** * Current allowed speed, which depends on train type, links, but not on other trains or speed needed to stop. */ @@ -59,14 +71,25 @@ final class TrainState { */ double headPosition; + /** + * * Distance in meters away from the {@code tailLink}s {@code toNode}. + */ + double tailPosition; + /** * Speed in m/s. */ double speed; - TrainState(MobsimDriverAgent driver, TrainInfo info, double timestamp, @Nullable Id linkId, List route) { + /** + * Current Acceleration, (or deceleration if negative) + */ + double acceleration; + + TrainState(MobsimDriverAgent driver, TrainInfo train, double timestamp, @Nullable Id linkId, List route) { this.driver = driver; - this.info = info; + this.pt = driver instanceof TransitDriverAgent ptDriver ? ptDriver : null; + this.train = train; this.route = route; this.timestamp = timestamp; this.headLink = linkId; @@ -80,9 +103,12 @@ public String toString() { ", timestamp=" + timestamp + ", headLink=" + headLink + ", tailLink=" + tailLink + + ", targetSpeed=" + targetSpeed + ", allowedMaxSpeed=" + allowedMaxSpeed + ", headPosition=" + headPosition + + ", tailPosition=" + tailPosition + ", speed=" + speed + + ", acceleration=" + acceleration + '}'; } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 937e868007c..920615da1cc 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -53,7 +53,11 @@ enum Type { ENTER_LINK, - TRACK_UPDATE + LEAVE_LINK, + TRACK_RESERVATION, + + SPEED_CHANGE; + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java new file mode 100644 index 00000000000..ebd02a9e983 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java @@ -0,0 +1,10 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.assertj.core.api.AbstractAssert; +import org.matsim.api.core.v01.events.Event; + +class EventAssert extends AbstractAssert { + protected EventAssert(Event event, Class selfType) { + super(event, selfType); + } +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java new file mode 100644 index 00000000000..228f0648057 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java @@ -0,0 +1,38 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; +import org.assertj.core.api.AbstractCollectionAssert; +import org.assertj.core.api.Condition; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; + +import java.util.Collection; +import java.util.stream.StreamSupport; + +class EventsAssert extends AbstractCollectionAssert, Event, EventAssert> { + protected EventsAssert(Collection events, Class selfType) { + super(events, selfType); + } + + public EventsAssert hasTrainState(String veh, double time, double headPosition, double speed) { + return haveExactly(1, + new Condition<>(event -> + (event instanceof RailsimTrainStateEvent ev) + && ev.getVehicleId().equals(Id.createVehicleId(veh)) + && ev.getTime() == time + && ev.getHeadPosition() == headPosition + && ev.getSpeed() == speed, + String.format("event with veh %s time %.0f headPosition: %.2f speed: %.2f", veh, time, headPosition, speed)) + ); + } + + @Override + protected EventAssert toAssert(Event value, String description) { + return null; + } + + @Override + protected EventsAssert newAbstractIterableAssert(Iterable iterable) { + return new EventsAssert(StreamSupport.stream(iterable.spliterator(), false).toList(), EventsAssert.class); + } +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 1c3291838de..040bf2ebefd 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -41,11 +41,17 @@ public void simple() { RailsimTest.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, 0, "t1_OUT-t2_IN", "t3_IN-t3_OUT"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "t1_OUT-t2_IN", "t3_IN-t3_OUT"); - test.engine().doSimStep(0); + for (int i = 0; i < 120; i++) { + test.engine().updateAllStates(i); + } - System.out.println(collector.events); + collector.events.forEach(System.out::println); + + RailsimTest.assertThat(collector) + .hasSizeGreaterThan(5) + .hasTrainState("train", 59, 870.25, 29.5); // TODO: Add assertions when more logic is implemented diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java new file mode 100644 index 00000000000..7b09482ec1c --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java @@ -0,0 +1,46 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RailsimStateTest { + + @Test + public void calc() { + + assertThat(RailsimEngine.calcTraveledDist(5, 2, 0)) + .isEqualTo(10); + + assertThat(RailsimEngine.solveTraveledDist(5, 15, 0)) + .isEqualTo(3); + + double d = RailsimEngine.calcTraveledDist(5, 3, 1); + + assertThat(d) + .isEqualTo(19.5); + + assertThat(RailsimEngine.solveTraveledDist(5, 19.5, 1)) + .isEqualTo(3); + + + } + + @Test + public void negative() { + + double d = RailsimEngine.calcTraveledDist(5, 5, -1); + + assertThat(d).isEqualTo(12.5); + + assertThat(RailsimEngine.calcTraveledDist(5, 10, -1)) + .isEqualTo(0); + + double t = RailsimEngine.solveTraveledDist(5, 12.5, -1); + + assertThat(t) + .isEqualTo(5); + + } +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java index 647906b81fc..0695a0b99c8 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java @@ -9,30 +9,41 @@ import org.matsim.core.mobsim.qsim.interfaces.MobsimVehicle; import org.matsim.core.population.routes.NetworkRoute; import org.matsim.core.population.routes.RouteUtils; -import org.matsim.core.router.Dijkstra; import org.matsim.core.router.DijkstraFactory; -import org.matsim.core.router.costcalculators.FreespeedTravelTimeAndDisutility; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; -import org.matsim.vehicles.Vehicle; -import org.matsim.vehicles.VehicleType; -import org.matsim.vehicles.VehicleUtils; +import org.matsim.vehicles.*; import org.mockito.Answers; import org.mockito.Mockito; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * Helper class for test cases. */ public class RailsimTest { + + static Map vehicles = new EnumMap<>(TestVehicle.class); + + static { + + Vehicles veh = VehicleUtils.createVehiclesContainer(); + + MatsimVehicleReader reader = new MatsimVehicleReader(veh); + reader.readURL(RailsimTest.class.getResource("/trainVehicleTypes.xml")); + + vehicles.put(TestVehicle.Sprinter, veh.getVehicleTypes().get(Id.create("Sprinter", VehicleType.class))); + vehicles.put(TestVehicle.Express, veh.getVehicleTypes().get(Id.create("Express", VehicleType.class))); + vehicles.put(TestVehicle.Regio, veh.getVehicleTypes().get(Id.create("Regio", VehicleType.class))); + vehicles.put(TestVehicle.Cargo, veh.getVehicleTypes().get(Id.create("Cargo", VehicleType.class))); + } + /** * Create a departure within the engine. Route will be determined automatically. */ - public static void createDeparture(Holder test, double time, String from, String to) { + public static void createDeparture(Holder test, TestVehicle type, String veh, double time, String from, String to) { DijkstraFactory f = new DijkstraFactory(); LeastCostPathCalculator lcp = f.createPathCalculator(test.network(), Mockito.mock(TravelDisutility.class), new FreeSpeedTravelTime()); @@ -44,13 +55,15 @@ public static void createDeparture(Holder test, double time, String from, String NetworkRoute route = RouteUtils.createNetworkRoute(path.links.stream().map(Link::getId).toList()); // Setup mocks for driver and vehicle + Id vehicleId = Id.createVehicleId(veh); + MobsimDriverAgent driver = Mockito.mock(MobsimDriverAgent.class, Answers.RETURNS_MOCKS); MobsimVehicle mobVeh = Mockito.mock(MobsimVehicle.class, Answers.RETURNS_MOCKS); - VehicleType type = VehicleUtils.createVehicleType(Id.create("vehicle type", VehicleType.class)); - Vehicle vehicle = VehicleUtils.createVehicle(Id.createVehicleId("vehicle"), type); + Vehicle vehicle = VehicleUtils.createVehicle(vehicleId, vehicles.get(type)); Mockito.when(mobVeh.getVehicle()).thenReturn(vehicle); + Mockito.when(mobVeh.getId()).thenReturn(vehicleId); Mockito.when(driver.getVehicle()).thenReturn(mobVeh); @@ -72,4 +85,12 @@ public void handleEvent(Event event) { public record Holder(RailsimEngine engine, Network network) { } + + /** + * Helper method for event assertions. + */ + public static EventsAssert assertThat(EventCollector events) { + return new EventsAssert(events.events, EventsAssert.class); + } + } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java new file mode 100644 index 00000000000..19f4ed8afa4 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java @@ -0,0 +1,11 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +/** + * Vehicle types that are defined for the test cases. + */ +public enum TestVehicle { + Sprinter, + Express, + Regio, + Cargo +} diff --git a/contribs/railsim/src/test/resources/trainVehicleTypes.xml b/contribs/railsim/src/test/resources/trainVehicleTypes.xml new file mode 100644 index 00000000000..5634409b399 --- /dev/null +++ b/contribs/railsim/src/test/resources/trainVehicleTypes.xml @@ -0,0 +1,50 @@ + + + + + + + 0.7 + 0.7 + + + + + + + + + + + 0.7 + 0.7 + + + + + + + + + + 0.5 + 0.5 + + + + + + + + + + 0.1 + 0.1 + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml index 89d2e617067..88c3506c97c 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml @@ -5,6 +5,9 @@ Atlantis + + + @@ -25,31 +28,20 @@ - - - 999 - - - - - 2 - - - - - 999 - - - - - 5 - - - - - 999 - - + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml new file mode 100644 index 00000000000..bbe97e205f2 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml @@ -0,0 +1,73 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + t2_B-t2_A + + + + + 1 + t2_A-t2_B + + + + + + + + + + From d67b6e6480f01294e15ad84fe7746f7b3148623d Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 12 May 2023 16:59:37 +0200 Subject: [PATCH 024/258] train state updates and deceleration working, simple test are running --- .../events/RailsimTrainStateEvent.java | 11 +- .../railsim/qsimengine/FuzzyUtils.java | 37 ++ .../contrib/railsim/qsimengine/RailLink.java | 4 +- .../railsim/qsimengine/RailsimEngine.java | 348 +++++++++++++----- .../railsim/qsimengine/TrainState.java | 14 + .../railsim/qsimengine/UpdateEvent.java | 10 +- .../railsim/qsimengine/EventsAssert.java | 6 +- .../railsim/qsimengine/RailsimEngineTest.java | 43 ++- .../railsim/qsimengine/RailsimStateTest.java | 7 + .../railsim/qsimengine/RailsimTest.java | 8 + .../contrib/railsim/qsimengine/network0.xml | 55 ++- .../contrib/railsim/qsimengine/network1.xml | 73 ---- 12 files changed, 416 insertions(+), 200 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java index 301f4dd69ac..af9dd296cf3 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -3,6 +3,7 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.events.Event; import org.matsim.api.core.v01.events.HasVehicleId; +import org.matsim.api.core.v01.network.Link; import org.matsim.vehicles.Vehicle; import java.util.Map; @@ -15,17 +16,23 @@ public class RailsimTrainStateEvent extends Event implements HasVehicleId { public static final String EVENT_TYPE = "railsimTrainStateEvent"; private final Id vehicleId; + private final Id headLink; private final double headPosition; + private final Id tailLink; private final double tailPosition; private final double speed; private final double acceleration; private final double targetSpeed; - public RailsimTrainStateEvent(double time, Id vehicleId, double headPosition, double tailPosition, + public RailsimTrainStateEvent(double time, Id vehicleId, + Id headLink, double headPosition, + Id tailLink, double tailPosition, double speed, double acceleration, double targetSpeed) { super(time); this.vehicleId = vehicleId; + this.headLink = headLink; this.headPosition = headPosition; + this.tailLink = tailLink; this.tailPosition = tailPosition; this.speed = speed; this.acceleration = acceleration; @@ -62,7 +69,9 @@ public double getAcceleration() { public Map getAttributes() { Map attr = super.getAttributes(); attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); + attr.put("headLink", String.valueOf(headLink)); attr.put("headPosition", Double.toString(headPosition)); + attr.put("tailLink", String.valueOf(tailLink)); attr.put("tailPosition", Double.toString(tailPosition)); attr.put("speed", Double.toString(speed)); attr.put("acceleration", Double.toString(acceleration)); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java new file mode 100644 index 00000000000..52d2d62a0e2 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java @@ -0,0 +1,37 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +/** + * Util class for fuzzy comparisons. + */ +class FuzzyUtils { + + private FuzzyUtils() { + } + + private final static double EPSILON = 1E-5; + + + /** + * Returns true if two doubles are approximately equal. + */ + public static boolean equals(double a, double b) { + return a == b || Math.abs(a - b) < EPSILON; + } + + + /** + * Returns true if the first double is approximately greater than the second. + */ + public static boolean greaterEqualThan(double a, double b) { + return equals(a, b) || a - b > EPSILON; + } + + + /** + * Returns true if the first double is approximately less than the second. + */ + public static boolean lessEqualThan(double a, double b) { + return equals(a, b) || b - a > EPSILON; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index 468207cc1e1..a4f2e3fc85e 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -119,12 +119,12 @@ public int blockTrack(MobsimDriverAgent driver) { /** * Release a non-free track to be free again. */ - public void releaseTrack(MobsimDriverAgent driver) { + public int releaseTrack(MobsimDriverAgent driver) { for (int i = 0; i < state.length; i++) { if (reservations[i] == driver) { state[i] = TrackState.FREE; reservations[i] = null; - return; + return i; } } throw new IllegalStateException("Driver " + driver + " has not reserved the track."); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 37ca11c7130..81faeb4e54c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -3,12 +3,12 @@ import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; -import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.events.LinkEnterEvent; +import org.matsim.api.core.v01.events.LinkLeaveEvent; import org.matsim.api.core.v01.network.Link; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimDriverAgent; @@ -25,6 +25,11 @@ final class RailsimEngine implements Steppable { private static final Logger log = LogManager.getLogger(RailsimEngine.class); + /** + * If trains need to wait, they will check every x seconds if they can proceeed. + */ + private static final double POLL_INTERVAL = 10; + private final EventsManager eventsManager; private final RailsimConfigGroup config; @@ -63,7 +68,9 @@ public void doSimStep(double time) { // Update loop over all required state updates while (update != null && update.plannedTime <= time) { updateQueue.poll(); - updateState(time, update); + + // Use planned time here, otherwise there will be inaccuracies + updateState(update.plannedTime, update); update = updateQueue.peek(); } } @@ -109,8 +116,13 @@ private void updateState(double time, UpdateEvent event) { // Do different updates depending on the type switch (event.type) { case DEPARTURE -> updateDeparture(time, event); - case POSITION -> updatePosition(time, event); + case POSITION -> { + updatePosition(time, event); + decideNextUpdate(time, event); + } + case SPEED_CHANGE -> updateSpeed(time, event); case ENTER_LINK -> enterLink(time, event); + case LEAVE_LINK -> leaveLink(time, event); case TRACK_RESERVATION -> reserveTrack(time, event); default -> throw new IllegalStateException("Unhandled update type " + event.type); } @@ -120,26 +132,38 @@ private void updateState(double time, UpdateEvent event) { } } - private void reserveTrack(double time, UpdateEvent event) { + private void updateSpeed(double time, UpdateEvent event) { TrainState state = event.state; - RailLink link = state.route.get(state.routeIdx); + updatePosition(time, event); - int track = link.reserveTrack(state.driver); - if (track >= 0) { + state.targetSpeed = event.newSpeed; + if (state.targetSpeed < state.speed) + state.acceleration = -state.train.deceleration(); + else if (state.targetSpeed > state.speed) + state.acceleration = state.train.acceleration(); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), - state.driver.getVehicle().getId(), TrackState.RESERVED, track)); + eventsManager.processEvent(state.asEvent(time)); - } else { - // Brake - state.targetSpeed = 0; + decideNextUpdate(time, event); + } - // TODO: continue when track is released - } + private void reserveTrack(double time, UpdateEvent event) { + + TrainState state = event.state; updatePosition(time, event); + + if (!reserveLinkTracks(time, state.routeIdx, state)) { + // Break when reservation is not possible + state.targetSpeed = 0; + state.acceleration = -state.train.deceleration(); + + event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; + event.plannedTime += POLL_INTERVAL; + } else + decideNextUpdate(time, event); } private void updateDeparture(double time, UpdateEvent event) { @@ -151,27 +175,74 @@ private void updateDeparture(double time, UpdateEvent event) { // for departure only the track has to be free and no tracks in advance if (firstLink.hasFreeTrack()) { - int track = firstLink.reserveTrack(state.driver); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, firstLink.getLinkId(), - state.driver.getVehicle().getId(), TrackState.RESERVED, track)); + + reserveLinkTracks(time, 0, state); + + state.timestamp = time; // Call enter link logic immediately enterLink(time, event); } - // vehicle will wait implicitly - // TODO: should be done via callback later - event.plannedTime += 1; + // vehicle will wait + event.plannedTime += POLL_INTERVAL; + } + + /** + * Reserve links in advance as necessary. + */ + private boolean reserveLinkTracks(double time, int idx, TrainState state) { + + double stopTime = state.targetSpeed / state.train.deceleration(); + // safety distance + double safety = calcTraveledDist(state.targetSpeed, stopTime, -state.train.deceleration()); + + double reserved = 0; + + do { + RailLink nextLink = state.route.get(idx++); + int track = nextLink.reserveTrack(state.driver); + + if (track > -1) { + reserved += nextLink.length; + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, nextLink.getLinkId(), + state.driver.getVehicle().getId(), TrackState.RESERVED, track)); + + } else { + return false; + } + + } while (reserved < safety && idx < state.route.size()); + + return true; } private void enterLink(double time, UpdateEvent event) { TrainState state = event.state; + updatePosition(time, event); + // Get link and increment state.headPosition = 0; state.headLink = state.route.get(state.routeIdx++).getLinkId(); + // Arrival at destination + if (state.isRouteAtEnd()) { + + assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; + + // TODO: release all blocked or occupied tracks + + state.driver.notifyArrivalOnLinkByNonNetworkMode(state.headLink); + state.driver.endLegAndComputeNextState(time); + + activeTrains.remove(state); + + event.type = UpdateEvent.Type.IDLE; + return; + } + // On departure tail link is the same head link if (state.tailLink == null) { state.tailLink = state.headLink; @@ -187,87 +258,74 @@ private void enterLink(double time, UpdateEvent event) { eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.headLink, state.driver.getVehicle().getId(), TrackState.BLOCKED, track)); - state.acceleration = state.train.acceleration(); - state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); - state.targetSpeed = state.allowedMaxSpeed; - state.timestamp = time; + // TODO: this probably needs to be a separate function to calculate possible target speed more accurately + if (calcDeccelDistance(event) == Double.POSITIVE_INFINITY) { + state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); + state.targetSpeed = state.allowedMaxSpeed; + state.acceleration = state.train.acceleration(); + } - event.type = UpdateEvent.Type.POSITION; + eventsManager.processEvent(state.asEvent(time)); - updatePosition(time, event); + decideNextUpdate(time, event); } - /** - * Update position within a link and decides on next update. - */ - private void updatePosition(double time, UpdateEvent event) { + private void leaveLink(double time, UpdateEvent event) { TrainState state = event.state; - - double elapsed = time - state.timestamp; - - if (elapsed > 0) { - - advancePosition(state, elapsed); - state.timestamp = time; - - assert state.tailPosition >= 0 : "Illegal state update. Tail position should not be negative"; - assert state.headPosition <= links.get(state.headLink).length : "Illegal state update. Head position must be smaller than link length"; + RailLink link = null; + // Find the next link in the route + for (int i = state.routeIdx; i >= 1; i--) { + if (state.route.get(i - 1).getLinkId().equals(state.tailLink)) { + link = state.route.get(i); + } } - // Decide when the next update is necessary + Objects.requireNonNull(link, "Could not find next link in route"); - // (1) start deceleration - double deccelDist = Double.POSITIVE_INFINITY; - double nextSpeed = retrieveNextSpeed(state); - if (nextSpeed < state.targetSpeed) { - double timeDeccel = (state.targetSpeed - nextSpeed) / state.train.deceleration(); - - deccelDist = calcTraveledDist(state.targetSpeed, timeDeccel, -state.train.deceleration()); - } + updatePosition(time, event); - // (2) next link needs reservation - double reserveDist = Double.POSITIVE_INFINITY; - if (!state.route.get(state.routeIdx).isReserved(state.driver)) { - // time needed for full stop - double stopTime = state.allowedMaxSpeed / state.train.deceleration(); - // safety distance - reserveDist = calcTraveledDist(state.speed, stopTime, -state.train.deceleration()); - } + state.tailLink = link.getLinkId(); + state.tailPosition = link.length + state.train.length(); - // (3) tail link changes - double tailDist = state.tailPosition; - // (4) head link changes - double headDist = links.get(state.headLink).length - state.tailPosition; + eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.tailLink)); - decideNextUpdate(event, time, deccelDist, reserveDist, tailDist, headDist); + int track = link.releaseTrack(state.driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.tailLink, state.driver.getVehicle().getId(), + TrackState.FREE, track)); - eventsManager.processEvent(new RailsimTrainStateEvent(time, state.driver.getVehicle().getId(), - state.headPosition, state.tailPosition, state.speed, state.acceleration, state.targetSpeed)); + decideNextUpdate(time, event); } /** - * Calculated updated state based on elapsed time. + * Update position within a link and decides on next update. */ - private static void advancePosition(TrainState state, double elapsed) { - double accelTime = state.acceleration >= 0 - // Time to reach full speed - ? (state.targetSpeed - state.speed) / state.acceleration - // Time to reach 0 - : (state.speed) / state.acceleration; + private void updatePosition(double time, UpdateEvent event) { + + TrainState state = event.state; + + double elapsed = time - state.timestamp; + + if (elapsed == 0) + return; + + double accelTime = (state.targetSpeed - state.speed) / state.acceleration; double dist; - if (accelTime < elapsed) { + if (state.acceleration == 0) { + dist = state.speed * elapsed; + + } else if (accelTime < elapsed) { // Travelled distance under constant acceleration dist = calcTraveledDist(state.speed, accelTime, state.acceleration); // Remaining time at constant speed - if (state.acceleration >= 0) - dist += calcTraveledDist(state.allowedMaxSpeed, accelTime - elapsed, 0); + if (state.acceleration > 0) + dist += calcTraveledDist(state.targetSpeed, elapsed - accelTime, 0); - // Reach either max speed or 0 - state.speed = state.acceleration >= 0 ? state.allowedMaxSpeed : 0; + // Target speed was reached + state.speed = state.targetSpeed; state.acceleration = 0; } else { @@ -278,22 +336,72 @@ private static void advancePosition(TrainState state, double elapsed) { } + assert FuzzyUtils.greaterEqualThan(dist, 0) : "Travel distance must be positive, but was" + dist; + state.headPosition += dist; state.tailPosition -= dist; + state.timestamp = time; + + assert FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; + assert FuzzyUtils.lessEqualThan(state.headPosition, links.get(state.headLink).length) : "Illegal state update. Head position must be smaller than link length"; + assert FuzzyUtils.greaterEqualThan(state.headPosition, 0) : "Head position must be positive"; + + eventsManager.processEvent(state.asEvent(time)); } /** * Decide which update is the earliest and needs to be the next. */ - private void decideNextUpdate(UpdateEvent event, double time, - double deccelDist, double reserveDist, - double tailDist, double headDist) { + private void decideNextUpdate(double time, UpdateEvent event) { + + TrainState state = event.state; + RailLink currentLink = links.get(state.headLink); + + // (1) max speed reached + double accelDist = Double.POSITIVE_INFINITY; + if (state.acceleration > 0 && state.targetSpeed > state.speed) { + + accelDist = calcTraveledDist(state.speed, (state.targetSpeed - state.speed) / state.acceleration, state.acceleration); + } + + // (2) start deceleration + double deccelDist = calcDeccelDistance(event); + + assert FuzzyUtils.greaterEqualThan(deccelDist, 0) : "Deceleration distance must be larger than 0"; + + // (3) next link needs reservation + double reserveDist = Double.POSITIVE_INFINITY; + if (!state.isRouteAtEnd() && !state.route.get(state.routeIdx).isReserved(state.driver)) { + // time needed for full stop + double stopTime = state.allowedMaxSpeed / state.train.deceleration(); + + assert stopTime > 0 : "Stop time can not be negative"; + + // Distance for full stop + double safetyDist = calcTraveledDist(state.allowedMaxSpeed, stopTime, -state.train.deceleration()); + + reserveDist = currentLink.length - safetyDist - state.headPosition; + } + + // (4) tail link changes + double tailDist = state.tailPosition; + // (5) head link changes + double headDist = currentLink.length - state.headPosition; + + assert tailDist >= 0 : "Tail distance must be positive"; + assert headDist >= 0 : "Head distance must be positive"; + + // Find the earliest required update double dist; - if (deccelDist <= reserveDist && deccelDist <= tailDist && deccelDist <= headDist) { + if (accelDist <= deccelDist && accelDist <= reserveDist && accelDist <= tailDist && accelDist <= headDist) { + dist = accelDist; + event.type = UpdateEvent.Type.POSITION; + } else if (deccelDist <= accelDist && deccelDist <= reserveDist && deccelDist <= tailDist && deccelDist <= headDist && + event.newSpeed != state.targetSpeed) { dist = deccelDist; event.type = UpdateEvent.Type.SPEED_CHANGE; - } else if (reserveDist <= deccelDist && reserveDist <= tailDist && reserveDist <= headDist) { + } else if (reserveDist <= accelDist && reserveDist <= deccelDist && reserveDist <= tailDist && reserveDist <= headDist) { dist = reserveDist; event.type = UpdateEvent.Type.TRACK_RESERVATION; } else if (tailDist <= deccelDist && tailDist <= reserveDist && tailDist <= headDist) { @@ -304,8 +412,10 @@ private void decideNextUpdate(UpdateEvent event, double time, event.type = UpdateEvent.Type.ENTER_LINK; } + assert dist >= 0 : "Distance for next update must be positive"; + // dist is the minimum of all supplied distances - event.plannedTime = time + calcRequiredTime(event.state, dist); + event.plannedTime = time + calcRequiredTime(state, dist); } /** @@ -331,7 +441,16 @@ private static double calcRequiredTime(TrainState state, double dist) { return accelTime + (dist - d) / state.targetSpeed; } else { - throw new IllegalStateException("Not needed / implemented yet"); + + double deccelTime = -(state.speed - state.targetSpeed) / state.acceleration; + + // max distance that can be reached + double max = calcTraveledDist(state.speed, deccelTime, state.acceleration); + + if (dist < max) { + return solveTraveledDist(state.speed, dist, state.acceleration); + } else + return deccelTime; } } @@ -353,6 +472,62 @@ static double solveTraveledDist(double speed, double dist, double acceleration) return (Math.sqrt(2 * acceleration * dist + speed * speed) - speed) / acceleration; } + /** + * Calc when deceleration needs to start. + */ + private double calcDeccelDistance(UpdateEvent event) { + + // TODO: pure calculations without updates can probably be put into separate classes + + TrainState state = event.state; + + if (state.speed == 0) + return Double.POSITIVE_INFINITY; + + double assumedSpeed = state.speed; + + // Lookahead window + double window = calcTraveledDist(assumedSpeed, assumedSpeed / state.train.deceleration(), + -state.train.deceleration()) + links.get(state.headLink).length; + + // Distance to the next speed change point (link) + double dist = links.get(state.headLink).length - state.headPosition; + + double deccelDist = Double.POSITIVE_INFINITY; + double speed = 0; + + for (int i = state.routeIdx; i < state.route.size(); i++) { + + RailLink link = state.route.get(i); + double allowed; + // Last track where train comes to halt + if (i == state.route.size() - 1) + allowed = 0; + else { + allowed = link.getAllowedFreespeed(state.driver); + } + + if (allowed < assumedSpeed) { + double timeDeccel = (assumedSpeed - allowed) / state.train.deceleration(); + double newDeccelDist = calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); + + if ((dist - newDeccelDist) < deccelDist) { + deccelDist = dist - newDeccelDist; + speed = allowed; + } + } + + dist += link.length; + + // don't need to look further than distance needed for full stop + if (dist >= window) + break; + } + + event.newSpeed = speed; + return deccelDist; + } + /** * Allowed speed for the train. */ @@ -365,11 +540,4 @@ private double retrieveAllowedMaxSpeed(TrainState state) { ); } - private double retrieveNextSpeed(TrainState state) { - - // TODO: next link could be a transit stop, then the speed would be 0 - - return state.route.get(state.routeIdx).getAllowedFreespeed(state.driver); - } - } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index eff26b06581..7275228025f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -1,5 +1,6 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.core.mobsim.framework.MobsimDriverAgent; @@ -111,4 +112,17 @@ public String toString() { ", acceleration=" + acceleration + '}'; } + + + boolean isRouteAtEnd() { + return routeIdx == route.size(); + } + + RailsimTrainStateEvent asEvent(double time) { + return new RailsimTrainStateEvent(time, driver.getVehicle().getId(), + headLink, headPosition, + tailLink, tailPosition, + speed, acceleration, targetSpeed); + } + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 920615da1cc..d35ea3d36cd 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -11,6 +11,8 @@ final class UpdateEvent implements Comparable { double plannedTime; Type type; + double newSpeed = -1; + public UpdateEvent(TrainState state, Type type) { this.state = state; this.plannedTime = state.timestamp; @@ -46,17 +48,13 @@ public int hashCode() { enum Type { IDLE, - DEPARTURE, - POSITION, - ENTER_LINK, - LEAVE_LINK, TRACK_RESERVATION, - - SPEED_CHANGE; + WAIT_FOR_RESERVATION, + SPEED_CHANGE } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java index 228f0648057..4ebb2550ca7 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java @@ -19,9 +19,9 @@ public EventsAssert hasTrainState(String veh, double time, double headPosition, new Condition<>(event -> (event instanceof RailsimTrainStateEvent ev) && ev.getVehicleId().equals(Id.createVehicleId(veh)) - && ev.getTime() == time - && ev.getHeadPosition() == headPosition - && ev.getSpeed() == speed, + && FuzzyUtils.equals(ev.getTime(), time) + && FuzzyUtils.equals(ev.getHeadPosition(), headPosition) + && FuzzyUtils.equals(ev.getSpeed(), speed), String.format("event with veh %s time %.0f headPosition: %.2f speed: %.2f", veh, time, headPosition, speed)) ); } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 040bf2ebefd..a2f8d2be6cd 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -33,6 +33,8 @@ public void setUp() throws Exception { private RailsimTest.Holder getTestEngine(String network) { Network net = NetworkUtils.readNetwork(new File(utils.getPackageInputDirectory(), network).toString()); + collector.clear(); + return new RailsimTest.Holder(new RailsimEngine(eventsManager, new RailsimConfigGroup(), net.getLinks()), net); } @@ -40,21 +42,50 @@ private RailsimTest.Holder getTestEngine(String network) { public void simple() { RailsimTest.Holder test = getTestEngine("network0.xml"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); + + for (int i = 0; i < 400; i++) { + test.engine().doSimStep(i); + } + + RailsimTest.assertThat(collector) + .hasSizeGreaterThan(5) + .hasTrainState("train", 169, 500, 44) + .hasTrainState("train", 319.818181481007, 200, 0); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "t1_OUT-t2_IN", "t3_IN-t3_OUT"); + test = getTestEngine("network0.xml"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); - for (int i = 0; i < 120; i++) { + for (int i = 0; i < 400; i++) { test.engine().updateAllStates(i); } - collector.events.forEach(System.out::println); - RailsimTest.assertThat(collector) .hasSizeGreaterThan(5) - .hasTrainState("train", 59, 870.25, 29.5); + .hasTrainState("train", 169, 500, 44) + .hasTrainState("train", 319.818181481007, 200, 0); - // TODO: Add assertions when more logic is implemented + } + + @Test + public void congested() { + + RailsimTest.Holder test = getTestEngine("network0.xml"); + + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); + RailsimTest.createDeparture(test, TestVehicle.Sprinter, "train", 120, "l1-2", "l4-5"); } + + @Test + public void opposite() { + + RailsimTest.Holder test = getTestEngine("network0.xml"); + + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l4-5", "l1-2"); + + + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java index 7b09482ec1c..e2f50a16abf 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java @@ -43,4 +43,11 @@ public void negative() { .isEqualTo(5); } + + @Test + public void deccelDist() { + + Assertions.fail("Implement."); + + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java index 0695a0b99c8..198be20ee02 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java @@ -54,6 +54,8 @@ public static void createDeparture(Holder test, TestVehicle type, String veh, d LeastCostPathCalculator.Path path = lcp.calcLeastCostPath(fromLink.getFromNode(), toLink.getToNode(), 0, null, null); NetworkRoute route = RouteUtils.createNetworkRoute(path.links.stream().map(Link::getId).toList()); + System.out.println("Creating departure with route " + route); + // Setup mocks for driver and vehicle Id vehicleId = Id.createVehicleId(veh); @@ -79,8 +81,14 @@ public static class EventCollector implements BasicEventHandler { @Override public void handleEvent(Event event) { + System.out.println(event); events.add(event); } + + public void clear() { + events.clear(); + } + } public record Holder(RailsimEngine engine, Network network) { diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml index 88c3506c97c..6d595e6e51a 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml @@ -6,42 +6,59 @@ Atlantis - + - - - - - - - - - - - - + + + + + + - + - + - + + + l4-3 + + + + + l3-4 + + - + - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml deleted file mode 100644 index bbe97e205f2..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - t2_B-t2_A - - - - - 1 - t2_A-t2_B - - - - - - - - - - From b7e058bf6d530f4b091b1cdb5c3c671f22bcbf39 Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Tue, 16 May 2023 15:41:22 +0200 Subject: [PATCH 025/258] Updated according to comments, and added functionality for recording average charging power --- .../ChargerPowerTimeProfileCalculator.java | 116 +++++++----------- .../ev/stats/ChargerPowerTimeProfileView.java | 12 +- .../contrib/ev/stats/EvStatsModule.java | 27 ++-- 3 files changed, 69 insertions(+), 86 deletions(-) diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java index be977ce414e..4c8c8416573 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java @@ -29,105 +29,81 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; -public class ChargerPowerTimeProfileCalculator implements ChargingStartEventHandler,ChargingEndEventHandler, - MobsimAfterSimStepListener { +public class ChargerPowerTimeProfileCalculator implements ChargingStartEventHandler,ChargingEndEventHandler + { + + private Map, double[]> chargerProfiles = new HashMap<>(); + private Map,Double> chargingStartTime = new HashMap<>(); + private Map,Double> chargingStartEnergy = new HashMap<>(); - private Map, List> chargerProfiles; private final TimeDiscretizer timeDiscretizer; - private final ElectricFleet evFleet; - private final ChargingInfrastructure chargingInfrastructure; - private final MatsimServices matsimServices; + private final double qsimEndTime; private final int chargeTimeStep; @Inject - public ChargerPowerTimeProfileCalculator(ElectricFleet evFleet, ChargingInfrastructure chargingInfrastructure, - MatsimServices matsimServices, Config config) { - this.evFleet = evFleet; - this.chargingInfrastructure = chargingInfrastructure; - this.matsimServices = matsimServices; + public ChargerPowerTimeProfileCalculator(Config config) { chargeTimeStep = ConfigUtils.addOrGetModule(config, EvConfigGroup.class).chargeTimeStep; - double qsimEndTime = ConfigUtils.addOrGetModule(config, QSimConfigGroup.class).getEndTime().orElse(0.0); + qsimEndTime = ConfigUtils.addOrGetModule(config, QSimConfigGroup.class).getEndTime().orElse(0.0); timeDiscretizer = new TimeDiscretizer((int)Math.ceil(qsimEndTime), chargeTimeStep); } - private static final Map, Double> chargerEnergy = new HashMap<>(); - private static final Map, List>> vehiclesAtCharger = new HashMap<>(); private static final Map, Double> vehiclesEnergyPreviousTimeStep = new HashMap<>(); - //public static ProfileCalculator createChargerEnergyCalculator(final ChargingInfrastructure chargingInfrastructure) { - // List allChargers = new ArrayList<>(chargingInfrastructure.getChargers().values()); - - // ImmutableList header = allChargers.stream().map(charger -> charger.getId() + "").collect(toImmutableList()); - - // return TimeProfiles.createProfileCalculator(header, () -> allChargers.stream() - // .collect(toImmutableMap(charger -> charger.getId() + "", - // charger -> chargerEnergy.getOrDefault(charger.getId(), 0.0) - // ))); - //} private void normalizeProfile(double[] profile) { for (int i = 0; i < profile.length; i++) { profile[i] /= timeDiscretizer.getTimeInterval(); } } public Map, double[]> getChargerProfiles() { - Map,double[]> chargerProfilesArray = new HashMap<>(); - this.chargerProfiles.forEach((chargerId, doubles) -> { - double[] doubleArray = doubles.stream().mapToDouble(Double::doubleValue).toArray(); - chargerProfilesArray.put(chargerId,doubleArray); - }); - chargerProfilesArray.values().forEach(this::normalizeProfile); - return chargerProfilesArray; +// Map,double[]> chargerProfilesArray = new HashMap<>(); +// this.chargerProfiles.forEach((chargerId, doubles) -> { +// double[] doubleArray = doubles.stream().mapToDouble(Double::doubleValue).toArray(); +// chargerProfilesArray.put(chargerId,doubleArray); +// }); + chargerProfiles.values().forEach(this::normalizeProfile); + return chargerProfiles; } -// @Override -// public MobsimListener get() { -// ProfileCalculator calc = createChargerEnergyCalculator(chargingInfrastructure); -// return new TimeProfileCollector(calc, 300, "individual_chargers_charge_time_profiles", matsimServices); -// } @Override public void handleEvent(ChargingStartEvent event) { - vehiclesEnergyPreviousTimeStep.put(event.getVehicleId(), event.getCharge()); - List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); - if (presentVehicles == null) { - ArrayList> firstVehicle = new ArrayList<>(); - firstVehicle.add(event.getVehicleId()); - vehiclesAtCharger.put(event.getChargerId(), firstVehicle); - } else { - presentVehicles.add(event.getVehicleId()); - vehiclesAtCharger.put(event.getChargerId(), presentVehicles); - } +// vehiclesEnergyPreviousTimeStep.put(event.getVehicleId(), event.getCharge()); +// List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); +// if (presentVehicles == null) { +// ArrayList> firstVehicle = new ArrayList<>(); +// firstVehicle.add(event.getVehicleId()); +// vehiclesAtCharger.put(event.getChargerId(), firstVehicle); +// } else { +// presentVehicles.add(event.getVehicleId()); +// vehiclesAtCharger.put(event.getChargerId(), presentVehicles); +// } + chargingStartTime.put(event.getVehicleId(),event.getTime()); + chargingStartEnergy.put(event.getVehicleId(),EvUnits.J_to_kWh(event.getCharge())); } @Override public void handleEvent(ChargingEndEvent event) { - List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); - presentVehicles.remove(event.getVehicleId()); - vehiclesEnergyPreviousTimeStep.remove(event.getVehicleId()); - vehiclesAtCharger.put(event.getChargerId(), presentVehicles); +// List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); +// presentVehicles.remove(event.getVehicleId()); +// vehiclesEnergyPreviousTimeStep.remove(event.getVehicleId()); +// vehiclesAtCharger.put(event.getChargerId(), presentVehicles); + double chargingTimeIn_h = (event.getTime() - chargingStartTime.get(event.getVehicleId()))/3600.0; + double averagePowerIn_kW = (EvUnits.J_to_kWh(event.getCharge())-chargingStartEnergy.get(event.getVehicleId()))/chargingTimeIn_h; + increment(averagePowerIn_kW,event.getChargerId(),chargingStartTime.get(event.getVehicleId()),event.getTime()); } + private void increment(double averagePower,Id chargerId, double beginTime, double endTime){ + if (beginTime == endTime && beginTime >= qsimEndTime) { + return; + } + endTime = Math.min(endTime, qsimEndTime); + int fromIdx = timeDiscretizer.getIdx(beginTime); + int toIdx = timeDiscretizer.getIdx(endTime); - public void notifyMobsimAfterSimStep(MobsimAfterSimStepEvent event) { - if ((event.getSimulationTime() + 1) % chargeTimeStep == 0) { - vehiclesAtCharger.forEach((charger, vehicleList) -> { - List previousValues = chargerProfiles.get(charger); - if (!vehicleList.isEmpty()) { - double energy = vehicleList.stream().mapToDouble(vehicleId -> EvUnits.J_to_kWh((Objects.requireNonNull(evFleet.getElectricVehicles().get(vehicleId)).getBattery() - .getCharge() - vehiclesEnergyPreviousTimeStep.get(vehicleId)) * (3600.0 / chargeTimeStep))).sum(); - if (!Double.isNaN(energy) && !(energy == 0.0)) { - previousValues.add(energy); - chargerProfiles.put(charger, previousValues); - vehicleList.forEach(vehicleId -> vehiclesEnergyPreviousTimeStep.put(vehicleId, Objects.requireNonNull(evFleet.getElectricVehicles().get(vehicleId)).getBattery().getCharge())); - } else { - previousValues.add(0.0); - chargerProfiles.put(charger, previousValues); - } - } else { - previousValues.add(0.0); - chargerProfiles.put(charger,previousValues); - } - }); + for (int i = fromIdx; i < toIdx; i++) { + double[] chargingVector = chargerProfiles.get(chargerId); + chargingVector[i] += averagePower; + chargerProfiles.put(chargerId,chargingVector); } } diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java index 7301d2562a0..27dbb7559c9 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java @@ -14,15 +14,9 @@ public class ChargerPowerTimeProfileView implements ProfileWriter.ProfileView { private final ChargerPowerTimeProfileCalculator calculator; - private final Comparator> comparator; - private final Map seriesPaints; - - public ChargerPowerTimeProfileView(ChargerPowerTimeProfileCalculator calculator, Comparator> comparator, - Map, Paint> chargerPaint) { + public ChargerPowerTimeProfileView(ChargerPowerTimeProfileCalculator calculator) { this.calculator = calculator; - this.comparator = comparator; - seriesPaints = EntryStream.of(chargerPaint).mapKeys(Id::toString).toMap(); } @Override @@ -35,14 +29,14 @@ public ImmutableMap profiles() { return calculator.getChargerProfiles() .entrySet() .stream() - .sorted(Map.Entry.comparingByKey(comparator)) + .sorted(Map.Entry.comparingByKey(Id::compareTo)) .map(e -> Pair.of(e.getKey().toString(), e.getValue())) .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public Map seriesPaints() { - return seriesPaints; + return Map.of(); } diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java index 094afcb97d7..91f9de2878b 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/EvStatsModule.java @@ -21,6 +21,7 @@ package org.matsim.contrib.ev.stats; import com.google.inject.Provider; +import org.matsim.contrib.common.timeprofile.ProfileWriter; import org.matsim.contrib.ev.EvConfigGroup; import org.matsim.contrib.ev.EvModule; import org.matsim.contrib.ev.charging.ChargingEventSequenceCollector; @@ -47,11 +48,11 @@ public void install() { @Override protected void configureQSim() { if (evCfg.timeProfiles) { - addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider( SocHistogramTimeProfileCollectorProvider.class); - addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider( IndividualChargeTimeProfileCollectorProvider.class); - addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider( ChargerOccupancyTimeProfileCollectorProvider.class); + addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider(SocHistogramTimeProfileCollectorProvider.class); + addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider(IndividualChargeTimeProfileCollectorProvider.class); + addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider(ChargerOccupancyTimeProfileCollectorProvider.class); addQSimComponentBinding(EvModule.EV_COMPONENT).to(ChargerOccupancyXYDataCollector.class).asEagerSingleton(); - addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider( VehicleTypeAggregatedChargeTimeProfileCollectorProvider.class); + addQSimComponentBinding(EvModule.EV_COMPONENT).toProvider(VehicleTypeAggregatedChargeTimeProfileCollectorProvider.class); bind(ChargerPowerCollector.class).asEagerSingleton(); addMobsimScopeEventHandlerBinding().to(ChargerPowerCollector.class); @@ -61,11 +62,23 @@ protected void configureQSim() { addMobsimScopeEventHandlerBinding().to(EnergyConsumptionCollector.class); addQSimComponentBinding(EvModule.EV_COMPONENT).to(EnergyConsumptionCollector.class); // add more time profiles if necessary - bind(ChargerPowerTimeProfileCalculator.class).asEagerSingleton(); - addMobsimListenerBinding().to(ChargerPowerTimeProfileCalculator.class); - addQSimComponentBinding(EvModule.EV_COMPONENT).to(ChargerPowerTimeProfileCalculator.class); } } }); + bind(ChargerPowerTimeProfileCalculator.class).asEagerSingleton(); + addEventHandlerBinding().to(ChargerPowerTimeProfileCalculator.class); + addControlerListenerBinding().toProvider(new Provider<>() { + @Inject + private ChargerPowerTimeProfileCalculator calculator; + @Inject + private MatsimServices matsimServices; + + @Override + public ControlerListener get() { + var profileView = new ChargerPowerTimeProfileView(calculator); + return new ProfileWriter(matsimServices,"ev",profileView,"charger_power_time_profiles"); + + } + }); } } From d0a5d1be3b46eeef28dbc5dc2af0f4058be7183d Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Wed, 10 May 2023 10:06:51 +0200 Subject: [PATCH 026/258] update specs --- contribs/railsim/docs/events-specification.md | 13 +++----- .../railsim/docs/network-specification.md | 33 ++++++++----------- .../railsim/qsimengine/RailsimQSimEngine.java | 15 +++++---- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index 3752617b514..2233840d21e 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -2,15 +2,7 @@ railsim introduces additional, custom events. This document describes these event types. -==Note:== I'm not sure we really need this. I was inspired by the fact that the prototype -has custom events like `trainPathEntersLinkEvent`, `trainEntersLinkEvent` and `trainLeavesLinkEvent`. -Thus the following event types are currently **to be discussed**. - -==To Discuss:== should all event types start with `railsim`? - -(ik, mu): We do not insist on an additional trainPathEntersLink events. The combination of `vehicleEntersLinkEvent` plus -`TrainLeavesLinkEvent` and `railsimLinkStateChangeEvent` should be fine. -Cosmetic suggestion: A `trainEntersLinkEvent` instead of a default MATSim `vehicleEntersLinkEvent` would be helpful. +All the additional events use the prefix `railsim`. ## Event Types @@ -32,6 +24,9 @@ One could argue that setting the link state to `free` would imply the same. I (m say it makes sense to have it separate, because depending on the implementation, a link could remain blocked for a longer time even if the train has already passed (e.g. minimum headway time). +There is **no** railsimTrainEntersLinkEvent. The regular `LinkEnterEvent` is used to provide the highest +compatibility with existing analysis and visualization tools. + ### railsimTrainStateEvent This event is emitted every time there is a position update for a train. diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index d3c5c9014d4..ebcea0f4cc3 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -13,6 +13,8 @@ a mesoscopic level of modelling, where a link may represent multiple tracks. ## Specification +We try to use the prefix `railsim` where it is appropriate. + ### Link Attributes #### grade @@ -41,12 +43,10 @@ TODO The minimum time ("minimum train headway time") for the switch at the end of the link (toNode). If no link attribute is provided, a default of 0 is used. -#### vehicle type +#### railsimSpeed_ + vehicle type The vehicle-specific freespeed on this link. -Please note that the actual vehicle-type must be used as attribute name, see example. - -==Should there be a prefix, like `railsimspeed_`? we might want to add more vehicle-dependent attributes later.== +Please note that the actual vehicle-type must be used as part of the attribute name, see example. Example: @@ -54,19 +54,13 @@ Example: - 44.444 - 50.0 + 44.444 + 50.0 ``` -==Note:== The class `RailsimUtils` has additional getters, e.g. to get the freespeed depending on transit line and -route. -Do we keep these? If yes, we should document these as well, and a prefix would make even more sense in this case. - -(ik, mu): We don't think we have to keep this feature, speed limits per vehicle type should be sufficient. - ### Node Attributes Currently none. @@ -84,22 +78,20 @@ states to avoid collisions. Do we really want that? - 1 - B_A + 1 + B_A - 1 - A_B + 1 + A_B ``` -(ik, mu): General remark: If we use a prefix for attributes, should we just go with `railsim` everywhere. - ### Two tracks, each with a single direction TODO @@ -114,6 +106,7 @@ microscopic modelling approach. If two tracks cross each other, e.g. like in the form of the letter `X` or a plus `+`, a train driving in one direction effectively also blocks the intersecting tracks, even if they only share a common node, but not a common link. -TODO +There should be no additional link- or node-attributes necessary. The simulation should block the node in the case of +`railsimCapacity = 1`, but not if the capacity is larger than 1. If the node is blocked, no other trains must be able +to cross this node/intersection. -(ik, mu): See discussion about node states. diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index eec01498b11..b642dffdbc7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -171,13 +171,14 @@ instead of XYT (see above), we could think about if we could create linkEnter/li 1. RailsimQSimEngine extracts all "rail"-links (depending on config) and builds a data structure to store track-states 2. RailsimQSimEngine handles all "rail"-vehicles (also depending on config) and moves the trains each second First implementation can be very basic, e.g. constant speed per link according to freespeed, no checks if track is free - 3. Each train blocks the links it currently occupies, plus 1 link in front of it if possible - 4. A train can only move to the next link it that link is blocked by that train - 5. Each train tries to block as many link in front of it along the route as it needs for the stopping distance - 6. Trains accelerate smoothly when entering links with a higher freespeed - 7. Trains decelerate smoothly before entering links with a lower freespeed - 8. Trains decelerate smoothly if they cannot block enough links in front of them - 9. Deadlock Prevention + 3. Handle passengers at stops, see SBBTransitQSimEngine how to do this + 4. Each train blocks the links it currently occupies, plus 1 link in front of it if possible + 5. A train can only move to the next link it that link is blocked by that train + 6. Each train tries to block as many link in front of it along the route as it needs for the stopping distance + 7. Trains accelerate smoothly when entering links with a higher freespeed + 8. Trains decelerate smoothly before entering links with a lower freespeed + 9. Trains decelerate smoothly if they cannot block enough links in front of them + 10. Deadlock Prevention */ } From 986dfcd2b0114285ab64a4a30a56270bb0c15be9 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 17 May 2023 11:19:03 +0200 Subject: [PATCH 027/258] use train leaves link event instead of vehicle event --- .../railsim/events/RailsimTrainLeavesLinkEvent.java | 4 ++-- .../matsim/contrib/railsim/qsimengine/RailsimEngine.java | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java index 47841cf090c..0d8d55c8d76 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java @@ -19,10 +19,10 @@ public class RailsimTrainLeavesLinkEvent extends Event implements HasLinkId, Has private final Id linkId; private final Id vehicleId; - public RailsimTrainLeavesLinkEvent(double time, Id linkId, Id vehicleId) { + public RailsimTrainLeavesLinkEvent(double time,Id vehicleId, Id linkId) { super(time); - this.linkId = linkId; this.vehicleId = vehicleId; + this.linkId = linkId; } @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 81faeb4e54c..b13eb4e450e 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -3,6 +3,7 @@ import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainLeavesLinkEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; @@ -223,6 +224,11 @@ private void enterLink(double time, UpdateEvent event) { updatePosition(time, event); + // On route departure the head link is null + if (state.headLink != null) { + eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); + } + // Get link and increment state.headPosition = 0; state.headLink = state.route.get(state.routeIdx++).getLinkId(); @@ -288,8 +294,9 @@ private void leaveLink(double time, UpdateEvent event) { state.tailLink = link.getLinkId(); state.tailPosition = link.length + state.train.length(); - eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.tailLink)); + eventsManager.processEvent(new RailsimTrainLeavesLinkEvent(time, state.driver.getVehicle().getId(), state.tailLink)); + // TODO: link is released after headway time int track = link.releaseTrack(state.driver); eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.tailLink, state.driver.getVehicle().getId(), TrackState.FREE, track)); From 25231f25280211d497af4d43a67d77cb963ad111 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 17 May 2023 13:17:45 +0200 Subject: [PATCH 028/258] fix using correct distance in solveTraveledDist --- .../ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index b13eb4e450e..667ecb0d855 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -441,7 +441,7 @@ private static double calcRequiredTime(TrainState state, double dist) { // The required distance is reached during acceleration if (d > dist) { - return solveTraveledDist(state.speed, d, state.acceleration); + return solveTraveledDist(state.speed, dist, state.acceleration); } else // Time for accel plus remaining dist at max speed From c9d0746afbbe81b9148501379d3a274a5af476cf Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 17 May 2023 14:50:34 +0200 Subject: [PATCH 029/258] update the simple test case, use correct tail link ids in the events --- .../railsim/qsimengine/RailsimEngine.java | 100 +++++++++++------- .../railsim/qsimengine/UpdateEvent.java | 1 + .../railsim/qsimengine/RailsimEngineTest.java | 31 +++--- .../railsim/qsimengine/RailsimTest.java | 19 ++++ .../contrib/railsim/qsimengine/network0.xml | 22 ++-- 5 files changed, 109 insertions(+), 64 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 667ecb0d855..5b74b7bbe47 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -72,10 +72,30 @@ public void doSimStep(double time) { // Use planned time here, otherwise there will be inaccuracies updateState(update.plannedTime, update); + + // Add the update event again + if (update.type != UpdateEvent.Type.IDLE) { + updateQueue.add(update); + } + update = updateQueue.peek(); } } + /** + * Update the current state of all trains, even if no update would be needed. + */ + public void updateAllStates(double time) { + + // Process all waiting events first + doSimStep(time); + + for (TrainState train : activeTrains) { + if (train.timestamp < time) + updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); + } + } + /** * Handle the departure of a train. */ @@ -97,21 +117,6 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin return true; } - /** - * Update the current state of all trains, even if no update would be needed. - */ - public void updateAllStates(double time) { - - // Process all waiting events first - doSimStep(time); - updateQueue.clear(); - - for (TrainState train : activeTrains) { - if (train.timestamp < time) - updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); - } - } - private void updateState(double time, UpdateEvent event) { // Do different updates depending on the type @@ -125,12 +130,9 @@ private void updateState(double time, UpdateEvent event) { case ENTER_LINK -> enterLink(time, event); case LEAVE_LINK -> leaveLink(time, event); case TRACK_RESERVATION -> reserveTrack(time, event); + case WAIT_FOR_RESERVATION -> checkTrackReservation(time, event); default -> throw new IllegalStateException("Unhandled update type " + event.type); } - - if (event.type != UpdateEvent.Type.IDLE) { - updateQueue.add(event); - } } private void updateSpeed(double time, UpdateEvent event) { @@ -156,6 +158,8 @@ private void reserveTrack(double time, UpdateEvent event) { updatePosition(time, event); + // TODO: reservation strategy might be put behind interface + if (!reserveLinkTracks(time, state.routeIdx, state)) { // Break when reservation is not possible state.targetSpeed = 0; @@ -167,6 +171,27 @@ private void reserveTrack(double time, UpdateEvent event) { decideNextUpdate(time, event); } + private void checkTrackReservation(double time, UpdateEvent event) { + + TrainState state = event.state; + + if (reserveLinkTracks(time, state.routeIdx, state)) { + + // TODO: maximum speed could be lower + // see enterLink as well + + state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); + state.targetSpeed = state.allowedMaxSpeed; + state.acceleration = state.train.acceleration(); + + decideNextUpdate(time, event); + + } else { + event.plannedTime += POLL_INTERVAL; + } + + } + private void updateDeparture(double time, UpdateEvent event) { TrainState state = event.state; @@ -183,10 +208,10 @@ private void updateDeparture(double time, UpdateEvent event) { // Call enter link logic immediately enterLink(time, event); + } else { + // vehicle will wait and call departure again + event.plannedTime += POLL_INTERVAL; } - - // vehicle will wait - event.plannedTime += POLL_INTERVAL; } /** @@ -200,6 +225,9 @@ private boolean reserveLinkTracks(double time, int idx, TrainState state) { double reserved = 0; + + // TODO: could reserve some links or no links at all if only some can not be reserved + do { RailLink nextLink = state.route.get(idx++); int track = nextLink.reserveTrack(state.driver); @@ -238,8 +266,6 @@ private void enterLink(double time, UpdateEvent event) { assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; - // TODO: release all blocked or occupied tracks - state.driver.notifyArrivalOnLinkByNonNetworkMode(state.headLink); state.driver.endLegAndComputeNextState(time); @@ -279,28 +305,29 @@ private void enterLink(double time, UpdateEvent event) { private void leaveLink(double time, UpdateEvent event) { TrainState state = event.state; - RailLink link = null; + + RailLink tailLink = links.get(state.tailLink); + RailLink nextTailLink = null; // Find the next link in the route for (int i = state.routeIdx; i >= 1; i--) { if (state.route.get(i - 1).getLinkId().equals(state.tailLink)) { - link = state.route.get(i); + nextTailLink = state.route.get(i); } } - Objects.requireNonNull(link, "Could not find next link in route"); + Objects.requireNonNull(nextTailLink, "Could not find next link in route"); updatePosition(time, event); - state.tailLink = link.getLinkId(); - state.tailPosition = link.length + state.train.length(); - eventsManager.processEvent(new RailsimTrainLeavesLinkEvent(time, state.driver.getVehicle().getId(), state.tailLink)); - - // TODO: link is released after headway time - int track = link.releaseTrack(state.driver); + // TODO: link should be released after headway time + int track = tailLink.releaseTrack(state.driver); eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.tailLink, state.driver.getVehicle().getId(), TrackState.FREE, track)); + state.tailLink = nextTailLink.getLinkId(); + state.tailPosition = nextTailLink.length + state.train.length(); + decideNextUpdate(time, event); } @@ -372,7 +399,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { } // (2) start deceleration - double deccelDist = calcDeccelDistance(event); + double deccelDist = event.newSpeed == state.targetSpeed ? Double.POSITIVE_INFINITY : calcDeccelDistance(event); assert FuzzyUtils.greaterEqualThan(deccelDist, 0) : "Deceleration distance must be larger than 0"; @@ -404,8 +431,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { if (accelDist <= deccelDist && accelDist <= reserveDist && accelDist <= tailDist && accelDist <= headDist) { dist = accelDist; event.type = UpdateEvent.Type.POSITION; - } else if (deccelDist <= accelDist && deccelDist <= reserveDist && deccelDist <= tailDist && deccelDist <= headDist && - event.newSpeed != state.targetSpeed) { + } else if (deccelDist <= accelDist && deccelDist <= reserveDist && deccelDist <= tailDist && deccelDist <= headDist) { dist = deccelDist; event.type = UpdateEvent.Type.SPEED_CHANGE; } else if (reserveDist <= accelDist && reserveDist <= deccelDist && reserveDist <= tailDist && reserveDist <= headDist) { @@ -419,7 +445,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { event.type = UpdateEvent.Type.ENTER_LINK; } - assert dist >= 0 : "Distance for next update must be positive"; + assert FuzzyUtils.greaterEqualThan(dist, 0) : "Distance for next update must be positive"; // dist is the minimum of all supplied distances event.plannedTime = time + calcRequiredTime(state, dist); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index d35ea3d36cd..784d28fef64 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -52,6 +52,7 @@ enum Type { POSITION, ENTER_LINK, LEAVE_LINK, + FREE_RESERVATION, TRACK_RESERVATION, WAIT_FOR_RESERVATION, SPEED_CHANGE diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index a2f8d2be6cd..6d1a7dc9bea 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -42,28 +42,24 @@ private RailsimTest.Holder getTestEngine(String network) { public void simple() { RailsimTest.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); - for (int i = 0; i < 400; i++) { - test.engine().doSimStep(i); - } + test.doSimStepUntil(400); RailsimTest.assertThat(collector) .hasSizeGreaterThan(5) - .hasTrainState("train", 169, 500, 44) - .hasTrainState("train", 319.818181481007, 200, 0); + .hasTrainState("train", 148, 0, 20) + .hasTrainState("train", 188, 200, 0); test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); - for (int i = 0; i < 400; i++) { - test.engine().updateAllStates(i); - } + test.doStateUpdatesUntil(400, 1); RailsimTest.assertThat(collector) .hasSizeGreaterThan(5) - .hasTrainState("train", 169, 500, 44) - .hasTrainState("train", 319.818181481007, 200, 0); + .hasTrainState("train", 148, 0, 20) + .hasTrainState("train", 188, 200, 0); } @@ -72,8 +68,10 @@ public void congested() { RailsimTest.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); - RailsimTest.createDeparture(test, TestVehicle.Sprinter, "train", 120, "l1-2", "l4-5"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "regio", 0, "l1-2", "l5-6"); + RailsimTest.createDeparture(test, TestVehicle.Sprinter, "sprinter", 60, "l1-2", "l5-6"); + + test.doSimStepUntil(400); } @@ -83,9 +81,10 @@ public void opposite() { RailsimTest.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l4-5"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l4-5", "l1-2"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l5-6"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l6-5", "l2-1"); + test.doSimStepUntil(400); } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java index 198be20ee02..a725658cb4b 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java @@ -92,6 +92,25 @@ public void clear() { } public record Holder(RailsimEngine engine, Network network) { + + /** + * Step at one second until time is reached. + */ + public void doSimStepUntil(double time) { + for (double t = 0; t < time; t++) { + engine().doSimStep(t); + } + } + + /** + * Call state updates until time is reached with fixed interval. + */ + public void doStateUpdatesUntil(double time, double interval) { + + for (double t = 0; t < time; t += interval) { + engine().updateAllStates(t); + } + } } /** diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml index 6d595e6e51a..526d8556ddf 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml @@ -16,24 +16,24 @@ - - - - - + + + + + - - - - - - From d0bf464631210532f0f4f18a8dd371a16ba44047 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 17 May 2023 15:53:39 +0200 Subject: [PATCH 030/258] some refactoring, put reservation strategies behind interface --- .../railsim/qsimengine/RailsimCalc.java | 63 ++++++++++ .../railsim/qsimengine/RailsimEngine.java | 119 ++++-------------- .../qsimengine/ReservationAtLatestChance.java | 51 ++++++++ .../qsimengine/TrackReservationStrategy.java | 23 ++++ .../railsim/qsimengine/RailsimCalcTest.java | 46 +++++++ .../railsim/qsimengine/RailsimStateTest.java | 53 -------- 6 files changed, 206 insertions(+), 149 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java new file mode 100644 index 00000000000..30ab1a121d6 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -0,0 +1,63 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +/** + * Utility class holding static calculation methods related to state (updates). + */ +public class RailsimCalc { + + private RailsimCalc() { + } + + /** + * Calculate traveled distance given initial speed and constant acceleration. + */ + static double calcTraveledDist(double speed, double elapsedTime, double acceleration) { + return speed * elapsedTime + (elapsedTime * elapsedTime * acceleration / 2); + } + + /** + * Inverse of {@link #calcTraveledDist(double, double, double)}, solves for distance. + */ + static double solveTraveledDist(double speed, double dist, double acceleration) { + if (acceleration == 0) + return dist / speed; + + return (Math.sqrt(2 * acceleration * dist + speed * speed) - speed) / acceleration; + } + + /** + * Calculate time needed to advance distance {@code dist}. Depending on acceleration and max speed. + */ + static double calcRequiredTime(TrainState state, double dist) { + + if (state.acceleration == 0) + return state.speed == 0 ? Double.POSITIVE_INFINITY : dist / state.speed; + + if (state.acceleration > 0) { + + double accelTime = (state.targetSpeed - state.speed) / state.acceleration; + + double d = calcTraveledDist(state.speed, accelTime, state.acceleration); + + // The required distance is reached during acceleration + if (d > dist) { + return solveTraveledDist(state.speed, dist, state.acceleration); + + } else + // Time for accel plus remaining dist at max speed + return accelTime + (dist - d) / state.targetSpeed; + + } else { + + double deccelTime = -(state.speed - state.targetSpeed) / state.acceleration; + + // max distance that can be reached + double max = calcTraveledDist(state.speed, deccelTime, state.acceleration); + + if (dist < max) { + return solveTraveledDist(state.speed, dist, state.acceleration); + } else + return deccelTime; + } + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 5b74b7bbe47..4beec1c00f7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -43,6 +43,8 @@ final class RailsimEngine implements Steppable { private final Queue updateQueue = new PriorityQueue<>(); + private final TrackReservationStrategy reservation; + public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, Map, ? extends Link> network) { this.eventsManager = eventsManager; this.config = config; @@ -59,6 +61,8 @@ public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, Map this.links.put(e.getKey(), new RailLink(e.getValue(), opposite)); } } + + this.reservation = new ReservationAtLatestChance(); } @Override @@ -197,12 +201,8 @@ private void updateDeparture(double time, UpdateEvent event) { TrainState state = event.state; state.timestamp = time; - RailLink firstLink = state.route.get(0); - // for departure only the track has to be free and no tracks in advance - if (firstLink.hasFreeTrack()) { - - reserveLinkTracks(time, 0, state); + if (reserveLinkTracks(time, 0, state)) { state.timestamp = time; @@ -219,29 +219,17 @@ private void updateDeparture(double time, UpdateEvent event) { */ private boolean reserveLinkTracks(double time, int idx, TrainState state) { - double stopTime = state.targetSpeed / state.train.deceleration(); - // safety distance - double safety = calcTraveledDist(state.targetSpeed, stopTime, -state.train.deceleration()); - - double reserved = 0; + List links = reservation.retrieveLinksToReserve(time, idx, state); + // All links must be able to be reserved + if (!links.stream().allMatch(RailLink::hasFreeTrack)) + return false; - // TODO: could reserve some links or no links at all if only some can not be reserved - - do { - RailLink nextLink = state.route.get(idx++); - int track = nextLink.reserveTrack(state.driver); - - if (track > -1) { - reserved += nextLink.length; - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, nextLink.getLinkId(), - state.driver.getVehicle().getId(), TrackState.RESERVED, track)); - - } else { - return false; - } - - } while (reserved < safety && idx < state.route.size()); + for (RailLink link : links) { + int track = link.reserveTrack(state.driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), + state.driver.getVehicle().getId(), TrackState.RESERVED, track)); + } return true; } @@ -352,11 +340,11 @@ private void updatePosition(double time, UpdateEvent event) { } else if (accelTime < elapsed) { // Travelled distance under constant acceleration - dist = calcTraveledDist(state.speed, accelTime, state.acceleration); + dist = RailsimCalc.calcTraveledDist(state.speed, accelTime, state.acceleration); // Remaining time at constant speed if (state.acceleration > 0) - dist += calcTraveledDist(state.targetSpeed, elapsed - accelTime, 0); + dist += RailsimCalc.calcTraveledDist(state.targetSpeed, elapsed - accelTime, 0); // Target speed was reached state.speed = state.targetSpeed; @@ -365,7 +353,7 @@ private void updatePosition(double time, UpdateEvent event) { } else { // Acceleration was constant the whole time - dist = calcTraveledDist(state.speed, elapsed, state.acceleration); + dist = RailsimCalc.calcTraveledDist(state.speed, elapsed, state.acceleration); state.speed = state.speed + elapsed * state.acceleration; } @@ -395,7 +383,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { double accelDist = Double.POSITIVE_INFINITY; if (state.acceleration > 0 && state.targetSpeed > state.speed) { - accelDist = calcTraveledDist(state.speed, (state.targetSpeed - state.speed) / state.acceleration, state.acceleration); + accelDist = RailsimCalc.calcTraveledDist(state.speed, (state.targetSpeed - state.speed) / state.acceleration, state.acceleration); } // (2) start deceleration @@ -405,16 +393,8 @@ private void decideNextUpdate(double time, UpdateEvent event) { // (3) next link needs reservation double reserveDist = Double.POSITIVE_INFINITY; - if (!state.isRouteAtEnd() && !state.route.get(state.routeIdx).isReserved(state.driver)) { - // time needed for full stop - double stopTime = state.allowedMaxSpeed / state.train.deceleration(); - - assert stopTime > 0 : "Stop time can not be negative"; - - // Distance for full stop - double safetyDist = calcTraveledDist(state.allowedMaxSpeed, stopTime, -state.train.deceleration()); - - reserveDist = currentLink.length - safetyDist - state.headPosition; + if (!state.isRouteAtEnd()) { + reserveDist = reservation.nextUpdate(currentLink, state); } // (4) tail link changes @@ -448,62 +428,9 @@ private void decideNextUpdate(double time, UpdateEvent event) { assert FuzzyUtils.greaterEqualThan(dist, 0) : "Distance for next update must be positive"; // dist is the minimum of all supplied distances - event.plannedTime = time + calcRequiredTime(state, dist); - } - - /** - * Calculate time needed to advance distance {@code dist}. Depending on acceleration and max speed. - */ - private static double calcRequiredTime(TrainState state, double dist) { - - if (state.acceleration == 0) - return state.speed == 0 ? Double.POSITIVE_INFINITY : dist / state.speed; - - if (state.acceleration > 0) { - - double accelTime = (state.targetSpeed - state.speed) / state.acceleration; - - double d = calcTraveledDist(state.speed, accelTime, state.acceleration); - - // The required distance is reached during acceleration - if (d > dist) { - return solveTraveledDist(state.speed, dist, state.acceleration); - - } else - // Time for accel plus remaining dist at max speed - return accelTime + (dist - d) / state.targetSpeed; - - } else { - - double deccelTime = -(state.speed - state.targetSpeed) / state.acceleration; - - // max distance that can be reached - double max = calcTraveledDist(state.speed, deccelTime, state.acceleration); - - if (dist < max) { - return solveTraveledDist(state.speed, dist, state.acceleration); - } else - return deccelTime; - } - } - - - /** - * Calculate traveled distance given initial speed and constant acceleration. - */ - static double calcTraveledDist(double speed, double elapsedTime, double acceleration) { - return speed * elapsedTime + (elapsedTime * elapsedTime * acceleration / 2); + event.plannedTime = time + RailsimCalc.calcRequiredTime(state, dist); } - /** - * Inverse of {@link #calcTraveledDist(double, double, double)}, solves for distance. - */ - static double solveTraveledDist(double speed, double dist, double acceleration) { - if (acceleration == 0) - return dist / speed; - - return (Math.sqrt(2 * acceleration * dist + speed * speed) - speed) / acceleration; - } /** * Calc when deceleration needs to start. @@ -520,7 +447,7 @@ private double calcDeccelDistance(UpdateEvent event) { double assumedSpeed = state.speed; // Lookahead window - double window = calcTraveledDist(assumedSpeed, assumedSpeed / state.train.deceleration(), + double window = RailsimCalc.calcTraveledDist(assumedSpeed, assumedSpeed / state.train.deceleration(), -state.train.deceleration()) + links.get(state.headLink).length; // Distance to the next speed change point (link) @@ -542,7 +469,7 @@ private double calcDeccelDistance(UpdateEvent event) { if (allowed < assumedSpeed) { double timeDeccel = (assumedSpeed - allowed) / state.train.deceleration(); - double newDeccelDist = calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); + double newDeccelDist = RailsimCalc.calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); if ((dist - newDeccelDist) < deccelDist) { deccelDist = dist - newDeccelDist; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java new file mode 100644 index 00000000000..10e82a56765 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java @@ -0,0 +1,51 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; + +import javax.measure.quantity.Pressure; +import java.util.ArrayList; +import java.util.List; + +/** + * Reserve tracks latest as possible. + */ +public class ReservationAtLatestChance implements TrackReservationStrategy { + @Override + public double nextUpdate(RailLink currentLink, TrainState state) { + + // TODO: only having the next link reserved might not be sufficient + if (state.route.get(state.routeIdx).isReserved(state.driver)) + return Double.POSITIVE_INFINITY; + + // time needed for full stop + double stopTime = state.allowedMaxSpeed / state.train.deceleration(); + + assert stopTime > 0 : "Stop time can not be negative"; + + // Distance for full stop + double safetyDist = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, stopTime, -state.train.deceleration()); + + return currentLink.length - safetyDist - state.headPosition; + } + + @Override + public List retrieveLinksToReserve(double time, int idx, TrainState state) { + + List result = new ArrayList<>(); + + double stopTime = state.targetSpeed / state.train.deceleration(); + // safety distance + double safety = RailsimCalc.calcTraveledDist(state.targetSpeed, stopTime, -state.train.deceleration()); + + double reserved = 0; + + do { + RailLink nextLink = state.route.get(idx++); + result.add(nextLink); + reserved += nextLink.length; + + } while (reserved < safety && idx < state.route.size()); + + return result; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java new file mode 100644 index 00000000000..d1d32ef9b32 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java @@ -0,0 +1,23 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import java.util.List; + +/** + * Component to define when and how many tracks should be reserved in advance. + */ +public interface TrackReservationStrategy { + + + /** + * Calculate when the reservation function should be triggered. + * Should return {@link Double#POSITIVE_INFINITY} if this distance is far in the future and can be checked at later point. + * + * @param state current train state. + * @return travel distance after which reservations should be updated. + */ + double nextUpdate(RailLink currentLink, TrainState state); + + + List retrieveLinksToReserve(double time, int idx, TrainState state) ; + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java new file mode 100644 index 00000000000..8c248e226ff --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -0,0 +1,46 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RailsimCalcTest { + + @Test + public void calc() { + + assertThat(RailsimCalc.calcTraveledDist(5, 2, 0)) + .isEqualTo(10); + + assertThat(RailsimCalc.solveTraveledDist(5, 15, 0)) + .isEqualTo(3); + + double d = RailsimCalc.calcTraveledDist(5, 3, 1); + + assertThat(d) + .isEqualTo(19.5); + + assertThat(RailsimCalc.solveTraveledDist(5, 19.5, 1)) + .isEqualTo(3); + + + } + + @Test + public void negative() { + + double d = RailsimCalc.calcTraveledDist(5, 5, -1); + + assertThat(d).isEqualTo(12.5); + + assertThat(RailsimCalc.calcTraveledDist(5, 10, -1)) + .isEqualTo(0); + + double t = RailsimCalc.solveTraveledDist(5, 12.5, -1); + + assertThat(t) + .isEqualTo(5); + + } +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java deleted file mode 100644 index e2f50a16abf..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimStateTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.qsimengine; - -import org.assertj.core.api.Assertions; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class RailsimStateTest { - - @Test - public void calc() { - - assertThat(RailsimEngine.calcTraveledDist(5, 2, 0)) - .isEqualTo(10); - - assertThat(RailsimEngine.solveTraveledDist(5, 15, 0)) - .isEqualTo(3); - - double d = RailsimEngine.calcTraveledDist(5, 3, 1); - - assertThat(d) - .isEqualTo(19.5); - - assertThat(RailsimEngine.solveTraveledDist(5, 19.5, 1)) - .isEqualTo(3); - - - } - - @Test - public void negative() { - - double d = RailsimEngine.calcTraveledDist(5, 5, -1); - - assertThat(d).isEqualTo(12.5); - - assertThat(RailsimEngine.calcTraveledDist(5, 10, -1)) - .isEqualTo(0); - - double t = RailsimEngine.solveTraveledDist(5, 12.5, -1); - - assertThat(t) - .isEqualTo(5); - - } - - @Test - public void deccelDist() { - - Assertions.fail("Implement."); - - } -} From 59b0c481b786259be454efdb575e3b518ec97219 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 17 May 2023 17:07:25 +0200 Subject: [PATCH 031/258] more refactoring, adding todos --- .../railsim/qsimengine/RailsimCalc.java | 56 +++++++++++++ .../railsim/qsimengine/RailsimEngine.java | 79 +++---------------- .../qsimengine/ReservationAtLatestChance.java | 11 ++- .../qsimengine/TrackReservationStrategy.java | 2 +- .../railsim/qsimengine/RailsimEngineTest.java | 4 +- 5 files changed, 77 insertions(+), 75 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 30ab1a121d6..808d05670c5 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -60,4 +60,60 @@ static double calcRequiredTime(TrainState state, double dist) { return deccelTime; } } + + /** + * Calc the distance deceleration needs to start and the target speed. + */ + static double calcDeccelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) { + + // TODO: ignores acceleration that happens + + TrainState state = event.state; + + if (state.speed == 0) + return Double.POSITIVE_INFINITY; + + double assumedSpeed = state.speed; + + // Lookahead window + double window = RailsimCalc.calcTraveledDist(assumedSpeed, assumedSpeed / state.train.deceleration(), + -state.train.deceleration()) + currentLink.length; + + // Distance to the next speed change point (link) + double dist = currentLink.length - state.headPosition; + + double deccelDist = Double.POSITIVE_INFINITY; + double speed = 0; + + for (int i = state.routeIdx; i < state.route.size(); i++) { + + RailLink link = state.route.get(i); + double allowed; + // Last track where train comes to halt + if (i == state.route.size() - 1) + allowed = 0; + else { + allowed = link.getAllowedFreespeed(state.driver); + } + + if (allowed < assumedSpeed) { + double timeDeccel = (assumedSpeed - allowed) / state.train.deceleration(); + double newDeccelDist = RailsimCalc.calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); + + if ((dist - newDeccelDist) < deccelDist) { + deccelDist = dist - newDeccelDist; + speed = allowed; + } + } + + dist += link.length; + + // don't need to look further than distance needed for full stop + if (dist >= window) + break; + } + + event.newSpeed = speed; + return deccelDist; + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 4beec1c00f7..b3196f356d1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -151,6 +151,10 @@ private void updateSpeed(double time, UpdateEvent event) { else if (state.targetSpeed > state.speed) state.acceleration = state.train.acceleration(); + // Remove update information + // TODO: check if needed or not + // event.newSpeed = -1; + eventsManager.processEvent(state.asEvent(time)); decideNextUpdate(time, event); @@ -162,9 +166,7 @@ private void reserveTrack(double time, UpdateEvent event) { updatePosition(time, event); - // TODO: reservation strategy might be put behind interface - - if (!reserveLinkTracks(time, state.routeIdx, state)) { + if (!reserveLinkTracks(time, event.type, state.routeIdx, state)) { // Break when reservation is not possible state.targetSpeed = 0; state.acceleration = -state.train.deceleration(); @@ -179,7 +181,7 @@ private void checkTrackReservation(double time, UpdateEvent event) { TrainState state = event.state; - if (reserveLinkTracks(time, state.routeIdx, state)) { + if (reserveLinkTracks(time, event.type, state.routeIdx, state)) { // TODO: maximum speed could be lower // see enterLink as well @@ -202,7 +204,7 @@ private void updateDeparture(double time, UpdateEvent event) { state.timestamp = time; // for departure only the track has to be free and no tracks in advance - if (reserveLinkTracks(time, 0, state)) { + if (reserveLinkTracks(time, event.type, 0, state)) { state.timestamp = time; @@ -217,9 +219,9 @@ private void updateDeparture(double time, UpdateEvent event) { /** * Reserve links in advance as necessary. */ - private boolean reserveLinkTracks(double time, int idx, TrainState state) { + private boolean reserveLinkTracks(double time, UpdateEvent.Type type, int idx, TrainState state) { - List links = reservation.retrieveLinksToReserve(time, idx, state); + List links = reservation.retrieveLinksToReserve(time, type, idx, state); // All links must be able to be reserved if (!links.stream().allMatch(RailLink::hasFreeTrack)) @@ -279,7 +281,7 @@ private void enterLink(double time, UpdateEvent event) { TrackState.BLOCKED, track)); // TODO: this probably needs to be a separate function to calculate possible target speed more accurately - if (calcDeccelDistance(event) == Double.POSITIVE_INFINITY) { + if (RailsimCalc.calcDeccelDistanceAndSpeed(link, event) == Double.POSITIVE_INFINITY) { state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); state.targetSpeed = state.allowedMaxSpeed; state.acceleration = state.train.acceleration(); @@ -382,12 +384,13 @@ private void decideNextUpdate(double time, UpdateEvent event) { // (1) max speed reached double accelDist = Double.POSITIVE_INFINITY; if (state.acceleration > 0 && state.targetSpeed > state.speed) { - accelDist = RailsimCalc.calcTraveledDist(state.speed, (state.targetSpeed - state.speed) / state.acceleration, state.acceleration); } // (2) start deceleration - double deccelDist = event.newSpeed == state.targetSpeed ? Double.POSITIVE_INFINITY : calcDeccelDistance(event); + double deccelDist = (event.newSpeed == state.targetSpeed) ? + Double.POSITIVE_INFINITY : + RailsimCalc.calcDeccelDistanceAndSpeed(currentLink, event); assert FuzzyUtils.greaterEqualThan(deccelDist, 0) : "Deceleration distance must be larger than 0"; @@ -432,62 +435,6 @@ private void decideNextUpdate(double time, UpdateEvent event) { } - /** - * Calc when deceleration needs to start. - */ - private double calcDeccelDistance(UpdateEvent event) { - - // TODO: pure calculations without updates can probably be put into separate classes - - TrainState state = event.state; - - if (state.speed == 0) - return Double.POSITIVE_INFINITY; - - double assumedSpeed = state.speed; - - // Lookahead window - double window = RailsimCalc.calcTraveledDist(assumedSpeed, assumedSpeed / state.train.deceleration(), - -state.train.deceleration()) + links.get(state.headLink).length; - - // Distance to the next speed change point (link) - double dist = links.get(state.headLink).length - state.headPosition; - - double deccelDist = Double.POSITIVE_INFINITY; - double speed = 0; - - for (int i = state.routeIdx; i < state.route.size(); i++) { - - RailLink link = state.route.get(i); - double allowed; - // Last track where train comes to halt - if (i == state.route.size() - 1) - allowed = 0; - else { - allowed = link.getAllowedFreespeed(state.driver); - } - - if (allowed < assumedSpeed) { - double timeDeccel = (assumedSpeed - allowed) / state.train.deceleration(); - double newDeccelDist = RailsimCalc.calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); - - if ((dist - newDeccelDist) < deccelDist) { - deccelDist = dist - newDeccelDist; - speed = allowed; - } - } - - dist += link.length; - - // don't need to look further than distance needed for full stop - if (dist >= window) - break; - } - - event.newSpeed = speed; - return deccelDist; - } - /** * Allowed speed for the train. */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java index 10e82a56765..f5f06af2b52 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java @@ -1,8 +1,5 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; -import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; - -import javax.measure.quantity.Pressure; import java.util.ArrayList; import java.util.List; @@ -29,13 +26,15 @@ public double nextUpdate(RailLink currentLink, TrainState state) { } @Override - public List retrieveLinksToReserve(double time, int idx, TrainState state) { + public List retrieveLinksToReserve(double time, UpdateEvent.Type type, int idx, TrainState state) { List result = new ArrayList<>(); - double stopTime = state.targetSpeed / state.train.deceleration(); + double assumedSpeed = type == UpdateEvent.Type.DEPARTURE ? state.train.maxVelocity() : state.targetSpeed; + + double stopTime = assumedSpeed / state.train.deceleration(); // safety distance - double safety = RailsimCalc.calcTraveledDist(state.targetSpeed, stopTime, -state.train.deceleration()); + double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()); double reserved = 0; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java index d1d32ef9b32..cd1d62dce60 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java @@ -18,6 +18,6 @@ public interface TrackReservationStrategy { double nextUpdate(RailLink currentLink, TrainState state); - List retrieveLinksToReserve(double time, int idx, TrainState state) ; + List retrieveLinksToReserve(double time, UpdateEvent.Type type, int idx, TrainState state) ; } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 6d1a7dc9bea..84289d5faef 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -68,8 +68,8 @@ public void congested() { RailsimTest.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "regio", 0, "l1-2", "l5-6"); - RailsimTest.createDeparture(test, TestVehicle.Sprinter, "sprinter", 60, "l1-2", "l5-6"); + RailsimTest.createDeparture(test, TestVehicle.Cargo, "cargo", 0, "l1-2", "l5-6"); + RailsimTest.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); test.doSimStepUntil(400); From 056fb3975ef2a5d5e2a3ce986cae94dd3c8b69ac Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 19 May 2023 10:49:44 +0200 Subject: [PATCH 032/258] make sure to reserve enough links in advance --- .../railsim/qsimengine/RailsimCalc.java | 6 ++-- .../railsim/qsimengine/RailsimEngine.java | 23 +++++++++++-- .../qsimengine/ReservationAtLatestChance.java | 23 +++++++++---- .../railsim/qsimengine/RailsimEngineTest.java | 34 +++++++++---------- ...RailsimTest.java => RailsimTestUtils.java} | 4 +-- .../src/test/resources/trainVehicleTypes.xml | 4 +-- 6 files changed, 61 insertions(+), 33 deletions(-) rename contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/{RailsimTest.java => RailsimTestUtils.java} (97%) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 808d05670c5..d843e790d74 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -98,10 +98,10 @@ static double calcDeccelDistanceAndSpeed(RailLink currentLink, UpdateEvent event if (allowed < assumedSpeed) { double timeDeccel = (assumedSpeed - allowed) / state.train.deceleration(); - double newDeccelDist = RailsimCalc.calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); + double newDeccelDist = dist - RailsimCalc.calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); - if ((dist - newDeccelDist) < deccelDist) { - deccelDist = dist - newDeccelDist; + if (newDeccelDist < deccelDist) { + deccelDist = newDeccelDist; speed = allowed; } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index b3196f356d1..60611773c76 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -181,6 +181,9 @@ private void checkTrackReservation(double time, UpdateEvent event) { TrainState state = event.state; + // TODO: May not be needed + updatePosition(time, event); + if (reserveLinkTracks(time, event.type, state.routeIdx, state)) { // TODO: maximum speed could be lower @@ -203,7 +206,6 @@ private void updateDeparture(double time, UpdateEvent event) { TrainState state = event.state; state.timestamp = time; - // for departure only the track has to be free and no tracks in advance if (reserveLinkTracks(time, event.type, 0, state)) { state.timestamp = time; @@ -222,6 +224,10 @@ private void updateDeparture(double time, UpdateEvent event) { private boolean reserveLinkTracks(double time, UpdateEvent.Type type, int idx, TrainState state) { List links = reservation.retrieveLinksToReserve(time, type, idx, state); + links = links.stream().filter(link -> !link.isReserved(state.driver)).toList(); + + if (links.isEmpty()) + return true; // All links must be able to be reserved if (!links.stream().allMatch(RailLink::hasFreeTrack)) @@ -254,7 +260,17 @@ private void enterLink(double time, UpdateEvent event) { // Arrival at destination if (state.isRouteAtEnd()) { - assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; + // TODO: comment back in later +// assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; + + // Free all reservations + for (RailLink link : state.route) { + if (link.isReserved(state.driver)){ + int track = link.releaseTrack(state.driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), state.driver.getVehicle().getId(), + TrackState.FREE, track)); + } + } state.driver.notifyArrivalOnLinkByNonNetworkMode(state.headLink); state.driver.endLegAndComputeNextState(time); @@ -398,6 +414,9 @@ private void decideNextUpdate(double time, UpdateEvent event) { double reserveDist = Double.POSITIVE_INFINITY; if (!state.isRouteAtEnd()) { reserveDist = reservation.nextUpdate(currentLink, state); + + if (reserveDist < 0) + reserveDist = 0; } // (4) tail link changes diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java index f5f06af2b52..3bc916ac9a9 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java @@ -10,10 +10,6 @@ public class ReservationAtLatestChance implements TrackReservationStrategy { @Override public double nextUpdate(RailLink currentLink, TrainState state) { - // TODO: only having the next link reserved might not be sufficient - if (state.route.get(state.routeIdx).isReserved(state.driver)) - return Double.POSITIVE_INFINITY; - // time needed for full stop double stopTime = state.allowedMaxSpeed / state.train.deceleration(); @@ -22,7 +18,20 @@ public double nextUpdate(RailLink currentLink, TrainState state) { // Distance for full stop double safetyDist = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, stopTime, -state.train.deceleration()); - return currentLink.length - safetyDist - state.headPosition; + double dist = -state.headPosition - safetyDist; + int idx = state.routeIdx; + do { + RailLink nextLink = state.route.get(idx++); + dist += nextLink.length; + + if (nextLink.isReserved(state.driver)) + continue; + + return dist; + } while (dist <= safetyDist && idx < state.route.size()); + + // No need to reserve yet + return Double.POSITIVE_INFINITY; } @Override @@ -30,11 +39,11 @@ public List retrieveLinksToReserve(double time, UpdateEvent.Type type, List result = new ArrayList<>(); - double assumedSpeed = type == UpdateEvent.Type.DEPARTURE ? state.train.maxVelocity() : state.targetSpeed; + double assumedSpeed = state.train.maxVelocity(); double stopTime = assumedSpeed / state.train.deceleration(); // safety distance - double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()); + double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) + state.headPosition; double reserved = 0; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 84289d5faef..5bdb664d356 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -18,45 +18,45 @@ public class RailsimEngineTest { public MatsimTestUtils utils = new MatsimTestUtils(); private EventsManager eventsManager; - private RailsimTest.EventCollector collector; + private RailsimTestUtils.EventCollector collector; @Before public void setUp() throws Exception { eventsManager = EventsUtils.createEventsManager(); - collector = new RailsimTest.EventCollector(); + collector = new RailsimTestUtils.EventCollector(); eventsManager.addHandler(collector); eventsManager.initProcessing(); } - private RailsimTest.Holder getTestEngine(String network) { + private RailsimTestUtils.Holder getTestEngine(String network) { Network net = NetworkUtils.readNetwork(new File(utils.getPackageInputDirectory(), network).toString()); collector.clear(); - return new RailsimTest.Holder(new RailsimEngine(eventsManager, new RailsimConfigGroup(), net.getLinks()), net); + return new RailsimTestUtils.Holder(new RailsimEngine(eventsManager, new RailsimConfigGroup(), net.getLinks()), net); } @Test public void simple() { - RailsimTest.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); + RailsimTestUtils.Holder test = getTestEngine("network0.xml"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); test.doSimStepUntil(400); - RailsimTest.assertThat(collector) + RailsimTestUtils.assertThat(collector) .hasSizeGreaterThan(5) .hasTrainState("train", 148, 0, 20) .hasTrainState("train", 188, 200, 0); test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); test.doStateUpdatesUntil(400, 1); - RailsimTest.assertThat(collector) + RailsimTestUtils.assertThat(collector) .hasSizeGreaterThan(5) .hasTrainState("train", 148, 0, 20) .hasTrainState("train", 188, 200, 0); @@ -66,12 +66,12 @@ public void simple() { @Test public void congested() { - RailsimTest.Holder test = getTestEngine("network0.xml"); + RailsimTestUtils.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Cargo, "cargo", 0, "l1-2", "l5-6"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); + RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo", 0, "l1-2", "l5-6"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); - test.doSimStepUntil(400); + test.doSimStepUntil(600); } @@ -79,12 +79,12 @@ public void congested() { @Test public void opposite() { - RailsimTest.Holder test = getTestEngine("network0.xml"); + RailsimTestUtils.Holder test = getTestEngine("network0.xml"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l5-6"); - RailsimTest.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l6-5", "l2-1"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l5-6"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l6-5", "l2-1"); - test.doSimStepUntil(400); + test.doSimStepUntil(600); } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java similarity index 97% rename from contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java rename to contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java index a725658cb4b..477e2af50cf 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java @@ -22,7 +22,7 @@ /** * Helper class for test cases. */ -public class RailsimTest { +public class RailsimTestUtils { static Map vehicles = new EnumMap<>(TestVehicle.class); @@ -32,7 +32,7 @@ public class RailsimTest { Vehicles veh = VehicleUtils.createVehiclesContainer(); MatsimVehicleReader reader = new MatsimVehicleReader(veh); - reader.readURL(RailsimTest.class.getResource("/trainVehicleTypes.xml")); + reader.readURL(RailsimTestUtils.class.getResource("/trainVehicleTypes.xml")); vehicles.put(TestVehicle.Sprinter, veh.getVehicleTypes().get(Id.create("Sprinter", VehicleType.class))); vehicles.put(TestVehicle.Express, veh.getVehicleTypes().get(Id.create("Express", VehicleType.class))); diff --git a/contribs/railsim/src/test/resources/trainVehicleTypes.xml b/contribs/railsim/src/test/resources/trainVehicleTypes.xml index 5634409b399..667f435d87f 100644 --- a/contribs/railsim/src/test/resources/trainVehicleTypes.xml +++ b/contribs/railsim/src/test/resources/trainVehicleTypes.xml @@ -38,8 +38,8 @@ - 0.1 - 0.1 + 0.2 + 0.2 From f2e1018ed432604454f67f2ae0b7a963a4982d33 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 19 May 2023 11:17:24 +0200 Subject: [PATCH 033/258] fix shortest path calc for opposite test case --- .../contrib/railsim/qsimengine/RailsimEngineTest.java | 8 ++++++++ .../contrib/railsim/qsimengine/RailsimTestUtils.java | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 5bdb664d356..e17397e3c5b 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -86,5 +86,13 @@ public void opposite() { test.doSimStepUntil(600); + + test = getTestEngine("network0.xml"); + + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l5-6"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l6-5", "l2-1"); + +// test.doStateUpdatesUntil(600, 1); + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java index 477e2af50cf..e96d0966bad 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java @@ -10,8 +10,8 @@ import org.matsim.core.population.routes.NetworkRoute; import org.matsim.core.population.routes.RouteUtils; import org.matsim.core.router.DijkstraFactory; +import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility; import org.matsim.core.router.util.LeastCostPathCalculator; -import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; import org.matsim.vehicles.*; import org.mockito.Answers; @@ -46,7 +46,7 @@ public class RailsimTestUtils { public static void createDeparture(Holder test, TestVehicle type, String veh, double time, String from, String to) { DijkstraFactory f = new DijkstraFactory(); - LeastCostPathCalculator lcp = f.createPathCalculator(test.network(), Mockito.mock(TravelDisutility.class), new FreeSpeedTravelTime()); + LeastCostPathCalculator lcp = f.createPathCalculator(test.network(), new OnlyTimeDependentTravelDisutility(new FreeSpeedTravelTime()), new FreeSpeedTravelTime()); Link fromLink = test.network.getLinks().get(Id.createLinkId(from)); Link toLink = test.network.getLinks().get(Id.createLinkId(to)); From 1bb12867691e5ee43cc947f329082c12eecc0473 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 19 May 2023 11:48:45 +0200 Subject: [PATCH 034/258] fix typos --- .../railsim/qsimengine/RailsimCalc.java | 13 ++++++---- .../railsim/qsimengine/RailsimEngine.java | 24 +++++++++---------- .../railsim/qsimengine/RailsimQSimEngine.java | 1 - 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index d843e790d74..9f3465d6625 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -25,6 +25,11 @@ static double solveTraveledDist(double speed, double dist, double acceleration) return (Math.sqrt(2 * acceleration * dist + speed * speed) - speed) / acceleration; } + static double solveOptimalSpeed(double dist, double acceleration, double deceleration, + double currentSpeed, double targetSpeed) { + return 0; + } + /** * Calculate time needed to advance distance {@code dist}. Depending on acceleration and max speed. */ @@ -49,22 +54,22 @@ static double calcRequiredTime(TrainState state, double dist) { } else { - double deccelTime = -(state.speed - state.targetSpeed) / state.acceleration; + double decelTime = -(state.speed - state.targetSpeed) / state.acceleration; // max distance that can be reached - double max = calcTraveledDist(state.speed, deccelTime, state.acceleration); + double max = calcTraveledDist(state.speed, decelTime, state.acceleration); if (dist < max) { return solveTraveledDist(state.speed, dist, state.acceleration); } else - return deccelTime; + return decelTime; } } /** * Calc the distance deceleration needs to start and the target speed. */ - static double calcDeccelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) { + static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) { // TODO: ignores acceleration that happens diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 60611773c76..43ba4870fa0 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -181,11 +181,9 @@ private void checkTrackReservation(double time, UpdateEvent event) { TrainState state = event.state; - // TODO: May not be needed - updatePosition(time, event); - if (reserveLinkTracks(time, event.type, state.routeIdx, state)) { + updatePosition(time, event); // TODO: maximum speed could be lower // see enterLink as well @@ -261,7 +259,7 @@ private void enterLink(double time, UpdateEvent event) { if (state.isRouteAtEnd()) { // TODO: comment back in later -// assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; + assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; // Free all reservations for (RailLink link : state.route) { @@ -297,7 +295,7 @@ private void enterLink(double time, UpdateEvent event) { TrackState.BLOCKED, track)); // TODO: this probably needs to be a separate function to calculate possible target speed more accurately - if (RailsimCalc.calcDeccelDistanceAndSpeed(link, event) == Double.POSITIVE_INFINITY) { + if (RailsimCalc.calcDecelDistanceAndSpeed(link, event) == Double.POSITIVE_INFINITY) { state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); state.targetSpeed = state.allowedMaxSpeed; state.acceleration = state.train.acceleration(); @@ -404,11 +402,11 @@ private void decideNextUpdate(double time, UpdateEvent event) { } // (2) start deceleration - double deccelDist = (event.newSpeed == state.targetSpeed) ? + double decelDist = (event.newSpeed == state.targetSpeed) ? Double.POSITIVE_INFINITY : - RailsimCalc.calcDeccelDistanceAndSpeed(currentLink, event); + RailsimCalc.calcDecelDistanceAndSpeed(currentLink, event); - assert FuzzyUtils.greaterEqualThan(deccelDist, 0) : "Deceleration distance must be larger than 0"; + assert FuzzyUtils.greaterEqualThan(decelDist, 0) : "Deceleration distance must be larger than 0"; // (3) next link needs reservation double reserveDist = Double.POSITIVE_INFINITY; @@ -430,16 +428,16 @@ private void decideNextUpdate(double time, UpdateEvent event) { // Find the earliest required update double dist; - if (accelDist <= deccelDist && accelDist <= reserveDist && accelDist <= tailDist && accelDist <= headDist) { + if (accelDist <= decelDist && accelDist <= reserveDist && accelDist <= tailDist && accelDist <= headDist) { dist = accelDist; event.type = UpdateEvent.Type.POSITION; - } else if (deccelDist <= accelDist && deccelDist <= reserveDist && deccelDist <= tailDist && deccelDist <= headDist) { - dist = deccelDist; + } else if (decelDist <= accelDist && decelDist <= reserveDist && decelDist <= tailDist && decelDist <= headDist) { + dist = decelDist; event.type = UpdateEvent.Type.SPEED_CHANGE; - } else if (reserveDist <= accelDist && reserveDist <= deccelDist && reserveDist <= tailDist && reserveDist <= headDist) { + } else if (reserveDist <= accelDist && reserveDist <= decelDist && reserveDist <= tailDist && reserveDist <= headDist) { dist = reserveDist; event.type = UpdateEvent.Type.TRACK_RESERVATION; - } else if (tailDist <= deccelDist && tailDist <= reserveDist && tailDist <= headDist) { + } else if (tailDist <= decelDist && tailDist <= reserveDist && tailDist <= headDist) { dist = tailDist; event.type = UpdateEvent.Type.LEAVE_LINK; } else { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index b642dffdbc7..ea27cd5ca5c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -42,7 +42,6 @@ import org.matsim.core.population.routes.NetworkRoute; import javax.inject.Inject; -import java.util.List; import java.util.Map; import java.util.Set; From 1eadb4f04ba2c8d97ea7a0f76aa52171cf6c1f6b Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 19 May 2023 15:32:34 +0200 Subject: [PATCH 035/258] calculate correct target speed, when max speed can not be achieved --- .../railsim/qsimengine/RailsimCalc.java | 69 +++++++++++++++---- .../railsim/qsimengine/RailsimEngine.java | 1 - .../railsim/qsimengine/RailsimCalcTest.java | 26 ++++++- .../railsim/qsimengine/RailsimEngineTest.java | 10 ++- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 9f3465d6625..3b5d2513160 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -25,11 +25,6 @@ static double solveTraveledDist(double speed, double dist, double acceleration) return (Math.sqrt(2 * acceleration * dist + speed * speed) - speed) / acceleration; } - static double solveOptimalSpeed(double dist, double acceleration, double deceleration, - double currentSpeed, double targetSpeed) { - return 0; - } - /** * Calculate time needed to advance distance {@code dist}. Depending on acceleration and max speed. */ @@ -66,13 +61,46 @@ static double calcRequiredTime(TrainState state, double dist) { } } + /** + * Calculate the maximum speed that can be reached under the condition that speed must be reduced to {@code allowedSpeed} + * again after traveled {@code dist}. + */ + static SpeedTarget calcTargetSpeed(double dist, double acceleration, double deceleration, + double currentSpeed, double targetSpeed, double finalSpeed) { + + assert FuzzyUtils.greaterEqualThan(targetSpeed, finalSpeed) : "Final speed must be smaller than target"; + + double timeDecel = (targetSpeed- finalSpeed) / deceleration; + double distDecel = calcTraveledDist(targetSpeed, timeDecel, -deceleration); + + // This code below only works during deceleration + if (acceleration <= 0 || currentSpeed >= targetSpeed) + return new SpeedTarget(targetSpeed, distDecel); + + double timeAccel = (targetSpeed - currentSpeed) / acceleration; + double distAccel = calcTraveledDist(currentSpeed, timeAccel, acceleration); + + // there is enough distance to accelerate to the target speed + if (distAccel + distDecel < dist) + return new SpeedTarget(targetSpeed, distDecel); + + double nom = 2 * acceleration * deceleration * dist + + acceleration * finalSpeed * finalSpeed + + deceleration * currentSpeed * currentSpeed; + + double v = Math.sqrt(nom / (acceleration + deceleration)); + + timeDecel = (v- finalSpeed) / deceleration; + distDecel = calcTraveledDist(v, timeDecel, -deceleration); + + return new SpeedTarget(v, distDecel); + } + /** * Calc the distance deceleration needs to start and the target speed. */ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) { - // TODO: ignores acceleration that happens - TrainState state = event.state; if (state.speed == 0) @@ -87,7 +115,8 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) // Distance to the next speed change point (link) double dist = currentLink.length - state.headPosition; - double deccelDist = Double.POSITIVE_INFINITY; + double decelDist = Double.POSITIVE_INFINITY; + double targetSpeed = state.targetSpeed; double speed = 0; for (int i = state.routeIdx; i < state.route.size(); i++) { @@ -102,11 +131,14 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) } if (allowed < assumedSpeed) { - double timeDeccel = (assumedSpeed - allowed) / state.train.deceleration(); - double newDeccelDist = dist - RailsimCalc.calcTraveledDist(assumedSpeed, timeDeccel, -state.train.deceleration()); - if (newDeccelDist < deccelDist) { - deccelDist = newDeccelDist; + SpeedTarget target = calcTargetSpeed(dist, state.acceleration, state.train.deceleration(), state.speed, state.targetSpeed, allowed); + + double newDecelDist = dist - target.decelDist; + + if (newDecelDist < decelDist) { + decelDist = newDecelDist; + targetSpeed = target.targetSpeed; speed = allowed; } } @@ -118,7 +150,18 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) break; } + state.targetSpeed = targetSpeed; + event.newSpeed = speed; - return deccelDist; + return decelDist; + } + + record SpeedTarget(double targetSpeed, double decelDist) implements Comparable { + + @Override + public int compareTo(SpeedTarget o) { + return Double.compare(decelDist, o.decelDist); + } } + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 43ba4870fa0..5b33f151fbc 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -258,7 +258,6 @@ private void enterLink(double time, UpdateEvent event) { // Arrival at destination if (state.isRouteAtEnd()) { - // TODO: comment back in later assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; // Free all reservations diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index 8c248e226ff..ac420a46327 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -1,6 +1,6 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; -import org.assertj.core.api.Assertions; +import org.assertj.core.data.Offset; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -43,4 +43,28 @@ public void negative() { .isEqualTo(5); } + + @Test + public void maxSpeed() { + + double dist = 1000; + + double current = 5; + double f = 0; + + RailsimCalc.SpeedTarget res = RailsimCalc.calcTargetSpeed(dist, 0.5, 0.5, + 5, 30, 0); + + System.out.println(res); + + double timeDecel = (res.targetSpeed() - f) / 0.5; + double distDecel = RailsimCalc.calcTraveledDist(res.targetSpeed(), timeDecel, -0.5); + + double timeAccel = (res.targetSpeed() - current) / 0.5; + double distAccel = RailsimCalc.calcTraveledDist(5, timeAccel, 0.5); + + assertThat(distDecel + distAccel) + .isCloseTo(dist, Offset.offset(0.001)); + + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index e17397e3c5b..9932ba92235 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -86,13 +86,21 @@ public void opposite() { test.doSimStepUntil(600); + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio2", 210.7272722504356, 2000, 0) + .hasTrainState("regio1", 348.49615180280205, 200, 0); + test = getTestEngine("network0.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l5-6"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l6-5", "l2-1"); -// test.doStateUpdatesUntil(600, 1); + test.doStateUpdatesUntil(600, 1); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio2", 210.7272722504356, 2000, 0) + .hasTrainState("regio1", 348.49615180280205, 200, 0); } } From 878cd922ec06c4c313c5d38d4baf1b9211fa849d Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Wed, 24 May 2023 00:05:01 +0200 Subject: [PATCH 036/258] define eventhandlers and eventmappers to work with railsim events --- .../RailsimLinkStateChangeEventHandler.java | 27 ++++++++++ .../RailsimTrainStateEventHandler.java | 27 ++++++++++ .../RailsimLinkStateChangeEventMapper.java | 49 +++++++++++++++++ .../RailsimTrainStateEventMapper.java | 52 +++++++++++++++++++ .../events/RailsimLinkStateChangeEvent.java | 17 ++++-- .../events/RailsimTrainStateEvent.java | 36 ++++++++++--- 6 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java new file mode 100644 index 00000000000..de61a24758a --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java @@ -0,0 +1,27 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.eventhandlers; + +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import org.matsim.core.events.handler.EventHandler; + +public interface RailsimLinkStateChangeEventHandler extends EventHandler { + void handleEvent(RailsimLinkStateChangeEvent event); +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java new file mode 100644 index 00000000000..f775b70ba87 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java @@ -0,0 +1,27 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.eventhandlers; + +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; +import org.matsim.core.events.handler.EventHandler; + +public interface RailsimTrainStateEventHandler extends EventHandler { + void handleEvent(RailsimTrainStateEvent event); +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java new file mode 100644 index 00000000000..7739f80cede --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java @@ -0,0 +1,49 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.eventmappers; + +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import ch.sbb.matsim.contrib.railsim.qsimengine.TrackState; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.vehicles.Vehicle; + +public class RailsimLinkStateChangeEventMapper implements MatsimEventsReader.CustomEventMapper { + @Override + public RailsimLinkStateChangeEvent apply(GenericEvent event) { + var attributes = event.getAttributes(); + return new RailsimLinkStateChangeEvent( + event.getTime(), + asId(attributes.get(RailsimLinkStateChangeEvent.ATTRIBUTE_LINK), Link.class), + asId(attributes.get(RailsimLinkStateChangeEvent.ATTRIBUTE_VEHICLE), Vehicle.class), + TrackState.valueOf(attributes.get(RailsimLinkStateChangeEvent.ATTRIBUTE_STATE)), + Integer.parseInt(attributes.get(RailsimLinkStateChangeEvent.ATTRIBUTE_TRACK)) + ); + } + + private static Id asId(String value, Class idClass) { + if (value == null) { + return null; + } + return Id.create(value, idClass); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java new file mode 100644 index 00000000000..586c9283aa3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java @@ -0,0 +1,52 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.eventmappers; + +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.vehicles.Vehicle; + +public class RailsimTrainStateEventMapper implements MatsimEventsReader.CustomEventMapper { + @Override + public RailsimTrainStateEvent apply(GenericEvent event) { + var attributes = event.getAttributes(); + return new RailsimTrainStateEvent( + event.getTime(), + asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_VEHICLE), Vehicle.class), + asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEADLINK), Link.class), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEADPOSITION)), + asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TAILLINK), Link.class), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TAILPOSITION)), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_SPEED)), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_ACCELERATION)), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TARGETSPEED)) + ); + } + + private static Id asId(String value, Class idClass) { + if (value == null) { + return null; + } + return Id.create(value, idClass); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java index 939848dd5cf..47114bfaf7b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java @@ -17,6 +17,9 @@ public class RailsimLinkStateChangeEvent extends Event implements HasLinkId, Has public static final String EVENT_TYPE = "railsimLinkStateChangeEvent"; + public static final String ATTRIBUTE_STATE = "state"; + public static final String ATTRIBUTE_TRACK = "track"; + private final Id linkId; private final Id vehicleId; private final TrackState state; @@ -42,7 +45,15 @@ public Id getLinkId() { @Override public Id getVehicleId() { - return null; + return this.vehicleId; + } + + public TrackState getState() { + return state; + } + + public int getTrack() { + return track; } @Override @@ -50,8 +61,8 @@ public Map getAttributes() { Map attr = super.getAttributes(); attr.put(ATTRIBUTE_LINK, this.linkId.toString()); attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); - attr.put("state", this.state.toString()); - attr.put("track", String.valueOf(track)); + attr.put(ATTRIBUTE_STATE, this.state.toString()); + attr.put(ATTRIBUTE_TRACK, String.valueOf(track)); return attr; } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java index af9dd296cf3..6b773cab57d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -15,6 +15,14 @@ public class RailsimTrainStateEvent extends Event implements HasVehicleId { public static final String EVENT_TYPE = "railsimTrainStateEvent"; + public static final String ATTRIBUTE_HEADLINK = "headLink"; + public static final String ATTRIBUTE_HEADPOSITION = "headPosition"; + public static final String ATTRIBUTE_TAILLINK = "tailLink"; + public static final String ATTRIBUTE_TAILPOSITION = "tailPosition"; + public static final String ATTRIBUTE_SPEED = "speed"; + public static final String ATTRIBUTE_ACCELERATION = "acceleration"; + public static final String ATTRIBUTE_TARGETSPEED = "targetSpeed"; + private final Id vehicleId; private final Id headLink; private final double headPosition; @@ -61,21 +69,33 @@ public double getSpeed() { return speed; } + public double getTargetSpeed() { + return this.targetSpeed; + } + public double getAcceleration() { - return acceleration; + return this.acceleration; + } + + public Id getHeadLink() { + return this.headLink; + } + + public Id getTailLink() { + return this.tailLink; } @Override public Map getAttributes() { Map attr = super.getAttributes(); attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); - attr.put("headLink", String.valueOf(headLink)); - attr.put("headPosition", Double.toString(headPosition)); - attr.put("tailLink", String.valueOf(tailLink)); - attr.put("tailPosition", Double.toString(tailPosition)); - attr.put("speed", Double.toString(speed)); - attr.put("acceleration", Double.toString(acceleration)); - attr.put("targetSpeed", Double.toString(targetSpeed)); + attr.put(ATTRIBUTE_HEADLINK, String.valueOf(headLink)); + attr.put(ATTRIBUTE_HEADPOSITION, Double.toString(headPosition)); + attr.put(ATTRIBUTE_TAILLINK, String.valueOf(tailLink)); + attr.put(ATTRIBUTE_TAILPOSITION, Double.toString(tailPosition)); + attr.put(ATTRIBUTE_SPEED, Double.toString(speed)); + attr.put(ATTRIBUTE_ACCELERATION, Double.toString(acceleration)); + attr.put(ATTRIBUTE_TARGETSPEED, Double.toString(targetSpeed)); return attr; } } From b9aa14c7b90efa0a5b3c1056a2eaa592aa86fd33 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Wed, 24 May 2023 00:08:45 +0200 Subject: [PATCH 037/258] add 2 railsim analyses for link- and train-states --- .../matsim/contrib/railsim/RailsimModule.java | 4 + .../railsim/analysis/PostProcessAnalysis.java | 77 +++++++++++++++++ .../linkstates/RailLinkStateAnalysis.java | 37 ++++++++ .../linkstates/RailLinkStateWriter.java | 49 +++++++++++ .../RailsimLinkStateAnalysisModule.java | 31 +++++++ .../RailsimLinkStateControlerListener.java | 58 +++++++++++++ .../RailsimTrainStateAnalysisModule.java | 31 +++++++ .../RailsimTrainStateControlerListener.java | 58 +++++++++++++ .../trainstates/TrainStateAnalysis.java | 37 ++++++++ .../trainstates/TrainStateWriter.java | 85 +++++++++++++++++++ 10 files changed, 467 insertions(+) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateWriter.java diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java index 27a01358868..4e4bed746c4 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java @@ -19,6 +19,8 @@ package ch.sbb.matsim.contrib.railsim; +import ch.sbb.matsim.contrib.railsim.analysis.linkstates.RailsimLinkStateAnalysisModule; +import ch.sbb.matsim.contrib.railsim.analysis.trainstates.RailsimTrainStateAnalysisModule; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; import org.matsim.core.config.ConfigUtils; @@ -30,5 +32,7 @@ public class RailsimModule extends AbstractModule { public void install() { installQSimModule(new RailsimQSimModule()); ConfigUtils.addOrGetModule(getConfig(), RailsimConfigGroup.class); + install(new RailsimLinkStateAnalysisModule()); + install(new RailsimTrainStateAnalysisModule()); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java new file mode 100644 index 00000000000..c0b40d76fb5 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java @@ -0,0 +1,77 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis; + +import ch.sbb.matsim.contrib.railsim.analysis.linkstates.RailLinkStateAnalysis; +import ch.sbb.matsim.contrib.railsim.analysis.linkstates.RailLinkStateWriter; +import ch.sbb.matsim.contrib.railsim.analysis.trainstates.TrainStateAnalysis; +import ch.sbb.matsim.contrib.railsim.analysis.trainstates.TrainStateWriter; +import ch.sbb.matsim.contrib.railsim.eventmappers.RailsimLinkStateChangeEventMapper; +import ch.sbb.matsim.contrib.railsim.eventmappers.RailsimTrainStateEventMapper; +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.core.network.io.MatsimNetworkReader; +import org.matsim.core.scenario.ScenarioUtils; + +public class PostProcessAnalysis { + + public static void main(String[] args) { + String eventsFilename; + String networkFilename = null; + if (args.length > 0) { + eventsFilename = args[0]; + } else { + System.err.println("Please provide events filename."); + System.exit(2); + return; + } + if (args.length > 1) { + networkFilename = args[1]; + } + + Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig()); + if (networkFilename != null) { + new MatsimNetworkReader(scenario.getNetwork()).readFile(networkFilename); + } + + RailLinkStateAnalysis linkStateAnalysis = new RailLinkStateAnalysis(); + TrainStateAnalysis trainStateAnalysis = new TrainStateAnalysis(); + + EventsManager events = EventsUtils.createEventsManager(); + events.addHandler(linkStateAnalysis); + events.addHandler(trainStateAnalysis); + events.initProcessing(); + + MatsimEventsReader reader = new MatsimEventsReader(events); + reader.addCustomEventMapper(RailsimLinkStateChangeEvent.EVENT_TYPE, new RailsimLinkStateChangeEventMapper()); + reader.addCustomEventMapper(RailsimTrainStateEvent.EVENT_TYPE, new RailsimTrainStateEventMapper()); + reader.readFile(eventsFilename); + + events.finishProcessing(); + + RailLinkStateWriter.writeCsv(linkStateAnalysis, "railsimLinkStates.csv"); + TrainStateWriter.writeCsv(trainStateAnalysis, scenario.getNetwork(), "railsimTrainStates.csv"); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java new file mode 100644 index 00000000000..2305226743e --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java @@ -0,0 +1,37 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.linkstates; + +import ch.sbb.matsim.contrib.railsim.eventhandlers.RailsimLinkStateChangeEventHandler; +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; + +import java.util.ArrayList; +import java.util.List; + +public class RailLinkStateAnalysis implements RailsimLinkStateChangeEventHandler { + + final List events = new ArrayList<>(1000); + + @Override + public void handleEvent(RailsimLinkStateChangeEvent event) { + this.events.add(event); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java new file mode 100644 index 00000000000..d1e386fb94f --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java @@ -0,0 +1,49 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.linkstates; + +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.matsim.core.utils.io.IOUtils; + +import java.io.IOException; +import java.io.UncheckedIOException; + +public class RailLinkStateWriter { + + public static void writeCsv(RailLinkStateAnalysis analysis, String filename) throws UncheckedIOException { + String[] header = {"link", "time", "state", "vehicle", "track"}; + + try (CSVPrinter csv = new CSVPrinter(IOUtils.getBufferedWriter(filename), CSVFormat.DEFAULT.builder().setHeader(header).build())) { + for (RailsimLinkStateChangeEvent event : analysis.events) { + csv.print(event.getLinkId().toString()); + csv.print(event.getTime()); + csv.print(event.getState().toString()); + csv.print(event.getVehicleId() != null ? event.getVehicleId().toString() : ""); + csv.print(event.getTrack()); + csv.println(); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java new file mode 100644 index 00000000000..66a2be4de8e --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java @@ -0,0 +1,31 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.linkstates; + +import com.google.inject.Singleton; +import org.matsim.core.controler.AbstractModule; + +public final class RailsimLinkStateAnalysisModule extends AbstractModule { + @Override + public void install() { + bind(RailsimLinkStateControlerListener.class).in(Singleton.class); + addControlerListenerBinding().to(RailsimLinkStateControlerListener.class); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java new file mode 100644 index 00000000000..33b38915061 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java @@ -0,0 +1,58 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.linkstates; + +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.events.IterationStartsEvent; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.controler.listener.IterationStartsListener; + +import javax.inject.Inject; + +public final class RailsimLinkStateControlerListener implements IterationEndsListener, IterationStartsListener { + + private RailLinkStateAnalysis analysis; + private OutputDirectoryHierarchy controlerIO; + private final EventsManager eventsManager; + private final Scenario scenario; + + @Inject + RailsimLinkStateControlerListener(Scenario scenario, EventsManager eventsManager, OutputDirectoryHierarchy controlerIO) { + this.eventsManager = eventsManager; + this.controlerIO = controlerIO; + this.scenario = scenario; + this.analysis = new RailLinkStateAnalysis(); + } + + @Override + public void notifyIterationStarts(IterationStartsEvent event) { + this.eventsManager.addHandler(this.analysis); + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + String railLinkStatesCsvFilename = this.controlerIO.getIterationFilename(event.getIteration(), "railsimLinkStates.csv", this.scenario.getConfig().controler().getCompressionType()); + RailLinkStateWriter.writeCsv(this.analysis, railLinkStatesCsvFilename); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java new file mode 100644 index 00000000000..8105bc2a09f --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java @@ -0,0 +1,31 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.trainstates; + +import com.google.inject.Singleton; +import org.matsim.core.controler.AbstractModule; + +public final class RailsimTrainStateAnalysisModule extends AbstractModule { + @Override + public void install() { + bind(RailsimTrainStateControlerListener.class).in(Singleton.class); + addControlerListenerBinding().to(RailsimTrainStateControlerListener.class); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java new file mode 100644 index 00000000000..7c0cf970e70 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java @@ -0,0 +1,58 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.trainstates; + +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.events.IterationStartsEvent; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.controler.listener.IterationStartsListener; + +import javax.inject.Inject; + +public final class RailsimTrainStateControlerListener implements IterationEndsListener, IterationStartsListener { + + private TrainStateAnalysis analysis; + private OutputDirectoryHierarchy controlerIO; + private final EventsManager eventsManager; + private final Scenario scenario; + + @Inject + RailsimTrainStateControlerListener(Scenario scenario, EventsManager eventsManager, OutputDirectoryHierarchy controlerIO) { + this.eventsManager = eventsManager; + this.controlerIO = controlerIO; + this.scenario = scenario; + this.analysis = new TrainStateAnalysis(); + } + + @Override + public void notifyIterationStarts(IterationStartsEvent event) { + this.eventsManager.addHandler(this.analysis); + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + String railLinkStatesCsvFilename = this.controlerIO.getIterationFilename(event.getIteration(), "trainStates.csv", this.scenario.getConfig().controler().getCompressionType()); + TrainStateWriter.writeCsv(this.analysis, this.scenario.getNetwork(), railLinkStatesCsvFilename); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java new file mode 100644 index 00000000000..e1bb46c99a3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java @@ -0,0 +1,37 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.trainstates; + +import ch.sbb.matsim.contrib.railsim.eventhandlers.RailsimTrainStateEventHandler; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; + +import java.util.ArrayList; +import java.util.List; + +public class TrainStateAnalysis implements RailsimTrainStateEventHandler { + + final List events = new ArrayList<>(1000); + + @Override + public void handleEvent(RailsimTrainStateEvent event) { + this.events.add(event); + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateWriter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateWriter.java new file mode 100644 index 00000000000..2921c3ce0bd --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateWriter.java @@ -0,0 +1,85 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package ch.sbb.matsim.contrib.railsim.analysis.trainstates; + +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.utils.io.IOUtils; + +import java.io.IOException; +import java.io.UncheckedIOException; + +public class TrainStateWriter { + + public static void writeCsv(TrainStateAnalysis analysis, Network network, String filename) throws UncheckedIOException { + String[] header = {"vehicle", "time", "acceleration", "speed", "targetSpeed", "headLink", "headPosition", "headX", "headY", "tailLink", "tailPosition", "tailX", "tailY"}; + + try (CSVPrinter csv = new CSVPrinter(IOUtils.getBufferedWriter(filename), CSVFormat.DEFAULT.builder().setHeader(header).build())) { + for (RailsimTrainStateEvent event : analysis.events) { + csv.print(event.getVehicleId().toString()); + csv.print(event.getTime()); + csv.print(event.getAcceleration()); + csv.print(event.getSpeed()); + csv.print(event.getTargetSpeed()); + + csv.print(event.getHeadLink().toString()); + csv.print(event.getHeadPosition()); + if (network != null) { + Link link = network.getLinks().get(event.getHeadLink()); + if (link != null) { + double fraction = event.getHeadPosition() / link.getLength(); + Coord from = link.getFromNode().getCoord(); + Coord to = link.getToNode().getCoord(); + csv.print(from.getX() + (to.getX() - from.getX()) * fraction); + csv.print(from.getY() + (to.getY() - from.getY()) * fraction); + } + } else { + csv.print(""); + csv.print(""); + } + + csv.print(event.getTailLink().toString()); + csv.print(event.getTailPosition()); + if (network != null) { + Link link = network.getLinks().get(event.getTailLink()); + if (link != null) { + double fraction = event.getTailPosition() / link.getLength(); + Coord from = link.getFromNode().getCoord(); + Coord to = link.getToNode().getCoord(); + csv.print(from.getX() + (to.getX() - from.getX()) * fraction); + csv.print(from.getY() + (to.getY() - from.getY()) * fraction); + } + } else { + csv.print(""); + csv.print(""); + } + + csv.println(); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } +} From 38d1cdf9aa1c8cdd9e9f08e29e6a48903e5c3e77 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Wed, 24 May 2023 00:10:45 +0200 Subject: [PATCH 038/258] include additional events for compatibility reasons --- .../matsim/contrib/railsim/qsimengine/RailsimEngine.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 5b33f151fbc..8dd1ce31ba1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -10,6 +10,8 @@ import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.events.LinkEnterEvent; import org.matsim.api.core.v01.events.LinkLeaveEvent; +import org.matsim.api.core.v01.events.PersonEntersVehicleEvent; +import org.matsim.api.core.v01.events.VehicleEntersTrafficEvent; import org.matsim.api.core.v01.network.Link; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimDriverAgent; @@ -107,6 +109,11 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin log.info("Train {} is departing", agent.getVehicle()); + this.eventsManager.processEvent(new PersonEntersVehicleEvent(now, agent.getId(), agent.getVehicle().getId())); +// if (this.createLinkEvents) { + this.eventsManager.processEvent(new VehicleEntersTrafficEvent(now, agent.getId(), linkId, agent.getVehicle().getId(), agent.getMode(), 1.0)); +// } + List list = route.getLinkIds().stream().map(links::get).collect(Collectors.toList()); list.add(0, links.get(linkId)); list.add(links.get(route.getEndLinkId())); From a788ac6c07b541123b15688dfeb1f9a396703281 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Wed, 24 May 2023 00:11:24 +0200 Subject: [PATCH 039/258] prevent endless loop due to floating point arithmetics / loss of precision --- .../contrib/railsim/qsimengine/FuzzyUtils.java | 14 ++++++++++++++ .../contrib/railsim/qsimengine/RailsimEngine.java | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java index 52d2d62a0e2..a67741db085 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java @@ -26,6 +26,13 @@ public static boolean greaterEqualThan(double a, double b) { return equals(a, b) || a - b > EPSILON; } + /** + * Returns true if the first double is certainly greater than the second. + */ + public static boolean greaterThan(double a, double b) { + return a - b > EPSILON; + } + /** * Returns true if the first double is approximately less than the second. @@ -34,4 +41,11 @@ public static boolean lessEqualThan(double a, double b) { return equals(a, b) || b - a > EPSILON; } + /** + * Returns true if the first double is approximately less than the second. + */ + public static boolean lessThan(double a, double b) { + return b - a > EPSILON; + } + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 8dd1ce31ba1..e99405026ab 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -378,6 +378,11 @@ private void updatePosition(double time, UpdateEvent event) { dist = RailsimCalc.calcTraveledDist(state.speed, elapsed, state.acceleration); state.speed = state.speed + elapsed * state.acceleration; + if (FuzzyUtils.equals(state.speed, state.targetSpeed)) { + state.speed = state.targetSpeed; + state.acceleration = 0; + } + } assert FuzzyUtils.greaterEqualThan(dist, 0) : "Travel distance must be positive, but was" + dist; @@ -403,7 +408,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { // (1) max speed reached double accelDist = Double.POSITIVE_INFINITY; - if (state.acceleration > 0 && state.targetSpeed > state.speed) { + if (state.acceleration > 0 && FuzzyUtils.greaterThan(state.targetSpeed, state.speed)) { accelDist = RailsimCalc.calcTraveledDist(state.speed, (state.targetSpeed - state.speed) / state.acceleration, state.acceleration); } From 5441569f692290929fa86cd60dc9151fab7dc35f Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Wed, 24 May 2023 00:13:14 +0200 Subject: [PATCH 040/258] additional get*Filename methods with support for CompressionTypes --- .../controler/OutputDirectoryHierarchy.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/matsim/src/main/java/org/matsim/core/controler/OutputDirectoryHierarchy.java b/matsim/src/main/java/org/matsim/core/controler/OutputDirectoryHierarchy.java index b156b95d4cc..40465612b13 100644 --- a/matsim/src/main/java/org/matsim/core/controler/OutputDirectoryHierarchy.java +++ b/matsim/src/main/java/org/matsim/core/controler/OutputDirectoryHierarchy.java @@ -35,9 +35,9 @@ import javax.inject.Inject; /** - * + * * Represents the directory hierarchy where the MATSim output goes in. - * + * * @author dgrether, michaz * */ @@ -46,15 +46,15 @@ public final class OutputDirectoryHierarchy { public enum OverwriteFileSetting {failIfDirectoryExists, overwriteExistingFiles, deleteDirectoryIfExists} private static final String DIRECTORY_ITERS = "ITERS"; - + private static final Logger log = LogManager.getLogger(OutputDirectoryHierarchy.class); - + private String runId = null; - + private final String outputPath; private final ControlerConfigGroup.CompressionType defaultCompressionType; - + private OverwriteFileSetting overwriteFiles = OverwriteFileSetting.failIfDirectoryExists; @Inject @@ -78,12 +78,12 @@ public OutputDirectoryHierarchy( Config config ) { public OutputDirectoryHierarchy(String outputPath, OverwriteFileSetting overwriteFiles, ControlerConfigGroup.CompressionType defaultCompressionType) { this(outputPath, null, overwriteFiles, true, defaultCompressionType); } - + public OutputDirectoryHierarchy(String outputPath, String runId, OverwriteFileSetting overwriteFiles, ControlerConfigGroup.CompressionType defaultCompressionType) { this(outputPath, runId, overwriteFiles, true, defaultCompressionType); - } + } /** - * + * * @param runId the runId, may be null * @param overwriteFiles overwrite existing files instead of crashing * @param outputPath the path to the output directory @@ -144,6 +144,13 @@ public final String getIterationFilename(final int iteration, final String filen return s.toString(); } + public String getIterationFilename(int iteration, String filename, ControlerConfigGroup.CompressionType compression) { + if (compression == null) { + return getIterationFilename(iteration, filename); + } + return getIterationFilename(iteration, filename + compression.fileEnding); + } + public final String getIterationFilename(int iteration, Controler.DefaultFiles file) { return getIterationFilename(iteration, file, this.defaultCompressionType); } @@ -154,7 +161,7 @@ public final String getIterationFilename(int iteration, Controler.DefaultFiles f } return getIterationFilename(iteration, file.filename + compression.fileEnding); } - + /** * Returns the complete filename to access a file in the output-directory. * @@ -173,6 +180,13 @@ public final String getOutputFilename(final String filename) { return s.toString(); } + public String getOutputFilename(String filename, ControlerConfigGroup.CompressionType compression) { + if (compression == null) { + return getOutputFilename(filename); + } + return getOutputFilename(filename + compression.fileEnding); + } + public final String getOutputFilenameWithOutputPrefix(final String filename) { return getOutputFilename(Controler.OUTPUT_PREFIX + filename); } @@ -188,7 +202,7 @@ public final String getOutputFilename(Controler.DefaultFiles file, ControlerConf return getOutputFilename(Controler.OUTPUT_PREFIX + file.filename + compression.fileEnding); } - + public String getOutputPath() { return outputPath; } @@ -225,7 +239,7 @@ public void deleteIterationDirectory() { log.warn("Could not delete iteration directory " + path + "."); } } - + private void createDirectories() { File outputDir = new File(outputPath); if (outputDir.exists()) { @@ -280,7 +294,7 @@ private void createDirectories() { "The output directory path " + outputPath + " could not be created. Check pathname and permissions! Full path: " + new File(outputPath).getAbsolutePath()); } - + File tmpDir = new File(getTempPath()); if (!tmpDir.mkdir() && !tmpDir.exists()) { throw new RuntimeException("The tmp directory " @@ -293,5 +307,5 @@ private void createDirectories() { + " could not be created."); } } - + } From 6e10d2f3cfae73ef9e8ebe8e95a3f5de368735f2 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 25 May 2023 14:44:40 +0200 Subject: [PATCH 041/258] introduce resources and interface for the disposition --- .../railsim/docs/network-specification.md | 22 ++-- .../matsim/contrib/railsim/RailsimUtils.java | 17 ++- .../contrib/railsim/RunRailsimExample.java | 24 ++++ .../railsim/analysis/PostProcessAnalysis.java | 6 +- ...StateWriter.java => RailsimCsvWriter.java} | 48 ++++---- .../linkstates/RailLinkStateAnalysis.java | 3 + .../linkstates/RailLinkStateWriter.java | 49 -------- .../RailsimLinkStateControlerListener.java | 3 +- .../RailsimTrainStateControlerListener.java | 3 +- .../trainstates/TrainStateAnalysis.java | 3 + .../contrib/railsim/qsimengine/RailLink.java | 78 ++++++++----- .../railsim/qsimengine/RailResource.java | 43 +++++++ .../qsimengine/RailResourceManager.java | 97 ++++++++++++++++ .../railsim/qsimengine/RailsimCalc.java | 62 ++++++++++ .../railsim/qsimengine/RailsimEngine.java | 106 ++++++++---------- .../railsim/qsimengine/RailsimQSimEngine.java | 13 +-- .../qsimengine/ReservationAtLatestChance.java | 59 ---------- .../qsimengine/TrackReservationStrategy.java | 23 ---- .../railsim/qsimengine/TrackState.java | 7 +- .../railsim/qsimengine/UpdateEvent.java | 4 +- .../diposition/SimpleDisposition.java | 72 ++++++++++++ .../diposition/TrainDisposition.java | 33 ++++++ .../qsimengine/router/TrainRouter.java | 10 ++ .../railsim/qsimengine/RailsimEngineTest.java | 8 +- .../railsim/qsimengine/RailsimTestUtils.java | 27 +++++ .../contrib/railsim/qsimengine/network0.xml | 4 +- 26 files changed, 541 insertions(+), 283 deletions(-) rename contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/{trainstates/TrainStateWriter.java => RailsimCsvWriter.java} (57%) delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/TrainDisposition.java create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index ebcea0f4cc3..912afe9f047 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -21,24 +21,30 @@ We try to use the prefix `railsim` where it is appropriate. TODO -#### trainCapacity +#### railsimTrainCapacity The number of trains that can be on this link at the same time. If the attribute is not provided, a default of 1 is used. -#### trainOppositeDirectionLink +#### railsimResourceId -The id of a link leading in the opposite direction of this link. +The id of a resource, i.e. a segment of links that share a constant capacity. +This can be used to denote certain blocks of links that can only be reserved as a whole. +One use-case is the modelling of bidirectional links, where one train blocks both directions. MATSim uses uni-directional links. While on a road, cars might usually be able to pass each other even on small roads by going very slow and near the edge, but trains cannot. -In order to correctly simulate the simulation where a train blocks a bidirectional track -#### maxSpeed +This can also be used to model intersections as one resource, which will restrict crossing trains. + +The train capacity will be derived as the minimum of all included links of this resource. +Links that have no resource id will be handled as individual resource. + +#### railsimMaxSpeed TODO -#### minimumTime +#### railsimMinimumTime The minimum time ("minimum train headway time") for the switch at the end of the link (toNode). If no link attribute is provided, a default of 0 is used. @@ -79,14 +85,14 @@ states to avoid collisions. Do we really want that? modes="rail"> 1 - B_A + AB 1 - A_B + AB diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 4bd8921e8cf..78332055772 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -17,7 +17,7 @@ public final class RailsimUtils { // link public static final String LINK_ATTRIBUTE_GRADE = "railsimGrade"; - public static final String LINK_ATTRIBUTE_OPPOSITE_DIRECTION = "railsimTrainOppositeDirectionLink"; + public static final String LINK_ATTRIBUTE_RESOURCE_ID = "railsimResourceId"; public static final String LINK_ATTRIBUTE_CAPACITY = "railsimTrainCapacity"; public static final String LINK_ATTRIBUTE_MAX_SPEED = "railsimMaxSpeed"; public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "railsimMinimumTime"; @@ -64,6 +64,13 @@ public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGrou return attr != null ? (double) attr : acceleration; } + /** + * Resource id or null if there is none. + */ + public static String getResourceId(Link link) { + return (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_RESOURCE_ID); + } + /** * @return the vehicle-specific freespeed or 0 if there is no vehicle-specific freespeed provided in the link attributes */ @@ -100,12 +107,4 @@ public static double getLinkFreespeedForTransitLineAndTransitRoute(Id getOppositeDirectionLink(Link link) { - String oppositeLink = (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION); - return oppositeLink != null ? Id.createLinkId(oppositeLink) : null; - } - } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java index 962a96de921..402a13f31a8 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java @@ -33,6 +33,30 @@ */ public class RunRailsimExample { + // TODO: Vehicle + // vehicle should start on link end and go directly to next + // vehicle drives the last link completely + + // TODO: Zwischenebene mit Segmenten + + // TODO: blockId attribute für links + + // TODO: Kreuzungsweiche, Knoten mit Belegungslogik + + // TODO: Alle x Sekunden links vorreservieren + + // 1 extrem fall Reservation für ganze strecke im vorraus + + // bei train departure wird zukünftiger fahrweg mit übergeben + + // Disposition interface + + // TODO: tail link umstellen auf entfernung from node + // head position = 0, link länge - zug länge + + // TODO: erste link darf auch negativ sein + // run railsim example + public static void main(String[] args) { if (args.length == 0) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java index c0b40d76fb5..3b779af9f8c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java @@ -20,9 +20,7 @@ package ch.sbb.matsim.contrib.railsim.analysis; import ch.sbb.matsim.contrib.railsim.analysis.linkstates.RailLinkStateAnalysis; -import ch.sbb.matsim.contrib.railsim.analysis.linkstates.RailLinkStateWriter; import ch.sbb.matsim.contrib.railsim.analysis.trainstates.TrainStateAnalysis; -import ch.sbb.matsim.contrib.railsim.analysis.trainstates.TrainStateWriter; import ch.sbb.matsim.contrib.railsim.eventmappers.RailsimLinkStateChangeEventMapper; import ch.sbb.matsim.contrib.railsim.eventmappers.RailsimTrainStateEventMapper; import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; @@ -71,7 +69,7 @@ public static void main(String[] args) { events.finishProcessing(); - RailLinkStateWriter.writeCsv(linkStateAnalysis, "railsimLinkStates.csv"); - TrainStateWriter.writeCsv(trainStateAnalysis, scenario.getNetwork(), "railsimTrainStates.csv"); + RailsimCsvWriter.writeLinkStatesCsv(linkStateAnalysis.getEvents(), "railsimLinkStates.csv"); + RailsimCsvWriter.writeTrainStatesCsv(trainStateAnalysis.getEvents(), scenario.getNetwork(), "railsimTrainStates.csv"); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateWriter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java similarity index 57% rename from contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateWriter.java rename to contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java index 2921c3ce0bd..b5353c60ed7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateWriter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java @@ -1,24 +1,6 @@ -/* *********************************************************************** * - * project: org.matsim.* - * * - * *********************************************************************** * - * * - * copyright : (C) 2023 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package ch.sbb.matsim.contrib.railsim.analysis.trainstates; +package ch.sbb.matsim.contrib.railsim.analysis; +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; @@ -29,14 +11,33 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.util.List; + +public class RailsimCsvWriter { -public class TrainStateWriter { + public static void writeLinkStatesCsv(List events, String filename) throws UncheckedIOException { + String[] header = {"link", "time", "state", "vehicle", "track"}; - public static void writeCsv(TrainStateAnalysis analysis, Network network, String filename) throws UncheckedIOException { + try (CSVPrinter csv = new CSVPrinter(IOUtils.getBufferedWriter(filename), CSVFormat.DEFAULT.builder().setHeader(header).build())) { + for (RailsimLinkStateChangeEvent event : events) { + csv.print(event.getLinkId().toString()); + csv.print(event.getTime()); + csv.print(event.getState().toString()); + csv.print(event.getVehicleId() != null ? event.getVehicleId().toString() : ""); + csv.print(event.getTrack()); + csv.println(); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } + + public static void writeTrainStatesCsv(List events, Network network, String filename) throws UncheckedIOException { String[] header = {"vehicle", "time", "acceleration", "speed", "targetSpeed", "headLink", "headPosition", "headX", "headY", "tailLink", "tailPosition", "tailX", "tailY"}; try (CSVPrinter csv = new CSVPrinter(IOUtils.getBufferedWriter(filename), CSVFormat.DEFAULT.builder().setHeader(header).build())) { - for (RailsimTrainStateEvent event : analysis.events) { + for (RailsimTrainStateEvent event : events) { csv.print(event.getVehicleId().toString()); csv.print(event.getTime()); csv.print(event.getAcceleration()); @@ -82,4 +83,5 @@ public static void writeCsv(TrainStateAnalysis analysis, Network network, String } } + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java index 2305226743e..519c3142c40 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java @@ -34,4 +34,7 @@ public void handleEvent(RailsimLinkStateChangeEvent event) { this.events.add(event); } + public List getEvents() { + return events; + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java deleted file mode 100644 index d1e386fb94f..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateWriter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * * - * *********************************************************************** * - * * - * copyright : (C) 2023 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package ch.sbb.matsim.contrib.railsim.analysis.linkstates; - -import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVPrinter; -import org.matsim.core.utils.io.IOUtils; - -import java.io.IOException; -import java.io.UncheckedIOException; - -public class RailLinkStateWriter { - - public static void writeCsv(RailLinkStateAnalysis analysis, String filename) throws UncheckedIOException { - String[] header = {"link", "time", "state", "vehicle", "track"}; - - try (CSVPrinter csv = new CSVPrinter(IOUtils.getBufferedWriter(filename), CSVFormat.DEFAULT.builder().setHeader(header).build())) { - for (RailsimLinkStateChangeEvent event : analysis.events) { - csv.print(event.getLinkId().toString()); - csv.print(event.getTime()); - csv.print(event.getState().toString()); - csv.print(event.getVehicleId() != null ? event.getVehicleId().toString() : ""); - csv.print(event.getTrack()); - csv.println(); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java index 33b38915061..8a410719cb3 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java @@ -19,6 +19,7 @@ package ch.sbb.matsim.contrib.railsim.analysis.linkstates; +import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; import org.matsim.api.core.v01.Scenario; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.controler.OutputDirectoryHierarchy; @@ -52,7 +53,7 @@ public void notifyIterationStarts(IterationStartsEvent event) { @Override public void notifyIterationEnds(IterationEndsEvent event) { String railLinkStatesCsvFilename = this.controlerIO.getIterationFilename(event.getIteration(), "railsimLinkStates.csv", this.scenario.getConfig().controler().getCompressionType()); - RailLinkStateWriter.writeCsv(this.analysis, railLinkStatesCsvFilename); + RailsimCsvWriter.writeLinkStatesCsv(this.analysis.events, railLinkStatesCsvFilename); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java index 7c0cf970e70..5703b4d66aa 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java @@ -19,6 +19,7 @@ package ch.sbb.matsim.contrib.railsim.analysis.trainstates; +import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; import org.matsim.api.core.v01.Scenario; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.controler.OutputDirectoryHierarchy; @@ -52,7 +53,7 @@ public void notifyIterationStarts(IterationStartsEvent event) { @Override public void notifyIterationEnds(IterationEndsEvent event) { String railLinkStatesCsvFilename = this.controlerIO.getIterationFilename(event.getIteration(), "trainStates.csv", this.scenario.getConfig().controler().getCompressionType()); - TrainStateWriter.writeCsv(this.analysis, this.scenario.getNetwork(), railLinkStatesCsvFilename); + RailsimCsvWriter.writeTrainStatesCsv(this.analysis.events, this.scenario.getNetwork(), railLinkStatesCsvFilename); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java index e1bb46c99a3..77aaa0780b3 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java @@ -34,4 +34,7 @@ public void handleEvent(RailsimTrainStateEvent event) { this.events.add(event); } + public List getEvents() { + return events; + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index a4f2e3fc85e..ab36cac1b2d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -8,18 +8,15 @@ import javax.annotation.Nullable; import java.util.Arrays; +import java.util.Objects; /** - * Rail tracks in railsim, which may corresponds to multiple MATSim links (if there are opposing links). - * Therefor this kind of track is bidirectional. + * Rail links which can has multiple tracks and corresponds to exactly one link. */ -final class RailLink implements HasLinkId { +public final class RailLink implements HasLinkId { private final Id id; - @Nullable - private final Id oppositeId; - /** * States per track. */ @@ -34,18 +31,22 @@ final class RailLink implements HasLinkId { final double freeSpeed; final double minimumHeadwayTime; - // TODO: from and to node most likely needed at some point - // A node can be blocked if a train is crossing the path + /** + * Id of the resource this link belongs to. + */ + @Nullable + final Id resource; - public RailLink(Link link, @Nullable Id opposite) { + public RailLink(Link link) { id = link.getId(); - oppositeId = opposite; state = new TrackState[RailsimUtils.getTrainCapacity(link)]; Arrays.fill(state, TrackState.FREE); reservations = new MobsimDriverAgent[state.length]; length = link.getLength(); freeSpeed = link.getFreespeed(); minimumHeadwayTime = RailsimUtils.getMinimumTrainHeadwayTime(link); + String resourceId = RailsimUtils.getResourceId(link); + resource = resourceId != null ? Id.create(resourceId, RailResource.class) : null; } @Override @@ -53,6 +54,18 @@ public Id getLinkId() { return id; } + @Nullable + public Id getResourceId() { + return resource; + } + + /** + * Number of tracks on this link. + */ + public int getNumberOfTracks(){ + return state.length; + } + /** * Returns the allowed freespeed, depending on the context, which is given via driver. */ @@ -75,27 +88,10 @@ public boolean hasFreeTrack() { return false; } - /** - * Reserve a track for a specific driver. - * - * @return -1 if not track was free, otherwise track number. - */ - public int reserveTrack(MobsimDriverAgent driver) { - for (int i = 0; i < state.length; i++) { - if (state[i] == TrackState.FREE) { - state[i] = TrackState.RESERVED; - reservations[i] = driver; - return i; - } - } - - return -1; - } - /** * Check if driver has already reserved this link. */ - public boolean isReserved(MobsimDriverAgent driver) { + public boolean isBlockedBy(MobsimDriverAgent driver) { for (MobsimDriverAgent reservation : reservations) { if (reservation == driver) return true; @@ -108,12 +104,13 @@ public boolean isReserved(MobsimDriverAgent driver) { */ public int blockTrack(MobsimDriverAgent driver) { for (int i = 0; i < state.length; i++) { - if (reservations[i] == driver) { + if (state[i] == TrackState.FREE) { + reservations[i] = driver; state[i] = TrackState.BLOCKED; return i; } } - throw new IllegalStateException("No track was reserved to be blocked."); + throw new IllegalStateException("No track was free."); } /** @@ -129,4 +126,25 @@ public int releaseTrack(MobsimDriverAgent driver) { } throw new IllegalStateException("Driver " + driver + " has not reserved the track."); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RailLink link = (RailLink) o; + return Objects.equals(id, link.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "RailLink{" + + "id=" + id + + ", resource=" + resource + + '}'; + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java new file mode 100644 index 00000000000..d10602ebf1f --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java @@ -0,0 +1,43 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.matsim.core.mobsim.framework.MobsimDriverAgent; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +/** + * A resource representing multiple {@link RailLink}. + */ +public class RailResource { + + /** + * Links beloning to this resource. + */ + final List links; + + /** + * Maximum number of reservations. + */ + int capacity; + + /** + * Agents holding this resource exclusively. + */ + Set reservations; + + public RailResource(List links) { + capacity = links.stream().mapToInt(RailLink::getNumberOfTracks).min().orElseThrow(); + this.links = links; + reservations = new HashSet<>(); + } + + /** + * Whether an agent is able to block this resource. + */ + boolean hasCapacity() { + return reservations.size() < capacity; + } + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java new file mode 100644 index 00000000000..ab689d34f30 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -0,0 +1,97 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Class responsible for managing and blocking resources and segments of links. + */ +public final class RailResourceManager { + + /** + * Rail links + */ + private final Map, RailLink> links; + + + private final Map, RailResource> resources; + + /** + * Construct resources from network. + */ + public RailResourceManager(RailsimConfigGroup config, Network network) { + this.links = new IdMap<>(Link.class, network.getLinks().size()); + + Set modes = config.getRailNetworkModes(); + for (Map.Entry, ? extends Link> e : network.getLinks().entrySet()) { + if (e.getValue().getAllowedModes().stream().anyMatch(modes::contains)) + this.links.put(e.getKey(), new RailLink(e.getValue())); + } + + Map, List> collect = links.values().stream() + .filter(l -> l.resource != null) + .collect(Collectors.groupingBy(l -> l.resource, Collectors.toList()) + ); + + resources = new IdMap<>(RailResource.class, collect.size()); + for (Map.Entry, List> e : collect.entrySet()) { + resources.put(e.getKey(), new RailResource(e.getValue())); + } + } + + /** + * Get single link that belongs to an id. + */ + public RailLink getLink(Id id) { + return links.get(id); + } + + /** + * Return the resource for a given id. + */ + public RailResource getResource(Id id) { + return resources.get(id); + } + + /** + * Try to block a resource for a specific driver. + * + * @return true if the resource is now blocked or was blocked for this driver already. + */ + public boolean tryBlockResource(RailResource resource, MobsimDriverAgent driver) { + + if (resource.reservations.contains(driver)) + return true; + + if (resource.hasCapacity()) { + resource.reservations.add(driver); + return true; + } + + return false; + } + + /** + * Try to release a resource, but only if none of the links are blocked anymore by this driver. + * + * @return whether driver is still blocking this resource. + */ + public boolean tryReleaseResource(RailResource resource, MobsimDriverAgent driver) { + + if (resource.links.stream().noneMatch(l -> l.isBlockedBy(driver))) { + resource.reservations.remove(driver); + return true; + } + + return false; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 3b5d2513160..2a2f5230a0f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -1,5 +1,8 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import java.util.ArrayList; +import java.util.List; + /** * Utility class holding static calculation methods related to state (updates). */ @@ -156,6 +159,65 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) return decelDist; } + /** + * Calculate when the reservation function should be triggered. + * Should return {@link Double#POSITIVE_INFINITY} if this distance is far in the future and can be checked at later point. + * + * @param state current train state. + * @return travel distance after which reservations should be updated. + */ + public static double nextLinkReservation(TrainState state) { + + // time needed for full stop + double stopTime = state.allowedMaxSpeed / state.train.deceleration(); + + assert stopTime > 0 : "Stop time can not be negative"; + + // Distance for full stop + double safetyDist = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, stopTime, -state.train.deceleration()); + + double dist = -state.headPosition - safetyDist; + int idx = state.routeIdx; + do { + RailLink nextLink = state.route.get(idx++); + dist += nextLink.length; + + if (nextLink.isBlockedBy(state.driver)) + continue; + + return dist; + } while (dist <= safetyDist && idx < state.route.size()); + + // No need to reserve yet + return Double.POSITIVE_INFINITY; + } + + /** + * Links that need to be blocked or otherwise stop needs to be initiated. + */ + public static List calcLinksToBlock(int idx, TrainState state) { + + List result = new ArrayList<>(); + + double assumedSpeed = state.train.maxVelocity(); + + double stopTime = assumedSpeed / state.train.deceleration(); + // safety distance + double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) + state.headPosition; + + double reserved = 0; + + do { + RailLink nextLink = state.route.get(idx++); + result.add(nextLink); + reserved += nextLink.length; + + } while (reserved < safety && idx < state.route.size()); + + return result; + } + + record SpeedTarget(double targetSpeed, double decelDist) implements Comparable { @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index e99405026ab..ed02a57ed4c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -1,13 +1,13 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; -import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; import ch.sbb.matsim.contrib.railsim.events.RailsimTrainLeavesLinkEvent; +import ch.sbb.matsim.contrib.railsim.qsimengine.diposition.SimpleDisposition; +import ch.sbb.matsim.contrib.railsim.qsimengine.diposition.TrainDisposition; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.events.LinkEnterEvent; import org.matsim.api.core.v01.events.LinkLeaveEvent; import org.matsim.api.core.v01.events.PersonEntersVehicleEvent; @@ -36,35 +36,19 @@ final class RailsimEngine implements Steppable { private final EventsManager eventsManager; private final RailsimConfigGroup config; - /** - * Rail links - */ - private final Map, RailLink> links; - private final List activeTrains = new ArrayList<>(); private final Queue updateQueue = new PriorityQueue<>(); - private final TrackReservationStrategy reservation; + private final RailResourceManager resources; - public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, Map, ? extends Link> network) { + private final TrainDisposition disposition; + + public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, RailResourceManager resources) { this.eventsManager = eventsManager; this.config = config; - this.links = new IdMap<>(Link.class, network.size()); - for (Map.Entry, ? extends Link> e : network.entrySet()) { - - // This link and the opposite need to have the same attributes - - Id opposite = RailsimUtils.getOppositeDirectionLink(e.getValue()); - // Use existing instead of creating a new one - if (links.containsKey(opposite)) { - this.links.put(e.getKey(), links.get(opposite)); - } else { - this.links.put(e.getKey(), new RailLink(e.getValue(), opposite)); - } - } - - this.reservation = new ReservationAtLatestChance(); + this.resources = resources; + this.disposition = new SimpleDisposition(resources); } @Override @@ -111,12 +95,12 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin this.eventsManager.processEvent(new PersonEntersVehicleEvent(now, agent.getId(), agent.getVehicle().getId())); // if (this.createLinkEvents) { - this.eventsManager.processEvent(new VehicleEntersTrafficEvent(now, agent.getId(), linkId, agent.getVehicle().getId(), agent.getMode(), 1.0)); + this.eventsManager.processEvent(new VehicleEntersTrafficEvent(now, agent.getId(), linkId, agent.getVehicle().getId(), agent.getMode(), 1.0)); // } - List list = route.getLinkIds().stream().map(links::get).collect(Collectors.toList()); - list.add(0, links.get(linkId)); - list.add(links.get(route.getEndLinkId())); + List list = route.getLinkIds().stream().map(resources::getLink).collect(Collectors.toList()); + list.add(0, resources.getLink(linkId)); + list.add(resources.getLink(route.getEndLinkId())); TrainState state = new TrainState(agent, new TrainInfo(agent.getVehicle().getVehicle().getType(), config), now, null, list); @@ -140,7 +124,7 @@ private void updateState(double time, UpdateEvent event) { case SPEED_CHANGE -> updateSpeed(time, event); case ENTER_LINK -> enterLink(time, event); case LEAVE_LINK -> leaveLink(time, event); - case TRACK_RESERVATION -> reserveTrack(time, event); + case BLOCK_TRACK -> blockTrack(time, event); case WAIT_FOR_RESERVATION -> checkTrackReservation(time, event); default -> throw new IllegalStateException("Unhandled update type " + event.type); } @@ -167,13 +151,16 @@ else if (state.targetSpeed > state.speed) decideNextUpdate(time, event); } - private void reserveTrack(double time, UpdateEvent event) { + private void blockTrack(double time, UpdateEvent event) { TrainState state = event.state; updatePosition(time, event); - if (!reserveLinkTracks(time, event.type, state.routeIdx, state)) { + if (!blockLinkTracks(time, state.routeIdx, state)) { + + // TODO: calculate speed to arrival at the last blocked link + // Break when reservation is not possible state.targetSpeed = 0; state.acceleration = -state.train.deceleration(); @@ -188,7 +175,7 @@ private void checkTrackReservation(double time, UpdateEvent event) { TrainState state = event.state; - if (reserveLinkTracks(time, event.type, state.routeIdx, state)) { + if (blockLinkTracks(time, state.routeIdx, state)) { updatePosition(time, event); // TODO: maximum speed could be lower @@ -211,7 +198,9 @@ private void updateDeparture(double time, UpdateEvent event) { TrainState state = event.state; state.timestamp = time; - if (reserveLinkTracks(time, event.type, 0, state)) { + disposition.onDeparture(time, state.driver, state.route); + + if (blockLinkTracks(time, 0, state)) { state.timestamp = time; @@ -226,25 +215,27 @@ private void updateDeparture(double time, UpdateEvent event) { /** * Reserve links in advance as necessary. */ - private boolean reserveLinkTracks(double time, UpdateEvent.Type type, int idx, TrainState state) { + private boolean blockLinkTracks(double time, int idx, TrainState state) { - List links = reservation.retrieveLinksToReserve(time, type, idx, state); - links = links.stream().filter(link -> !link.isReserved(state.driver)).toList(); + List links = RailsimCalc.calcLinksToBlock(idx, state); if (links.isEmpty()) return true; - // All links must be able to be reserved - if (!links.stream().allMatch(RailLink::hasFreeTrack)) - return false; + List blocked = disposition.blockRailSegment(time, state.driver, links); - for (RailLink link : links) { - int track = link.reserveTrack(state.driver); + // Block the approved links + for (RailLink link : blocked) { + if (link.isBlockedBy(state.driver)) + continue; + + int track = link.blockTrack(state.driver); eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), - state.driver.getVehicle().getId(), TrackState.RESERVED, track)); + state.driver.getVehicle().getId(), TrackState.BLOCKED, track)); } - return true; + // Only continue successfully if all requested link have been blocked + return links.size() == blocked.size(); } private void enterLink(double time, UpdateEvent event) { @@ -269,10 +260,13 @@ private void enterLink(double time, UpdateEvent event) { // Free all reservations for (RailLink link : state.route) { - if (link.isReserved(state.driver)){ + if (link.isBlockedBy(state.driver)) { + int track = link.releaseTrack(state.driver); eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), state.driver.getVehicle().getId(), TrackState.FREE, track)); + + disposition.unblockRailLink(time, state.driver, link); } } @@ -288,17 +282,13 @@ private void enterLink(double time, UpdateEvent event) { // On departure tail link is the same head link if (state.tailLink == null) { state.tailLink = state.headLink; - state.tailPosition = links.get(state.tailLink).length + state.train.length(); + state.tailPosition = resources.getLink(state.tailLink).length + state.train.length(); } state.driver.notifyMoveOverNode(state.headLink); eventsManager.processEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); - RailLink link = links.get(state.headLink); - - int track = link.blockTrack(state.driver); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.headLink, state.driver.getVehicle().getId(), - TrackState.BLOCKED, track)); + RailLink link = resources.getLink(state.headLink); // TODO: this probably needs to be a separate function to calculate possible target speed more accurately if (RailsimCalc.calcDecelDistanceAndSpeed(link, event) == Double.POSITIVE_INFINITY) { @@ -316,7 +306,7 @@ private void leaveLink(double time, UpdateEvent event) { TrainState state = event.state; - RailLink tailLink = links.get(state.tailLink); + RailLink tailLink = resources.getLink(state.tailLink); RailLink nextTailLink = null; // Find the next link in the route for (int i = state.routeIdx; i >= 1; i--) { @@ -335,6 +325,8 @@ private void leaveLink(double time, UpdateEvent event) { eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.tailLink, state.driver.getVehicle().getId(), TrackState.FREE, track)); + disposition.unblockRailLink(time, state.driver, resources.getLink(state.tailLink)); + state.tailLink = nextTailLink.getLinkId(); state.tailPosition = nextTailLink.length + state.train.length(); @@ -392,7 +384,7 @@ private void updatePosition(double time, UpdateEvent event) { state.timestamp = time; assert FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; - assert FuzzyUtils.lessEqualThan(state.headPosition, links.get(state.headLink).length) : "Illegal state update. Head position must be smaller than link length"; + assert FuzzyUtils.lessEqualThan(state.headPosition, resources.getLink(state.headLink).length) : "Illegal state update. Head position must be smaller than link length"; assert FuzzyUtils.greaterEqualThan(state.headPosition, 0) : "Head position must be positive"; eventsManager.processEvent(state.asEvent(time)); @@ -404,7 +396,7 @@ private void updatePosition(double time, UpdateEvent event) { private void decideNextUpdate(double time, UpdateEvent event) { TrainState state = event.state; - RailLink currentLink = links.get(state.headLink); + RailLink currentLink = resources.getLink(state.headLink); // (1) max speed reached double accelDist = Double.POSITIVE_INFINITY; @@ -422,7 +414,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { // (3) next link needs reservation double reserveDist = Double.POSITIVE_INFINITY; if (!state.isRouteAtEnd()) { - reserveDist = reservation.nextUpdate(currentLink, state); + reserveDist = RailsimCalc.nextLinkReservation(state); if (reserveDist < 0) reserveDist = 0; @@ -447,7 +439,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { event.type = UpdateEvent.Type.SPEED_CHANGE; } else if (reserveDist <= accelDist && reserveDist <= decelDist && reserveDist <= tailDist && reserveDist <= headDist) { dist = reserveDist; - event.type = UpdateEvent.Type.TRACK_RESERVATION; + event.type = UpdateEvent.Type.BLOCK_TRACK; } else if (tailDist <= decelDist && tailDist <= reserveDist && tailDist <= headDist) { dist = tailDist; event.type = UpdateEvent.Type.LEAVE_LINK; @@ -470,8 +462,8 @@ private double retrieveAllowedMaxSpeed(TrainState state) { // TODO: needs to check the whole part of current route return Math.min( - links.get(state.headLink).getAllowedFreespeed(state.driver), - links.get(state.tailLink).getAllowedFreespeed(state.driver) + resources.getLink(state.headLink).getAllowedFreespeed(state.driver), + resources.getLink(state.tailLink).getAllowedFreespeed(state.driver) ); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index ea27cd5ca5c..32f5a24c325 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -21,13 +21,11 @@ import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.population.Leg; import org.matsim.api.core.v01.population.PlanElement; import org.matsim.api.core.v01.population.Route; import org.matsim.core.config.ConfigUtils; -import org.matsim.core.mobsim.framework.DriverAgent; import org.matsim.core.mobsim.framework.MobsimAgent; import org.matsim.core.mobsim.framework.MobsimDriverAgent; import org.matsim.core.mobsim.framework.PlanAgent; @@ -36,13 +34,11 @@ import org.matsim.core.mobsim.qsim.interfaces.DepartureHandler; import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine; import org.matsim.core.mobsim.qsim.interfaces.NetsimLink; -import org.matsim.core.mobsim.qsim.interfaces.NetsimNetwork; import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; import org.matsim.core.mobsim.qsim.qnetsimengine.QLinkI; import org.matsim.core.population.routes.NetworkRoute; import javax.inject.Inject; -import java.util.Map; import java.util.Set; /** @@ -73,14 +69,7 @@ public void setInternalInterface(InternalInterface internalInterface) { @Override public void onPrepareSim() { - - Map, Link> links = new IdMap<>(Link.class); - for (Link link : qsim.getScenario().getNetwork().getLinks().values()) { - if (link.getAllowedModes().stream().anyMatch(modes::contains)) - links.put(link.getId(), link); - } - - engine = new RailsimEngine(qsim.getEventsManager(), config, links); + engine = new RailsimEngine(qsim.getEventsManager(), config, new RailResourceManager(config, qsim.getScenario().getNetwork())); } @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java deleted file mode 100644 index 3bc916ac9a9..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/ReservationAtLatestChance.java +++ /dev/null @@ -1,59 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.qsimengine; - -import java.util.ArrayList; -import java.util.List; - -/** - * Reserve tracks latest as possible. - */ -public class ReservationAtLatestChance implements TrackReservationStrategy { - @Override - public double nextUpdate(RailLink currentLink, TrainState state) { - - // time needed for full stop - double stopTime = state.allowedMaxSpeed / state.train.deceleration(); - - assert stopTime > 0 : "Stop time can not be negative"; - - // Distance for full stop - double safetyDist = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, stopTime, -state.train.deceleration()); - - double dist = -state.headPosition - safetyDist; - int idx = state.routeIdx; - do { - RailLink nextLink = state.route.get(idx++); - dist += nextLink.length; - - if (nextLink.isReserved(state.driver)) - continue; - - return dist; - } while (dist <= safetyDist && idx < state.route.size()); - - // No need to reserve yet - return Double.POSITIVE_INFINITY; - } - - @Override - public List retrieveLinksToReserve(double time, UpdateEvent.Type type, int idx, TrainState state) { - - List result = new ArrayList<>(); - - double assumedSpeed = state.train.maxVelocity(); - - double stopTime = assumedSpeed / state.train.deceleration(); - // safety distance - double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) + state.headPosition; - - double reserved = 0; - - do { - RailLink nextLink = state.route.get(idx++); - result.add(nextLink); - reserved += nextLink.length; - - } while (reserved < safety && idx < state.route.size()); - - return result; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java deleted file mode 100644 index cd1d62dce60..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackReservationStrategy.java +++ /dev/null @@ -1,23 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.qsimengine; - -import java.util.List; - -/** - * Component to define when and how many tracks should be reserved in advance. - */ -public interface TrackReservationStrategy { - - - /** - * Calculate when the reservation function should be triggered. - * Should return {@link Double#POSITIVE_INFINITY} if this distance is far in the future and can be checked at later point. - * - * @param state current train state. - * @return travel distance after which reservations should be updated. - */ - double nextUpdate(RailLink currentLink, TrainState state); - - - List retrieveLinksToReserve(double time, UpdateEvent.Type type, int idx, TrainState state) ; - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java index 61a126fa488..c02fb537a29 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java @@ -1,10 +1,13 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; /** - * Current state of a track (link) + * Current state of a track. */ public enum TrackState { FREE, + + /** + * Blocked tracks that are exclusively available for trains. + */ BLOCKED, - RESERVED } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 784d28fef64..a4707f610ff 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -52,8 +52,8 @@ enum Type { POSITION, ENTER_LINK, LEAVE_LINK, - FREE_RESERVATION, - TRACK_RESERVATION, + RELEASE_TRACK, + BLOCK_TRACK, WAIT_FOR_RESERVATION, SPEED_CHANGE diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java new file mode 100644 index 00000000000..bc2d670a6f3 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java @@ -0,0 +1,72 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine.diposition; + +import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; +import ch.sbb.matsim.contrib.railsim.qsimengine.RailResource; +import ch.sbb.matsim.contrib.railsim.qsimengine.RailResourceManager; +import org.matsim.api.core.v01.Id; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Simple disposition without deadlock avoidance. + */ +public class SimpleDisposition implements TrainDisposition { + + private final RailResourceManager resources; + + public SimpleDisposition(RailResourceManager resources) { + this.resources = resources; + } + + @Override + public void onDeparture(double time, MobsimDriverAgent driver, List route) { + // Nothing to do. + } + + @Override + public List blockRailSegment(double time, MobsimDriverAgent driver, List segment) { + + List blocked = new ArrayList<>(); + + // Iterate all links that need to be blocked + for (RailLink link : segment) { + + if (link.isBlockedBy(driver)) { + blocked.add(link); + continue; + } + + Id resourceId = link.getResourceId(); + if (resourceId != null) { + + RailResource resource = resources.getResource(resourceId); + if (resources.tryBlockResource(resource, driver)) { + blocked.add(link); + continue; + } + + // Could not reserve resource + break; + } + + // Check if single link can be reserved + if (link.hasFreeTrack()) { + blocked.add(link); + } else + break; + } + + return blocked; + } + + @Override + public void unblockRailLink(double time, MobsimDriverAgent driver, RailLink link) { + + // Release held resources + if (link.getResourceId() != null) { + resources.tryReleaseResource(resources.getResource(link.getResourceId()), driver); + } + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/TrainDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/TrainDisposition.java new file mode 100644 index 00000000000..c10a71b8eb5 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/TrainDisposition.java @@ -0,0 +1,33 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine.diposition; + +import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; +import org.matsim.core.mobsim.framework.MobsimDriverAgent; + +import java.util.List; + +/** + * Disposition, handling route and track reservations. + */ +public interface TrainDisposition { + + + /** + * Method invoked when a train is departing. + */ + void onDeparture(double time, MobsimDriverAgent driver, List route); + + /** + * Train is reaching the given links and is trying to block them. + * + * @return links of the request that are exclusively blocked for the train. + */ + List blockRailSegment(double time, MobsimDriverAgent driver, List segment); + + /** + * Inform the resource manager that the train has passed a link that can now be unblocked. + * This needs to be called after track states have been updated already. + */ + void unblockRailLink(double time, MobsimDriverAgent driver, RailLink link); + + +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java new file mode 100644 index 00000000000..831b2ee3487 --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java @@ -0,0 +1,10 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine.router; + +public interface TrainRouter { + + // TODO Placeholder for a routing interface + + // Train stations or other areas are modeled as block with entry and exit links + // within these blocks the train can be rerouted depending on track availability + +} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 9932ba92235..6b565f47022 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -1,6 +1,8 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -32,10 +34,12 @@ public void setUp() throws Exception { private RailsimTestUtils.Holder getTestEngine(String network) { Network net = NetworkUtils.readNetwork(new File(utils.getPackageInputDirectory(), network).toString()); + RailsimConfigGroup config = new RailsimConfigGroup(); collector.clear(); - return new RailsimTestUtils.Holder(new RailsimEngine(eventsManager, new RailsimConfigGroup(), net.getLinks()), net); + return new RailsimTestUtils.Holder(new RailsimEngine( + eventsManager, config, new RailResourceManager(config, net)), net); } @Test @@ -86,6 +90,8 @@ public void opposite() { test.doSimStepUntil(600); + test.debug(collector, "opposite"); + RailsimTestUtils.assertThat(collector) .hasTrainState("regio2", 210.7272722504356, 2000, 0) .hasTrainState("regio1", 348.49615180280205, 200, 0); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java index e96d0966bad..33c961c57b1 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java @@ -1,9 +1,13 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.events.Event; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; +import org.matsim.core.events.algorithms.EventWriterXML; import org.matsim.core.events.handler.BasicEventHandler; import org.matsim.core.mobsim.framework.MobsimDriverAgent; import org.matsim.core.mobsim.qsim.interfaces.MobsimVehicle; @@ -89,6 +93,12 @@ public void clear() { events.clear(); } + public void dump(String out) { + EventWriterXML writer = new EventWriterXML(out); + events.forEach(writer::handleEvent); + writer.closeFile(); + } + } public record Holder(RailsimEngine engine, Network network) { @@ -111,6 +121,23 @@ public void doStateUpdatesUntil(double time, double interval) { engine().updateAllStates(t); } } + + public void debug(EventCollector collector, String out) { + RailsimCsvWriter.writeTrainStatesCsv( + collector.events.stream().filter(ev -> ev instanceof RailsimTrainStateEvent) + .map(ev -> (RailsimTrainStateEvent) ev) + .toList(), + network, + out + "_trainStates.csv" + ); + + RailsimCsvWriter.writeLinkStatesCsv( + collector.events.stream().filter(ev -> ev instanceof RailsimLinkStateChangeEvent) + .map(ev -> (RailsimLinkStateChangeEvent) ev) + .toList(), + out + "_linkStates.csv" + ); + } } /** diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml index 526d8556ddf..79bd3fed9cc 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml @@ -39,13 +39,13 @@ - l4-3 + l43 - l3-4 + l43 From 68fe1b6044b15417b2e4da38a4ed9fdef13d9c28 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 25 May 2023 16:42:31 +0200 Subject: [PATCH 042/258] change tail position logic to be consistent with headPosition, let trains start and the end of first link --- .../contrib/railsim/qsimengine/RailLink.java | 2 +- .../railsim/qsimengine/RailsimEngine.java | 30 +++++++++---------- .../railsim/qsimengine/TrainState.java | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index ab36cac1b2d..47695f3444a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -124,7 +124,7 @@ public int releaseTrack(MobsimDriverAgent driver) { return i; } } - throw new IllegalStateException("Driver " + driver + " has not reserved the track."); + throw new AssertionError("Driver " + driver + " has not reserved the track."); } @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index ed02a57ed4c..b56f2cf6ba2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -94,16 +94,14 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin log.info("Train {} is departing", agent.getVehicle()); this.eventsManager.processEvent(new PersonEntersVehicleEvent(now, agent.getId(), agent.getVehicle().getId())); -// if (this.createLinkEvents) { this.eventsManager.processEvent(new VehicleEntersTrafficEvent(now, agent.getId(), linkId, agent.getVehicle().getId(), agent.getMode(), 1.0)); -// } List list = route.getLinkIds().stream().map(resources::getLink).collect(Collectors.toList()); list.add(0, resources.getLink(linkId)); list.add(resources.getLink(route.getEndLinkId())); TrainState state = new TrainState(agent, new TrainInfo(agent.getVehicle().getVehicle().getType(), config), - now, null, list); + now, linkId, list); activeTrains.add(state); @@ -200,10 +198,18 @@ private void updateDeparture(double time, UpdateEvent event) { disposition.onDeparture(time, state.driver, state.route); + state.headPosition = resources.getLink(state.headLink).length; + state.tailPosition = resources.getLink(state.headLink).length - state.train.length(); + if (blockLinkTracks(time, 0, state)) { state.timestamp = time; + eventsManager.processEvent(state.asEvent(time)); + + // Train departs at the very end of the first link + state.routeIdx = 1; + // Call enter link logic immediately enterLink(time, event); } else { @@ -245,9 +251,7 @@ private void enterLink(double time, UpdateEvent event) { updatePosition(time, event); // On route departure the head link is null - if (state.headLink != null) { - eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); - } + eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); // Get link and increment state.headPosition = 0; @@ -279,11 +283,6 @@ private void enterLink(double time, UpdateEvent event) { return; } - // On departure tail link is the same head link - if (state.tailLink == null) { - state.tailLink = state.headLink; - state.tailPosition = resources.getLink(state.tailLink).length + state.train.length(); - } state.driver.notifyMoveOverNode(state.headLink); eventsManager.processEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); @@ -328,7 +327,7 @@ private void leaveLink(double time, UpdateEvent event) { disposition.unblockRailLink(time, state.driver, resources.getLink(state.tailLink)); state.tailLink = nextTailLink.getLinkId(); - state.tailPosition = nextTailLink.length + state.train.length(); + state.tailPosition = 0; decideNextUpdate(time, event); } @@ -380,10 +379,10 @@ private void updatePosition(double time, UpdateEvent event) { assert FuzzyUtils.greaterEqualThan(dist, 0) : "Travel distance must be positive, but was" + dist; state.headPosition += dist; - state.tailPosition -= dist; + state.tailPosition += dist; state.timestamp = time; - assert FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; + assert state.routeIdx <= 1 || FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; assert FuzzyUtils.lessEqualThan(state.headPosition, resources.getLink(state.headLink).length) : "Illegal state update. Head position must be smaller than link length"; assert FuzzyUtils.greaterEqualThan(state.headPosition, 0) : "Head position must be positive"; @@ -421,7 +420,8 @@ private void decideNextUpdate(double time, UpdateEvent event) { } // (4) tail link changes - double tailDist = state.tailPosition; + double tailDist = resources.getLink(state.tailLink).length - state.tailPosition; + // (5) head link changes double headDist = currentLink.length - state.headPosition; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 7275228025f..0abf0eba496 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -73,7 +73,7 @@ final class TrainState { double headPosition; /** - * * Distance in meters away from the {@code tailLink}s {@code toNode}. + * * Distance in meters away from the {@code tailLink}s {@code fromNode}. */ double tailPosition; From 9c846b57f570eafe04b587fe4cb687fa2f50e674 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 25 May 2023 17:12:15 +0200 Subject: [PATCH 043/258] train drives until the end of destination link --- .../railsim/qsimengine/RailsimCalc.java | 23 +++++++++++-------- .../railsim/qsimengine/RailsimEngine.java | 18 +++++++-------- .../railsim/qsimengine/TrainState.java | 2 +- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 2a2f5230a0f..c62f60dfc2a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -69,11 +69,11 @@ static double calcRequiredTime(TrainState state, double dist) { * again after traveled {@code dist}. */ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double deceleration, - double currentSpeed, double targetSpeed, double finalSpeed) { + double currentSpeed, double targetSpeed, double finalSpeed) { assert FuzzyUtils.greaterEqualThan(targetSpeed, finalSpeed) : "Final speed must be smaller than target"; - double timeDecel = (targetSpeed- finalSpeed) / deceleration; + double timeDecel = (targetSpeed - finalSpeed) / deceleration; double distDecel = calcTraveledDist(targetSpeed, timeDecel, -deceleration); // This code below only works during deceleration @@ -93,7 +93,7 @@ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double dece double v = Math.sqrt(nom / (acceleration + deceleration)); - timeDecel = (v- finalSpeed) / deceleration; + timeDecel = (v - finalSpeed) / deceleration; distDecel = calcTraveledDist(v, timeDecel, -deceleration); return new SpeedTarget(v, distDecel); @@ -122,14 +122,17 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) double targetSpeed = state.targetSpeed; double speed = 0; - for (int i = state.routeIdx; i < state.route.size(); i++) { + for (int i = state.routeIdx; i <= state.route.size(); i++) { - RailLink link = state.route.get(i); + RailLink link; double allowed; // Last track where train comes to halt - if (i == state.route.size() - 1) + if (i == state.route.size()) { + link = null; allowed = 0; + } else { + link = state.route.get(i); allowed = link.getAllowedFreespeed(state.driver); } @@ -146,7 +149,8 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) } } - dist += link.length; + if (link != null) + dist += link.length; // don't need to look further than distance needed for full stop if (dist >= window) @@ -178,7 +182,8 @@ public static double nextLinkReservation(TrainState state) { double dist = -state.headPosition - safetyDist; int idx = state.routeIdx; - do { + + while (dist <= safetyDist && idx < state.route.size()) { RailLink nextLink = state.route.get(idx++); dist += nextLink.length; @@ -186,7 +191,7 @@ public static double nextLinkReservation(TrainState state) { continue; return dist; - } while (dist <= safetyDist && idx < state.route.size()); + } // No need to reserve yet return Double.POSITIVE_INFINITY; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index b56f2cf6ba2..5928c6c95cb 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -105,6 +105,8 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin activeTrains.add(state); + disposition.onDeparture(now, state.driver, state.route); + updateQueue.add(new UpdateEvent(state, UpdateEvent.Type.DEPARTURE)); return true; @@ -196,8 +198,6 @@ private void updateDeparture(double time, UpdateEvent event) { TrainState state = event.state; state.timestamp = time; - disposition.onDeparture(time, state.driver, state.route); - state.headPosition = resources.getLink(state.headLink).length; state.tailPosition = resources.getLink(state.headLink).length - state.train.length(); @@ -250,13 +250,6 @@ private void enterLink(double time, UpdateEvent event) { updatePosition(time, event); - // On route departure the head link is null - eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); - - // Get link and increment - state.headPosition = 0; - state.headLink = state.route.get(state.routeIdx++).getLinkId(); - // Arrival at destination if (state.isRouteAtEnd()) { @@ -283,6 +276,13 @@ private void enterLink(double time, UpdateEvent event) { return; } + // On route departure the head link is null + eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); + + // Get link and increment + state.headPosition = 0; + state.headLink = state.route.get(state.routeIdx++).getLinkId(); + state.driver.notifyMoveOverNode(state.headLink); eventsManager.processEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 0abf0eba496..8a3ddf3d8c2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -115,7 +115,7 @@ public String toString() { boolean isRouteAtEnd() { - return routeIdx == route.size(); + return routeIdx >= route.size(); } RailsimTrainStateEvent asEvent(double time) { From 1802c4aea4b5e4dca43dfc917a99e3932f93c665 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 26 May 2023 11:21:06 +0200 Subject: [PATCH 044/258] updated test cases, fixed issues related to waiting for track reservations --- .../railsim/qsimengine/RailsimCalc.java | 4 +- .../railsim/qsimengine/RailsimEngine.java | 23 +++++++--- .../railsim/qsimengine/UpdateEvent.java | 1 + .../railsim/qsimengine/RailsimEngineTest.java | 29 ++++++------- .../contrib/railsim/qsimengine/network0.xml | 42 ++++++++++++------- 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index c62f60dfc2a..08464e7e3ca 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -57,10 +57,10 @@ static double calcRequiredTime(TrainState state, double dist) { // max distance that can be reached double max = calcTraveledDist(state.speed, decelTime, state.acceleration); - if (dist < max) { + if (dist <= max) { return solveTraveledDist(state.speed, dist, state.acceleration); } else - return decelTime; + return Double.POSITIVE_INFINITY; } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 5928c6c95cb..1957e91a148 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -165,10 +165,13 @@ private void blockTrack(double time, UpdateEvent event) { state.targetSpeed = 0; state.acceleration = -state.train.deceleration(); - event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; - event.plannedTime += POLL_INTERVAL; - } else + event.checkReservation = time + POLL_INTERVAL; decideNextUpdate(time, event); + + } else { + event.checkReservation = -1; + decideNextUpdate(time, event); + } } private void checkTrackReservation(double time, UpdateEvent event) { @@ -185,10 +188,14 @@ private void checkTrackReservation(double time, UpdateEvent event) { state.targetSpeed = state.allowedMaxSpeed; state.acceleration = state.train.acceleration(); + event.checkReservation = -1; decideNextUpdate(time, event); } else { - event.plannedTime += POLL_INTERVAL; + + event.checkReservation = time + POLL_INTERVAL; + decideNextUpdate(time, event); + } } @@ -412,7 +419,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { // (3) next link needs reservation double reserveDist = Double.POSITIVE_INFINITY; - if (!state.isRouteAtEnd()) { + if (!state.isRouteAtEnd() && event.checkReservation < 0) { reserveDist = RailsimCalc.nextLinkReservation(state); if (reserveDist < 0) @@ -452,6 +459,12 @@ private void decideNextUpdate(double time, UpdateEvent event) { // dist is the minimum of all supplied distances event.plannedTime = time + RailsimCalc.calcRequiredTime(state, dist); + + // insert reservation event if necessary + if (event.checkReservation >= 0 && event.plannedTime > event.checkReservation) { + event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; + event.plannedTime = event.checkReservation; + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index a4707f610ff..06ed57c3952 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -12,6 +12,7 @@ final class UpdateEvent implements Comparable { Type type; double newSpeed = -1; + double checkReservation = -1; public UpdateEvent(TrainState state, Type type) { this.state = state; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 6b565f47022..ce3714c39bb 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -1,8 +1,6 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; -import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; -import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -52,8 +50,8 @@ public void simple() { RailsimTestUtils.assertThat(collector) .hasSizeGreaterThan(5) - .hasTrainState("train", 148, 0, 20) - .hasTrainState("train", 188, 200, 0); + .hasTrainState("train", 144, 0, 44) + .hasTrainState("train", 233.4545441058463, 2000, 0); test = getTestEngine("network0.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); @@ -62,8 +60,8 @@ public void simple() { RailsimTestUtils.assertThat(collector) .hasSizeGreaterThan(5) - .hasTrainState("train", 148, 0, 20) - .hasTrainState("train", 188, 200, 0); + .hasTrainState("train", 144, 0, 44) + .hasTrainState("train", 233.4545441058463, 2000, 0); } @@ -85,28 +83,31 @@ public void opposite() { RailsimTestUtils.Holder test = getTestEngine("network0.xml"); - RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l5-6"); - RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l6-5", "l2-1"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l7-8"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l8-7", "l2-1"); test.doSimStepUntil(600); test.debug(collector, "opposite"); RailsimTestUtils.assertThat(collector) - .hasTrainState("regio2", 210.7272722504356, 2000, 0) - .hasTrainState("regio1", 348.49615180280205, 200, 0); + .hasTrainState("regio1", 292.5454533774468, 600, 0) + .hasTrainState("regio2", 443.62677217662434, 1000, 0); test = getTestEngine("network0.xml"); - RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l5-6"); - RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l6-5", "l2-1"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l7-8"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l8-7", "l2-1"); test.doStateUpdatesUntil(600, 1); + test.debug(collector, "opposite_detailed"); + + RailsimTestUtils.assertThat(collector) - .hasTrainState("regio2", 210.7272722504356, 2000, 0) - .hasTrainState("regio1", 348.49615180280205, 200, 0); + .hasTrainState("regio1", 292.5454533774468, 600, 0) + .hasTrainState("regio2", 443.62677217662434, 1000, 0); } } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml index 79bd3fed9cc..ac6b478fe47 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml @@ -16,47 +16,59 @@ - - - - + + + + + + - - - - + + + - l43 + l45 - - l43 + l45 - + + + - - - From 9704d586db04f2d41b7b24d447cada7fc12d12bd Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 26 May 2023 11:50:30 +0200 Subject: [PATCH 045/258] setup integration tests --- .../contrib/railsim/RunRailsimExample.java | 24 ------ .../integration/RailsimIntegrationTest.java | 44 +++++++++++ .../railsim/integration/test0/config.xml | 39 ++++++++++ .../integration/test0/trainNetwork.xml | 78 +++++++++++++++++++ .../integration/test0/transitSchedule.xml | 41 ++++++++++ .../integration/test0/transitVehicles.xml | 34 ++++++++ 6 files changed, 236 insertions(+), 24 deletions(-) create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java index 402a13f31a8..962a96de921 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java @@ -33,30 +33,6 @@ */ public class RunRailsimExample { - // TODO: Vehicle - // vehicle should start on link end and go directly to next - // vehicle drives the last link completely - - // TODO: Zwischenebene mit Segmenten - - // TODO: blockId attribute für links - - // TODO: Kreuzungsweiche, Knoten mit Belegungslogik - - // TODO: Alle x Sekunden links vorreservieren - - // 1 extrem fall Reservation für ganze strecke im vorraus - - // bei train departure wird zukünftiger fahrweg mit übergeben - - // Disposition interface - - // TODO: tail link umstellen auf entfernung from node - // head position = 0, link länge - zug länge - - // TODO: erste link darf auch negativ sein - // run railsim example - public static void main(String[] args) { if (args.length == 0) { diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java new file mode 100644 index 00000000000..781bbbdd30a --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -0,0 +1,44 @@ +package ch.sbb.matsim.contrib.railsim.integration; + +import ch.sbb.matsim.contrib.railsim.RailsimModule; +import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.Controler; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.testcases.MatsimTestUtils; + +import java.io.File; + +public class RailsimIntegrationTest { + + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public void scenario0() { + + File dir = new File(utils.getPackageInputDirectory(), "test0"); + + Config config = ConfigUtils.loadConfig(new File(dir, "config.xml").toString()); + + config.controler().setOutputDirectory(utils.getOutputDirectory()); + + Scenario scenario = ScenarioUtils.loadScenario(config); + Controler controler = new Controler(scenario); + + controler.addOverridingModule(new RailsimModule()); + + /* + controler.configureQSimComponents(components -> { + new RailsimQSimModule().configure(components); + }); + */ + + controler.run(); + + } +} diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml new file mode 100644 index 00000000000..ac6b478fe47 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml @@ -0,0 +1,78 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + l45 + + + + + l45 + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml new file mode 100644 index 00000000000..cfe989052e2 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml new file mode 100644 index 00000000000..a773dc87f0a --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml @@ -0,0 +1,34 @@ + + + + + + + 5.0 + serial + 5.0 + 0.5 + 0.5 + + + + + + + + + + + + + + + + + + + + + + From 4ca80b78d591925b1ae7fceda1d64d7b99e34206 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 26 May 2023 16:01:16 +0200 Subject: [PATCH 046/258] basic integration test working (no pt stops yet) --- .../matsim/contrib/railsim/RailsimModule.java | 16 +++- .../contrib/railsim/RunRailsimExample.java | 7 +- .../RailsimLinkStateAnalysisModule.java | 31 -------- .../RailsimTrainStateAnalysisModule.java | 31 -------- .../RailsimTrainStateControlerListener.java | 2 +- .../railsim/qsimengine/RailsimCalc.java | 10 ++- .../railsim/qsimengine/RailsimEngine.java | 11 ++- .../contrib/railsim/qsimengine/TrainInfo.java | 14 ++++ .../integration/RailsimIntegrationTest.java | 7 +- .../railsim/qsimengine/RailsimEngineTest.java | 6 +- .../railsim/integration/test0/config.xml | 6 +- .../integration/test0/trainNetwork.xml | 78 ------------------- .../integration/test0/transitVehicles.xml | 8 +- 13 files changed, 52 insertions(+), 175 deletions(-) delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java index 4e4bed746c4..9d25bc4a9a9 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimModule.java @@ -19,20 +19,28 @@ package ch.sbb.matsim.contrib.railsim; -import ch.sbb.matsim.contrib.railsim.analysis.linkstates.RailsimLinkStateAnalysisModule; -import ch.sbb.matsim.contrib.railsim.analysis.trainstates.RailsimTrainStateAnalysisModule; +import ch.sbb.matsim.contrib.railsim.analysis.linkstates.RailsimLinkStateControlerListener; +import ch.sbb.matsim.contrib.railsim.analysis.trainstates.RailsimTrainStateControlerListener; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; +import com.google.inject.Singleton; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.AbstractModule; +/** + * Railsim module installing all needed component. + */ public class RailsimModule extends AbstractModule { @Override public void install() { installQSimModule(new RailsimQSimModule()); ConfigUtils.addOrGetModule(getConfig(), RailsimConfigGroup.class); - install(new RailsimLinkStateAnalysisModule()); - install(new RailsimTrainStateAnalysisModule()); + + bind(RailsimLinkStateControlerListener.class).in(Singleton.class); + addControlerListenerBinding().to(RailsimLinkStateControlerListener.class); + + bind(RailsimTrainStateControlerListener.class).in(Singleton.class); + addControlerListenerBinding().to(RailsimTrainStateControlerListener.class); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java index 962a96de921..0d954193140 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java @@ -48,10 +48,9 @@ public static void main(String[] args) { Controler controler = new Controler(scenario); controler.addOverridingModule(new RailsimModule()); - controler.configureQSimComponents(components -> { - new RailsimQSimModule().configure(components); - // if you have other extensions that provide QSim components, call their configure-method here - }); + + // if you have other extensions that provide QSim components, call their configure-method here + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); controler.run(); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java deleted file mode 100644 index 66a2be4de8e..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateAnalysisModule.java +++ /dev/null @@ -1,31 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * * - * *********************************************************************** * - * * - * copyright : (C) 2023 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package ch.sbb.matsim.contrib.railsim.analysis.linkstates; - -import com.google.inject.Singleton; -import org.matsim.core.controler.AbstractModule; - -public final class RailsimLinkStateAnalysisModule extends AbstractModule { - @Override - public void install() { - bind(RailsimLinkStateControlerListener.class).in(Singleton.class); - addControlerListenerBinding().to(RailsimLinkStateControlerListener.class); - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java deleted file mode 100644 index 8105bc2a09f..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateAnalysisModule.java +++ /dev/null @@ -1,31 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * * - * *********************************************************************** * - * * - * copyright : (C) 2023 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package ch.sbb.matsim.contrib.railsim.analysis.trainstates; - -import com.google.inject.Singleton; -import org.matsim.core.controler.AbstractModule; - -public final class RailsimTrainStateAnalysisModule extends AbstractModule { - @Override - public void install() { - bind(RailsimTrainStateControlerListener.class).in(Singleton.class); - addControlerListenerBinding().to(RailsimTrainStateControlerListener.class); - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java index 5703b4d66aa..505a478e9d7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java @@ -52,7 +52,7 @@ public void notifyIterationStarts(IterationStartsEvent event) { @Override public void notifyIterationEnds(IterationEndsEvent event) { - String railLinkStatesCsvFilename = this.controlerIO.getIterationFilename(event.getIteration(), "trainStates.csv", this.scenario.getConfig().controler().getCompressionType()); + String railLinkStatesCsvFilename = this.controlerIO.getIterationFilename(event.getIteration(), "railsimTrainStates.csv", this.scenario.getConfig().controler().getCompressionType()); RailsimCsvWriter.writeTrainStatesCsv(this.analysis.events, this.scenario.getNetwork(), railLinkStatesCsvFilename); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 08464e7e3ca..61e2ef19010 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -33,6 +33,9 @@ static double solveTraveledDist(double speed, double dist, double acceleration) */ static double calcRequiredTime(TrainState state, double dist) { + if (dist == 0) + return 0; + if (state.acceleration == 0) return state.speed == 0 ? Double.POSITIVE_INFINITY : dist / state.speed; @@ -57,7 +60,9 @@ static double calcRequiredTime(TrainState state, double dist) { // max distance that can be reached double max = calcTraveledDist(state.speed, decelTime, state.acceleration); - if (dist <= max) { + if (FuzzyUtils.equals(dist, max)) { + return decelTime; + } else if (dist <= max) { return solveTraveledDist(state.speed, dist, state.acceleration); } else return Double.POSITIVE_INFINITY; @@ -130,8 +135,7 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) if (i == state.route.size()) { link = null; allowed = 0; - } - else { + } else { link = state.route.get(i); allowed = link.getAllowedFreespeed(state.driver); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 1957e91a148..d8d8a38b7a1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -17,6 +17,7 @@ import org.matsim.core.mobsim.framework.MobsimDriverAgent; import org.matsim.core.mobsim.framework.Steppable; import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.vehicles.VehicleType; import java.util.*; import java.util.stream.Collectors; @@ -91,7 +92,7 @@ public void updateAllStates(double time) { */ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id linkId, NetworkRoute route) { - log.info("Train {} is departing", agent.getVehicle()); + log.debug("Train {} is departing at {}", agent.getVehicle(), now); this.eventsManager.processEvent(new PersonEntersVehicleEvent(now, agent.getId(), agent.getVehicle().getId())); this.eventsManager.processEvent(new VehicleEntersTrafficEvent(now, agent.getId(), linkId, agent.getVehicle().getId(), agent.getMode(), 1.0)); @@ -100,8 +101,10 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin list.add(0, resources.getLink(linkId)); list.add(resources.getLink(route.getEndLinkId())); - TrainState state = new TrainState(agent, new TrainInfo(agent.getVehicle().getVehicle().getType(), config), - now, linkId, list); + VehicleType type = agent.getVehicle().getVehicle().getType(); + TrainState state = new TrainState(agent, new TrainInfo(type, config), now, linkId, list); + + state.train.checkConsistency(); activeTrains.add(state); @@ -465,6 +468,8 @@ private void decideNextUpdate(double time, UpdateEvent event) { event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; event.plannedTime = event.checkReservation; } + + assert Double.isFinite(event.plannedTime) : "Planned update time must be finite, but was " + event.plannedTime; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java index 896b6f5436a..18af61e4f6d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java @@ -3,12 +3,14 @@ import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import org.matsim.api.core.v01.Id; import org.matsim.vehicles.VehicleType; /** * Non-mutable static information for a single train. */ record TrainInfo( + Id id, double length, double maxVelocity, double acceleration, @@ -19,6 +21,7 @@ record TrainInfo( public TrainInfo(VehicleType vehicle, RailsimConfigGroup config) { // TODO: this( + vehicle.getId(), vehicle.getLength(), vehicle.getMaximumVelocity(), RailsimUtils.getTrainAcceleration(vehicle, config), @@ -27,4 +30,15 @@ public TrainInfo(VehicleType vehicle, RailsimConfigGroup config) { ); } + public void checkConsistency() { + if (!Double.isFinite(maxVelocity) || maxVelocity <= 0) + throw new IllegalArgumentException("Train of type " + id + " does not have a finite maximumVelocity."); + + if (!Double.isFinite(acceleration) || acceleration <= 0) + throw new IllegalArgumentException("Train of type " + id + " does not have a finite and positive acceleration."); + + if (!Double.isFinite(deceleration) || deceleration <= 0) + throw new IllegalArgumentException("Train of type " + id + " does not have a finite and positive deceleration."); + + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 781bbbdd30a..4ab37d9186c 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -31,12 +31,7 @@ public void scenario0() { Controler controler = new Controler(scenario); controler.addOverridingModule(new RailsimModule()); - - /* - controler.configureQSimComponents(components -> { - new RailsimQSimModule().configure(components); - }); - */ + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); controler.run(); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index ce3714c39bb..4008640b750 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -74,7 +74,6 @@ public void congested() { RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); test.doSimStepUntil(600); - } @@ -88,7 +87,7 @@ public void opposite() { test.doSimStepUntil(600); - test.debug(collector, "opposite"); +// test.debug(collector, "opposite"); RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 292.5454533774468, 600, 0) @@ -102,8 +101,7 @@ public void opposite() { test.doStateUpdatesUntil(600, 1); - test.debug(collector, "opposite_detailed"); - +// test.debug(collector, "opposite_detailed"); RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 292.5454533774468, 600, 0) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml index 80d11a1d7ab..5725cce9371 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml @@ -10,6 +10,7 @@ + @@ -19,9 +20,6 @@ - - - @@ -33,7 +31,7 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml deleted file mode 100644 index ac6b478fe47..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/trainNetwork.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - l45 - - - - - l45 - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml index a773dc87f0a..dce684b4165 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml @@ -11,14 +11,10 @@ 0.5 0.5 - - - + - - + - From 86d9569b513c89103d3f9cea034f0fe11bfdb235 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 31 May 2023 16:33:06 +0200 Subject: [PATCH 047/258] add transit stop handling, added a larger integration test --- .../railsim/config/RailsimConfigGroup.java | 12 +- .../railsim/qsimengine/RailsimCalc.java | 12 +- .../railsim/qsimengine/RailsimEngine.java | 144 +++++++++++++----- .../railsim/qsimengine/TrainState.java | 15 ++ .../railsim/qsimengine/UpdateEvent.java | 7 + .../integration/RailsimIntegrationTest.java | 20 +++ .../integration/test0/transitSchedule.xml | 1 + .../railsim/integration/test_genf/config.xml | 35 +++++ .../test_genf/transitSchedule.xml.gz | Bin 0 -> 70575 bytes .../test_genf/transitVehicles.xml.gz | Bin 0 -> 1366 bytes .../railsim/qsimengine/network_genf.xml.gz | Bin 0 -> 159748 bytes 11 files changed, 204 insertions(+), 42 deletions(-) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitSchedule.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitVehicles.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index 471a6650cb2..5d6e4c64cb0 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -20,6 +20,7 @@ package ch.sbb.matsim.contrib.railsim.config; import ch.sbb.matsim.contrib.railsim.RailsimUtils; +import org.matsim.core.config.Config; import org.matsim.core.config.ReflectiveConfigGroup; import java.util.Set; @@ -51,8 +52,17 @@ public Set getRailNetworkModes() { return Set.of(railNetworkModes.split(",")); } + @Override + protected void checkConsistency(Config config) { + super.checkConsistency(config); + for (String mode : getRailNetworkModes()) { + if (config.qsim().getMainModes().contains(mode)) { + throw new IllegalArgumentException(String.format("Railsim mode '%s' must not be a network mode in qsim.", mode)); + } + } + } + // TODO: add config parameters - // - "railNetworkModes", default "rail" // - "railVehicleModes", default "rail"(or "train"?) // - "linkEventsInterval", default "10", the iteration-interval when link-events should be generated by railsim // - "visualizationInterval", default "10", the iteration-interval when various csv-files should be generated used to visualize the results in Via diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 61e2ef19010..c21d3f142ac 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -116,8 +116,10 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) double assumedSpeed = state.speed; + double maxSpeed = Math.max(assumedSpeed, state.allowedMaxSpeed); + // Lookahead window - double window = RailsimCalc.calcTraveledDist(assumedSpeed, assumedSpeed / state.train.deceleration(), + double window = RailsimCalc.calcTraveledDist(maxSpeed, maxSpeed / state.train.deceleration(), -state.train.deceleration()) + currentLink.length; // Distance to the next speed change point (link) @@ -137,7 +139,13 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) allowed = 0; } else { link = state.route.get(i); - allowed = link.getAllowedFreespeed(state.driver); + + // If the previous link is a transit stop the speed needs to be 0 at the next link + // train stops at the very end of a link + if (i > 0 && state.isStop(state.route.get(i-1).getLinkId())) + allowed = 0; + else + allowed = link.getAllowedFreespeed(state.driver); } if (allowed < assumedSpeed) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index d8d8a38b7a1..286df677fe6 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -8,10 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.events.LinkEnterEvent; -import org.matsim.api.core.v01.events.LinkLeaveEvent; -import org.matsim.api.core.v01.events.PersonEntersVehicleEvent; -import org.matsim.api.core.v01.events.VehicleEntersTrafficEvent; +import org.matsim.api.core.v01.events.*; import org.matsim.api.core.v01.network.Link; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimDriverAgent; @@ -94,8 +91,9 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin log.debug("Train {} is departing at {}", agent.getVehicle(), now); - this.eventsManager.processEvent(new PersonEntersVehicleEvent(now, agent.getId(), agent.getVehicle().getId())); - this.eventsManager.processEvent(new VehicleEntersTrafficEvent(now, agent.getId(), linkId, agent.getVehicle().getId(), agent.getMode(), 1.0)); + // Queue the update event + // NO events can be generated here, or temporal ordering is not guaranteed + // (departures are handled before event queue is processed) List list = route.getLinkIds().stream().map(resources::getLink).collect(Collectors.toList()); list.add(0, resources.getLink(linkId)); @@ -115,6 +113,18 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin return true; } + private void createEvent(Event event) { + + // Because of the 1s update interval, events need to be rounded to the current simulation step +// event.setTime(Math.floor(event.getTime())); + +// System.out.println(event.getTime()); + + // TODO: event ordering can be violated sometimes + + this.eventsManager.processEvent(event); + } + private void updateState(double time, UpdateEvent event) { // Do different updates depending on the type @@ -122,7 +132,7 @@ private void updateState(double time, UpdateEvent event) { case DEPARTURE -> updateDeparture(time, event); case POSITION -> { updatePosition(time, event); - decideNextUpdate(time, event); + decideNextUpdate(event); } case SPEED_CHANGE -> updateSpeed(time, event); case ENTER_LINK -> enterLink(time, event); @@ -149,9 +159,9 @@ else if (state.targetSpeed > state.speed) // TODO: check if needed or not // event.newSpeed = -1; - eventsManager.processEvent(state.asEvent(time)); + createEvent(state.asEvent(time)); - decideNextUpdate(time, event); + decideNextUpdate(event); } private void blockTrack(double time, UpdateEvent event) { @@ -169,11 +179,11 @@ private void blockTrack(double time, UpdateEvent event) { state.acceleration = -state.train.deceleration(); event.checkReservation = time + POLL_INTERVAL; - decideNextUpdate(time, event); + decideNextUpdate(event); } else { event.checkReservation = -1; - decideNextUpdate(time, event); + decideNextUpdate(event); } } @@ -192,12 +202,12 @@ private void checkTrackReservation(double time, UpdateEvent event) { state.acceleration = state.train.acceleration(); event.checkReservation = -1; - decideNextUpdate(time, event); + decideNextUpdate(event); } else { event.checkReservation = time + POLL_INTERVAL; - decideNextUpdate(time, event); + decideNextUpdate(event); } @@ -213,15 +223,30 @@ private void updateDeparture(double time, UpdateEvent event) { if (blockLinkTracks(time, 0, state)) { + createEvent(new PersonEntersVehicleEvent(time, state.driver.getId(), state.driver.getVehicle().getId())); + createEvent(new VehicleEntersTrafficEvent(time, state.driver.getId(), + state.headLink, state.driver.getVehicle().getId(), state.driver.getMode(), 1.0)); + state.timestamp = time; - eventsManager.processEvent(state.asEvent(time)); + double stopTime = 0; + if (state.isStop(state.headLink)) { + stopTime = handleTransitStop(time, state); + } // Train departs at the very end of the first link state.routeIdx = 1; - // Call enter link logic immediately - enterLink(time, event); + createEvent(state.asEvent(time)); + + if (stopTime == 0) { + // Call enter link logic immediately + enterLink(time, event); + } else { + event.plannedTime = time + stopTime; + event.type = UpdateEvent.Type.ENTER_LINK; + } + } else { // vehicle will wait and call departure again event.plannedTime += POLL_INTERVAL; @@ -246,7 +271,7 @@ private boolean blockLinkTracks(double time, int idx, TrainState state) { continue; int track = link.blockTrack(state.driver); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), + createEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), state.driver.getVehicle().getId(), TrackState.BLOCKED, track)); } @@ -260,6 +285,19 @@ private void enterLink(double time, UpdateEvent event) { updatePosition(time, event); + // current head link is the pt stop, which means the train is at the end of the link when this is called + if (state.isStop(state.headLink)) { + + double stopTime = handleTransitStop(time, state); + + assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at pt stop " + state.speed; + + // Same event is re-scheduled after stopping, + event.plannedTime = time + stopTime; + + return; + } + // Arrival at destination if (state.isRouteAtEnd()) { @@ -270,7 +308,7 @@ private void enterLink(double time, UpdateEvent event) { if (link.isBlockedBy(state.driver)) { int track = link.releaseTrack(state.driver); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), state.driver.getVehicle().getId(), + createEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), state.driver.getVehicle().getId(), TrackState.FREE, track)); disposition.unblockRailLink(time, state.driver, link); @@ -287,28 +325,34 @@ private void enterLink(double time, UpdateEvent event) { } // On route departure the head link is null - eventsManager.processEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); + createEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); // Get link and increment state.headPosition = 0; state.headLink = state.route.get(state.routeIdx++).getLinkId(); - state.driver.notifyMoveOverNode(state.headLink); - eventsManager.processEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); + createEvent(new LinkEnterEvent(time, state.driver.getVehicle().getId(), state.headLink)); RailLink link = resources.getLink(state.headLink); + assert link.isBlockedBy(state.driver) : "Link has to be blocked by driver when entered"; + // TODO: this probably needs to be a separate function to calculate possible target speed more accurately - if (RailsimCalc.calcDecelDistanceAndSpeed(link, event) == Double.POSITIVE_INFINITY) { + if (RailsimCalc.calcDecelDistanceAndSpeed(link, event) == Double.POSITIVE_INFINITY && + !event.isAwaitingReservation()) { + state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); - state.targetSpeed = state.allowedMaxSpeed; - state.acceleration = state.train.acceleration(); + + if (state.allowedMaxSpeed > state.targetSpeed) { + state.targetSpeed = state.allowedMaxSpeed; + state.acceleration = state.train.acceleration(); + } } - eventsManager.processEvent(state.asEvent(time)); + createEvent(state.asEvent(time)); - decideNextUpdate(time, event); + decideNextUpdate(event); } private void leaveLink(double time, UpdateEvent event) { @@ -328,10 +372,10 @@ private void leaveLink(double time, UpdateEvent event) { updatePosition(time, event); - eventsManager.processEvent(new RailsimTrainLeavesLinkEvent(time, state.driver.getVehicle().getId(), state.tailLink)); + createEvent(new RailsimTrainLeavesLinkEvent(time, state.driver.getVehicle().getId(), state.tailLink)); // TODO: link should be released after headway time int track = tailLink.releaseTrack(state.driver); - eventsManager.processEvent(new RailsimLinkStateChangeEvent(time, state.tailLink, state.driver.getVehicle().getId(), + createEvent(new RailsimLinkStateChangeEvent(time, state.tailLink, state.driver.getVehicle().getId(), TrackState.FREE, track)); disposition.unblockRailLink(time, state.driver, resources.getLink(state.tailLink)); @@ -339,7 +383,7 @@ private void leaveLink(double time, UpdateEvent event) { state.tailLink = nextTailLink.getLinkId(); state.tailPosition = 0; - decideNextUpdate(time, event); + decideNextUpdate(event); } /** @@ -396,13 +440,28 @@ private void updatePosition(double time, UpdateEvent event) { assert FuzzyUtils.lessEqualThan(state.headPosition, resources.getLink(state.headLink).length) : "Illegal state update. Head position must be smaller than link length"; assert FuzzyUtils.greaterEqualThan(state.headPosition, 0) : "Head position must be positive"; - eventsManager.processEvent(state.asEvent(time)); + createEvent(state.asEvent(time)); + } + + /** + * Handle transit stop and update the state. + * + * @return stop time + */ + private double handleTransitStop(double time, TrainState state) { + + assert state.pt != null : "Pt driver must be present"; + + double stopTime = state.pt.handleTransitStop(state.nextStop, time); + state.nextStop = state.pt.getNextTransitStop(); + + return stopTime; } /** * Decide which update is the earliest and needs to be the next. */ - private void decideNextUpdate(double time, UpdateEvent event) { + private void decideNextUpdate(UpdateEvent event) { TrainState state = event.state; RailLink currentLink = resources.getLink(state.headLink); @@ -418,11 +477,11 @@ private void decideNextUpdate(double time, UpdateEvent event) { Double.POSITIVE_INFINITY : RailsimCalc.calcDecelDistanceAndSpeed(currentLink, event); - assert FuzzyUtils.greaterEqualThan(decelDist, 0) : "Deceleration distance must be larger than 0"; + assert FuzzyUtils.greaterEqualThan(decelDist, 0) : "Deceleration distance must be larger than 0, but was" + decelDist; // (3) next link needs reservation double reserveDist = Double.POSITIVE_INFINITY; - if (!state.isRouteAtEnd() && event.checkReservation < 0) { + if (!state.isRouteAtEnd() && !event.isAwaitingReservation()) { reserveDist = RailsimCalc.nextLinkReservation(state); if (reserveDist < 0) @@ -461,7 +520,7 @@ private void decideNextUpdate(double time, UpdateEvent event) { assert FuzzyUtils.greaterEqualThan(dist, 0) : "Distance for next update must be positive"; // dist is the minimum of all supplied distances - event.plannedTime = time + RailsimCalc.calcRequiredTime(state, dist); + event.plannedTime = state.timestamp + RailsimCalc.calcRequiredTime(state, dist); // insert reservation event if necessary if (event.checkReservation >= 0 && event.plannedTime > event.checkReservation) { @@ -477,12 +536,19 @@ private void decideNextUpdate(double time, UpdateEvent event) { * Allowed speed for the train. */ private double retrieveAllowedMaxSpeed(TrainState state) { - // TODO: needs to check the whole part of current route - return Math.min( - resources.getLink(state.headLink).getAllowedFreespeed(state.driver), - resources.getLink(state.tailLink).getAllowedFreespeed(state.driver) - ); + double maxSpeed = resources.getLink(state.headLink).getAllowedFreespeed(state.driver); + + for (int i = state.routeIdx - 1; i >= 0; i--) { + RailLink link = state.route.get(i); + maxSpeed = Math.min(maxSpeed, link.getAllowedFreespeed(state.driver)); + if (link.getLinkId().equals(state.tailLink)) { + break; + } + + } + + return maxSpeed; } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 8a3ddf3d8c2..2fd786dbf14 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -5,6 +5,7 @@ import org.matsim.api.core.v01.network.Link; import org.matsim.core.mobsim.framework.MobsimDriverAgent; import org.matsim.core.mobsim.qsim.pt.TransitDriverAgent; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; import javax.annotation.Nullable; import java.util.List; @@ -25,6 +26,12 @@ final class TrainState { @Nullable final TransitDriverAgent pt; + /** + * Next transit stop. + */ + @Nullable + TransitStopFacility nextStop; + /** * Train specific parameters. */ @@ -90,6 +97,7 @@ final class TrainState { TrainState(MobsimDriverAgent driver, TrainInfo train, double timestamp, @Nullable Id linkId, List route) { this.driver = driver; this.pt = driver instanceof TransitDriverAgent ptDriver ? ptDriver : null; + this.nextStop = pt != null ? pt.getNextTransitStop() : null; this.train = train; this.route = route; this.timestamp = timestamp; @@ -118,6 +126,13 @@ boolean isRouteAtEnd() { return routeIdx >= route.size(); } + /** + * Check whether to stop at certain link. + */ + boolean isStop(Id link) { + return nextStop != null && nextStop.getLinkId().equals(link); + } + RailsimTrainStateEvent asEvent(double time) { return new RailsimTrainStateEvent(time, driver.getVehicle().getId(), headLink, headPosition, diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 06ed57c3952..c2b3fd383c8 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -43,6 +43,13 @@ public int hashCode() { return Objects.hash(state); } + /** + * This train currently waits for an reservation for blocked tracks. + */ + boolean isAwaitingReservation() { + return checkReservation >= 0; + } + /** * The type of the requested update. */ diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 4ab37d9186c..fa73b4d2027 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -36,4 +36,24 @@ public void scenario0() { controler.run(); } + + @Test + public void scenario_genf() { + + File dir = new File(utils.getPackageInputDirectory(), "test_genf"); + + Config config = ConfigUtils.loadConfig(new File(dir, "config.xml").toString()); + + config.controler().setOutputDirectory(utils.getOutputDirectory()); + + Scenario scenario = ScenarioUtils.loadScenario(config); + Controler controler = new Controler(scenario); + + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); + + controler.run(); + + } + } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml index cfe989052e2..748f927f15f 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml @@ -25,6 +25,7 @@ + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml new file mode 100644 index 00000000000..9dc11a58822 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitSchedule.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitSchedule.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..5989dd29a2da50987ae36f330d55f9c789b06a11 GIT binary patch literal 70575 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 literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitVehicles.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..25ad72a9773194a1f7bddab3c9f2e74ec47735fd GIT binary patch literal 1366 zcmb2|=3sz;w{vp+Z+nQe+8WtG7dpIZW5%5%)s>QGW>C=PbCIYUO zdzF_P_0{bETqO9|`u3}+*ELD2T<6)W`}C|%vA4j;q}!+E zgYR%^3!OZ(%WL|5rOdchTs&6(aaFH9XQl1eO$mE}_=I=@xmwKLV{l0C@VFdwh!Iq@unhnJPBXC++B+j#S0X3oFwq)0W%p<5J&!SUPB1 zd-6uU$Eh{ii?`SmS~|>4+U@Rqd7JL<2VWO(U)r<7Z&{|;^s5zfPJUke>VZ>k`>e@l z>O^n8c%NFKenq7FP6SI)y;7%6Smw9u%hnvbciq`o;O5~8nN!xeUCxwf;rjIZZJqSx zPnXYSUbuO9fsmNITUU=*HwdPzNi+7oZGCiqkIVP{CtS{N|I-})|9R7*z5ZJQwglKX z8b;}x&CICZm~nE2Yuoe4d;d=V=RBB^FR3e`E76}2Q@<}aUsP96SCHLh%lD3H9sdml zRe8_c7h~@Bbn`Hz-=r&gA!%e-32ajz?NRBvg<@Ui14-z5@40J3O?1eKW#AJB5`Px{0 z7RK;!>$S0pA8zVSJjj}B2xA`I@E|GTz?%*2i3bmFc#s(J!9b@%Ld+&$DTL(s}CTxASvq6n{7Vk`(d0<>A`t0U} z@Wgq%l`9|KoM7`Y$74Z-nM$CJv`@~9b-d@<4l~`|`tW8##Dd*sHqt!YCD+^1n-d$~ z-TIJYpp)>*w>-DPOhOE-a*H{Xfusd!%tFG(l+A#ccOh1K9&a1#uI$5`8AG{Hj5%`+ zN%G)^22Wl!lRIbL*gjnP{>B@7rZC65*~{|Eigr)DmARx$dgWfXto;*zt(my%zDMo3 zu=nk;v!{hO*Inq(1S!f5(O$hZaP69FZd*4+Nnbscg(?v0^!HDuMr~ZYjdAGJfgz?wodiST^b$z@@m+xty9ytPW^l9RPnS=Q>A~u^E|oO%lFSJt=IEI sg!gv=nOa(=A6Kuqa%BCYAXI_;coEQo;t0MYE9%K!iX literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..22bff2fe04fd3fea4f9b7b5530299185c0586463 GIT binary patch literal 159748 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 RrfNpm`0>3wD>p^H{|9O_)Z_pF literal 0 HcmV?d00001 From 0f6d5ab7b87b31afcfb3036132fd814eb2235a8d Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 1 Jun 2023 11:19:25 +0200 Subject: [PATCH 048/258] fix issue with event ordering, current SimStepParallel event manger will lead to exceptions with sub second events, improve assertions, bigger intergration test runs without exception --- .../railsim/qsimengine/RailsimCalc.java | 47 ++++++++++--------- .../railsim/qsimengine/RailsimEngine.java | 31 ++++++------ .../railsim/qsimengine/RailsimEngineTest.java | 12 ++--- .../railsim/integration/test_genf/config.xml | 2 + 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index c21d3f142ac..01400f94dd9 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -33,7 +33,7 @@ static double solveTraveledDist(double speed, double dist, double acceleration) */ static double calcRequiredTime(TrainState state, double dist) { - if (dist == 0) + if (FuzzyUtils.equals(dist, 0)) return 0; if (state.acceleration == 0) @@ -76,8 +76,6 @@ static double calcRequiredTime(TrainState state, double dist) { static SpeedTarget calcTargetSpeed(double dist, double acceleration, double deceleration, double currentSpeed, double targetSpeed, double finalSpeed) { - assert FuzzyUtils.greaterEqualThan(targetSpeed, finalSpeed) : "Final speed must be smaller than target"; - double timeDecel = (targetSpeed - finalSpeed) / deceleration; double distDecel = calcTraveledDist(targetSpeed, timeDecel, -deceleration); @@ -85,6 +83,8 @@ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double dece if (acceleration <= 0 || currentSpeed >= targetSpeed) return new SpeedTarget(targetSpeed, distDecel); + assert FuzzyUtils.greaterEqualThan(targetSpeed, finalSpeed) : "Final speed must be smaller than target"; + double timeAccel = (targetSpeed - currentSpeed) / acceleration; double distAccel = calcTraveledDist(currentSpeed, timeAccel, acceleration); @@ -142,7 +142,7 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) // If the previous link is a transit stop the speed needs to be 0 at the next link // train stops at the very end of a link - if (i > 0 && state.isStop(state.route.get(i-1).getLinkId())) + if (i > 0 && state.isStop(state.route.get(i - 1).getLinkId())) allowed = 0; else allowed = link.getAllowedFreespeed(state.driver); @@ -179,31 +179,36 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) * Calculate when the reservation function should be triggered. * Should return {@link Double#POSITIVE_INFINITY} if this distance is far in the future and can be checked at later point. * - * @param state current train state. + * @param state current train state + * @param currentLink the link where the train head is on * @return travel distance after which reservations should be updated. */ - public static double nextLinkReservation(TrainState state) { + public static double nextLinkReservation(TrainState state, RailLink currentLink) { // time needed for full stop - double stopTime = state.allowedMaxSpeed / state.train.deceleration(); + double assumedSpeed = state.train.maxVelocity(); + double stopTime = assumedSpeed / state.train.deceleration(); assert stopTime > 0 : "Stop time can not be negative"; - // Distance for full stop - double safetyDist = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, stopTime, -state.train.deceleration()); + // TODO: there is an additional safety factor (also in links to block) + // this might be reduced, but currently the case when a train stops exactly before the not reserved link is not handled - double dist = -state.headPosition - safetyDist; - int idx = state.routeIdx; + // safety distance + double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) * 1.5; + + double dist = currentLink.length - state.headPosition; - while (dist <= safetyDist && idx < state.route.size()) { + int idx = state.routeIdx; + do { RailLink nextLink = state.route.get(idx++); - dist += nextLink.length; - if (nextLink.isBlockedBy(state.driver)) - continue; + if (!nextLink.isBlockedBy(state.driver)) + return dist - safety; - return dist; - } + dist += nextLink.length; + + } while (dist <= safety && idx < state.route.size()); // No need to reserve yet return Double.POSITIVE_INFINITY; @@ -220,16 +225,14 @@ public static List calcLinksToBlock(int idx, TrainState state) { double stopTime = assumedSpeed / state.train.deceleration(); // safety distance - double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) + state.headPosition; + double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) * 1.5 + state.headPosition; double reserved = 0; - - do { + while (reserved < safety && idx < state.route.size()) { RailLink nextLink = state.route.get(idx++); result.add(nextLink); reserved += nextLink.length; - - } while (reserved < safety && idx < state.route.size()); + } return result; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 286df677fe6..070d19219cc 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -114,14 +114,9 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin } private void createEvent(Event event) { - // Because of the 1s update interval, events need to be rounded to the current simulation step -// event.setTime(Math.floor(event.getTime())); - -// System.out.println(event.getTime()); - - // TODO: event ordering can be violated sometimes - + event.setTime(Math.ceil(event.getTime())); +// System.out.println(event.getTime()); this.eventsManager.processEvent(event); } @@ -156,8 +151,7 @@ else if (state.targetSpeed > state.speed) state.acceleration = state.train.acceleration(); // Remove update information - // TODO: check if needed or not - // event.newSpeed = -1; + event.newSpeed = -1; createEvent(state.asEvent(time)); @@ -290,7 +284,7 @@ private void enterLink(double time, UpdateEvent event) { double stopTime = handleTransitStop(time, state); - assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at pt stop " + state.speed; + assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at pt stop, but was " + state.speed; // Same event is re-scheduled after stopping, event.plannedTime = time + stopTime; @@ -301,7 +295,7 @@ private void enterLink(double time, UpdateEvent event) { // Arrival at destination if (state.isRouteAtEnd()) { - assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end but was " + state.speed; + assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end, but was " + state.speed; // Free all reservations for (RailLink link : state.route) { @@ -316,7 +310,7 @@ private void enterLink(double time, UpdateEvent event) { } state.driver.notifyArrivalOnLinkByNonNetworkMode(state.headLink); - state.driver.endLegAndComputeNextState(time); + state.driver.endLegAndComputeNextState(Math.ceil(time)); activeTrains.remove(state); @@ -439,6 +433,7 @@ private void updatePosition(double time, UpdateEvent event) { assert state.routeIdx <= 1 || FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; assert FuzzyUtils.lessEqualThan(state.headPosition, resources.getLink(state.headLink).length) : "Illegal state update. Head position must be smaller than link length"; assert FuzzyUtils.greaterEqualThan(state.headPosition, 0) : "Head position must be positive"; + assert FuzzyUtils.lessEqualThan(state.speed, state.allowedMaxSpeed) : "Speed must be less equal than the allowed speed"; createEvent(state.asEvent(time)); } @@ -452,7 +447,8 @@ private double handleTransitStop(double time, TrainState state) { assert state.pt != null : "Pt driver must be present"; - double stopTime = state.pt.handleTransitStop(state.nextStop, time); + // Time needs to be rounded to current sim step + double stopTime = state.pt.handleTransitStop(state.nextStop, Math.ceil(time)); state.nextStop = state.pt.getNextTransitStop(); return stopTime; @@ -466,6 +462,9 @@ private void decideNextUpdate(UpdateEvent event) { TrainState state = event.state; RailLink currentLink = resources.getLink(state.headLink); +// if (state.routeIdx >= 877 && state.routeIdx <= 880 && state.timestamp >= 7300) +// log.info("debug"); + // (1) max speed reached double accelDist = Double.POSITIVE_INFINITY; if (state.acceleration > 0 && FuzzyUtils.greaterThan(state.targetSpeed, state.speed)) { @@ -482,7 +481,7 @@ private void decideNextUpdate(UpdateEvent event) { // (3) next link needs reservation double reserveDist = Double.POSITIVE_INFINITY; if (!state.isRouteAtEnd() && !event.isAwaitingReservation()) { - reserveDist = RailsimCalc.nextLinkReservation(state); + reserveDist = RailsimCalc.nextLinkReservation(state, currentLink); if (reserveDist < 0) reserveDist = 0; @@ -494,8 +493,8 @@ private void decideNextUpdate(UpdateEvent event) { // (5) head link changes double headDist = currentLink.length - state.headPosition; - assert tailDist >= 0 : "Tail distance must be positive"; - assert headDist >= 0 : "Head distance must be positive"; + assert FuzzyUtils.greaterEqualThan(tailDist, 0) : "Tail distance must be positive"; + assert FuzzyUtils.greaterEqualThan(headDist, 0) : "Head distance must be positive"; // Find the earliest required update diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 4008640b750..a38f2234a0a 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -51,7 +51,7 @@ public void simple() { RailsimTestUtils.assertThat(collector) .hasSizeGreaterThan(5) .hasTrainState("train", 144, 0, 44) - .hasTrainState("train", 233.4545441058463, 2000, 0); + .hasTrainState("train", 234, 2000, 0); test = getTestEngine("network0.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); @@ -61,7 +61,7 @@ public void simple() { RailsimTestUtils.assertThat(collector) .hasSizeGreaterThan(5) .hasTrainState("train", 144, 0, 44) - .hasTrainState("train", 233.4545441058463, 2000, 0); + .hasTrainState("train", 234, 2000, 0); } @@ -90,8 +90,8 @@ public void opposite() { // test.debug(collector, "opposite"); RailsimTestUtils.assertThat(collector) - .hasTrainState("regio1", 292.5454533774468, 600, 0) - .hasTrainState("regio2", 443.62677217662434, 1000, 0); + .hasTrainState("regio1", 293, 600, 0) + .hasTrainState("regio2", 358, 1000, 0); test = getTestEngine("network0.xml"); @@ -104,8 +104,8 @@ public void opposite() { // test.debug(collector, "opposite_detailed"); RailsimTestUtils.assertThat(collector) - .hasTrainState("regio1", 292.5454533774468, 600, 0) - .hasTrainState("regio2", 443.62677217662434, 1000, 0); + .hasTrainState("regio1", 293, 600, 0) + .hasTrainState("regio2", 358, 1000, 0); } } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml index 9dc11a58822..3448f158baf 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml @@ -9,6 +9,8 @@ + + From 13002090223ed958dd371024881d713507b5bafe Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Fri, 2 Jun 2023 09:34:43 +0200 Subject: [PATCH 049/258] Updated according to comments, and removed method normalising profiles --- .../ChargerPowerTimeProfileCalculator.java | 70 ++++--------------- 1 file changed, 15 insertions(+), 55 deletions(-) diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java index 4c8c8416573..e471c772fdb 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java @@ -1,8 +1,8 @@ package org.matsim.contrib.ev.stats; +import java.util.HashMap; +import java.util.Map; -import com.google.inject.Inject; -import com.google.inject.Provider; import org.matsim.api.core.v01.Id; import org.matsim.contrib.common.timeprofile.TimeDiscretizer; import org.matsim.contrib.ev.EvConfigGroup; @@ -11,87 +11,48 @@ import org.matsim.contrib.ev.charging.ChargingEndEventHandler; import org.matsim.contrib.ev.charging.ChargingStartEvent; import org.matsim.contrib.ev.charging.ChargingStartEventHandler; -import org.matsim.contrib.ev.fleet.ElectricFleet; import org.matsim.contrib.ev.infrastructure.Charger; -import org.matsim.contrib.ev.infrastructure.ChargingInfrastructure; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.config.groups.QSimConfigGroup; -import org.matsim.core.controler.MatsimServices; -import org.matsim.core.events.MobsimScopeEventHandler; -import org.matsim.core.mobsim.framework.events.MobsimAfterSimStepEvent; -import org.matsim.core.mobsim.framework.listeners.MobsimAfterSimStepListener; -import org.matsim.core.mobsim.framework.listeners.MobsimListener; import org.matsim.vehicles.Vehicle; -import java.util.*; - -import static com.google.common.collect.ImmutableMap.toImmutableMap; - +import com.google.inject.Inject; -public class ChargerPowerTimeProfileCalculator implements ChargingStartEventHandler,ChargingEndEventHandler - { +public class ChargerPowerTimeProfileCalculator implements ChargingStartEventHandler, ChargingEndEventHandler { - private Map, double[]> chargerProfiles = new HashMap<>(); - private Map,Double> chargingStartTime = new HashMap<>(); - private Map,Double> chargingStartEnergy = new HashMap<>(); + private final Map, double[]> chargerProfiles = new HashMap<>(); + private final Map, Double> chargingStartTime = new HashMap<>(); + private final Map, Double> chargingStartEnergy = new HashMap<>(); private final TimeDiscretizer timeDiscretizer; private final double qsimEndTime; - private final int chargeTimeStep; @Inject public ChargerPowerTimeProfileCalculator(Config config) { - chargeTimeStep = ConfigUtils.addOrGetModule(config, EvConfigGroup.class).chargeTimeStep; + int chargeTimeStep = ConfigUtils.addOrGetModule(config, EvConfigGroup.class).chargeTimeStep; qsimEndTime = ConfigUtils.addOrGetModule(config, QSimConfigGroup.class).getEndTime().orElse(0.0); timeDiscretizer = new TimeDiscretizer((int)Math.ceil(qsimEndTime), chargeTimeStep); } - private static final Map, List>> vehiclesAtCharger = new HashMap<>(); - private static final Map, Double> vehiclesEnergyPreviousTimeStep = new HashMap<>(); - private void normalizeProfile(double[] profile) { - for (int i = 0; i < profile.length; i++) { - profile[i] /= timeDiscretizer.getTimeInterval(); - } - } public Map, double[]> getChargerProfiles() { -// Map,double[]> chargerProfilesArray = new HashMap<>(); -// this.chargerProfiles.forEach((chargerId, doubles) -> { -// double[] doubleArray = doubles.stream().mapToDouble(Double::doubleValue).toArray(); -// chargerProfilesArray.put(chargerId,doubleArray); -// }); - chargerProfiles.values().forEach(this::normalizeProfile); return chargerProfiles; } @Override public void handleEvent(ChargingStartEvent event) { -// vehiclesEnergyPreviousTimeStep.put(event.getVehicleId(), event.getCharge()); -// List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); -// if (presentVehicles == null) { -// ArrayList> firstVehicle = new ArrayList<>(); -// firstVehicle.add(event.getVehicleId()); -// vehiclesAtCharger.put(event.getChargerId(), firstVehicle); -// } else { -// presentVehicles.add(event.getVehicleId()); -// vehiclesAtCharger.put(event.getChargerId(), presentVehicles); -// } - chargingStartTime.put(event.getVehicleId(),event.getTime()); - chargingStartEnergy.put(event.getVehicleId(),EvUnits.J_to_kWh(event.getCharge())); + chargingStartTime.put(event.getVehicleId(), event.getTime()); + chargingStartEnergy.put(event.getVehicleId(), EvUnits.J_to_kWh(event.getCharge())); } @Override public void handleEvent(ChargingEndEvent event) { -// List> presentVehicles = vehiclesAtCharger.get(event.getChargerId()); -// presentVehicles.remove(event.getVehicleId()); -// vehiclesEnergyPreviousTimeStep.remove(event.getVehicleId()); -// vehiclesAtCharger.put(event.getChargerId(), presentVehicles); - double chargingTimeIn_h = (event.getTime() - chargingStartTime.get(event.getVehicleId()))/3600.0; - double averagePowerIn_kW = (EvUnits.J_to_kWh(event.getCharge())-chargingStartEnergy.get(event.getVehicleId()))/chargingTimeIn_h; - increment(averagePowerIn_kW,event.getChargerId(),chargingStartTime.get(event.getVehicleId()),event.getTime()); + double chargingTimeIn_h = (event.getTime() - chargingStartTime.get(event.getVehicleId())) / 3600.0; + double averagePowerIn_kW = (EvUnits.J_to_kWh(event.getCharge()) - chargingStartEnergy.get(event.getVehicleId())) / chargingTimeIn_h; + increment(averagePowerIn_kW, event.getChargerId(), chargingStartTime.get(event.getVehicleId()), event.getTime()); } - private void increment(double averagePower,Id chargerId, double beginTime, double endTime){ + private void increment(double averagePower, Id chargerId, double beginTime, double endTime) { if (beginTime == endTime && beginTime >= qsimEndTime) { return; } @@ -101,9 +62,8 @@ private void increment(double averagePower,Id chargerId, double beginTi int toIdx = timeDiscretizer.getIdx(endTime); for (int i = fromIdx; i < toIdx; i++) { - double[] chargingVector = chargerProfiles.get(chargerId); + double[] chargingVector = chargerProfiles.computeIfAbsent(chargerId, c -> new double[timeDiscretizer.getIntervalCount()]); chargingVector[i] += averagePower; - chargerProfiles.put(chargerId,chargingVector); } } From f3e2bdefd05009a4a301b1a3ea05a8c12b3176e2 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 2 Jun 2023 10:59:57 +0200 Subject: [PATCH 050/258] update matsim snapshot, moved link blocking and release into resource manager --- .../RailsimLinkStateControlerListener.java | 2 +- .../RailsimTrainStateControlerListener.java | 2 +- .../contrib/railsim/qsimengine/RailLink.java | 20 +++++------ .../qsimengine/RailResourceManager.java | 34 ++++++++++++++++++- .../railsim/qsimengine/RailsimCalc.java | 5 +-- .../qsimengine/RailsimDriverAgentFactory.java | 2 +- .../railsim/qsimengine/RailsimEngine.java | 19 +---------- .../railsim/qsimengine/RailsimQSimEngine.java | 5 +-- .../diposition/SimpleDisposition.java | 3 +- .../railsim/qsimengine/RailsimEngineTest.java | 2 +- 10 files changed, 56 insertions(+), 38 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java index 8a410719cb3..4e16b70b114 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java @@ -20,6 +20,7 @@ package ch.sbb.matsim.contrib.railsim.analysis.linkstates; import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; +import com.google.inject.Inject; import org.matsim.api.core.v01.Scenario; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.controler.OutputDirectoryHierarchy; @@ -28,7 +29,6 @@ import org.matsim.core.controler.listener.IterationEndsListener; import org.matsim.core.controler.listener.IterationStartsListener; -import javax.inject.Inject; public final class RailsimLinkStateControlerListener implements IterationEndsListener, IterationStartsListener { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java index 505a478e9d7..9804242a1fc 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java @@ -20,6 +20,7 @@ package ch.sbb.matsim.contrib.railsim.analysis.trainstates; import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; +import com.google.inject.Inject; import org.matsim.api.core.v01.Scenario; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.controler.OutputDirectoryHierarchy; @@ -28,7 +29,6 @@ import org.matsim.core.controler.listener.IterationEndsListener; import org.matsim.core.controler.listener.IterationStartsListener; -import javax.inject.Inject; public final class RailsimTrainStateControlerListener implements IterationEndsListener, IterationStartsListener { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index 47695f3444a..3f59510ba4f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -78,22 +78,22 @@ public double getAllowedFreespeed(MobsimDriverAgent driver) { } /** - * Whether at least one track is free. + * Check if driver has already reserved this link. */ - public boolean hasFreeTrack() { - for (TrackState trackState : state) { - if (trackState == TrackState.FREE) + public boolean isBlockedBy(MobsimDriverAgent driver) { + for (MobsimDriverAgent reservation : reservations) { + if (reservation == driver) return true; } return false; } /** - * Check if driver has already reserved this link. + * Whether at least one track is free. */ - public boolean isBlockedBy(MobsimDriverAgent driver) { - for (MobsimDriverAgent reservation : reservations) { - if (reservation == driver) + boolean hasFreeTrack() { + for (TrackState trackState : state) { + if (trackState == TrackState.FREE) return true; } return false; @@ -102,7 +102,7 @@ public boolean isBlockedBy(MobsimDriverAgent driver) { /** * Block a track that was previously reserved. */ - public int blockTrack(MobsimDriverAgent driver) { + int blockTrack(MobsimDriverAgent driver) { for (int i = 0; i < state.length; i++) { if (state[i] == TrackState.FREE) { reservations[i] = driver; @@ -116,7 +116,7 @@ public int blockTrack(MobsimDriverAgent driver) { /** * Release a non-free track to be free again. */ - public int releaseTrack(MobsimDriverAgent driver) { + int releaseTrack(MobsimDriverAgent driver) { for (int i = 0; i < state.length; i++) { if (reservations[i] == driver) { state[i] = TrackState.FREE; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java index ab689d34f30..c080136fccb 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -1,10 +1,12 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; +import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimDriverAgent; import java.util.List; @@ -17,6 +19,7 @@ */ public final class RailResourceManager { + private final EventsManager eventsManager; /** * Rail links */ @@ -28,7 +31,8 @@ public final class RailResourceManager { /** * Construct resources from network. */ - public RailResourceManager(RailsimConfigGroup config, Network network) { + public RailResourceManager(EventsManager eventsManager, RailsimConfigGroup config, Network network) { + this.eventsManager = eventsManager; this.links = new IdMap<>(Link.class, network.getLinks().size()); Set modes = config.getRailNetworkModes(); @@ -94,4 +98,32 @@ public boolean tryReleaseResource(RailResource resource, MobsimDriverAgent drive return false; } + + /** + * Try to block a track and return whether it was successful. + */ + public boolean tryBlockTrack(double time, MobsimDriverAgent driver, RailLink link) { + + if (link.isBlockedBy(driver)) + return true; + + if (link.hasFreeTrack()) { + int track = link.blockTrack(driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(Math.ceil(time), link.getLinkId(), + driver.getVehicle().getId(), TrackState.BLOCKED, track)); + + return true; + } + return false; + } + + /** + * Release a non-free track to be free again. + */ + public void releaseTrack(double time, MobsimDriverAgent driver, RailLink link) { + int track = link.releaseTrack(driver); + eventsManager.processEvent(new RailsimLinkStateChangeEvent(Math.ceil(time), link.getLinkId(), driver.getVehicle().getId(), + TrackState.FREE, track)); + } + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 01400f94dd9..181693c9074 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -186,7 +186,7 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) public static double nextLinkReservation(TrainState state, RailLink currentLink) { // time needed for full stop - double assumedSpeed = state.train.maxVelocity(); + double assumedSpeed = state.allowedMaxSpeed; double stopTime = assumedSpeed / state.train.deceleration(); assert stopTime > 0 : "Stop time can not be negative"; @@ -221,7 +221,8 @@ public static List calcLinksToBlock(int idx, TrainState state) { List result = new ArrayList<>(); - double assumedSpeed = state.train.maxVelocity(); + // safety distance + double assumedSpeed = state.allowedMaxSpeed; double stopTime = assumedSpeed / state.train.deceleration(); // safety distance diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java index 8edb169f632..4d29e93ec1f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java @@ -1,6 +1,7 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import com.google.inject.Inject; import org.matsim.api.core.v01.TransportMode; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; @@ -11,7 +12,6 @@ import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; import org.matsim.pt.Umlauf; -import javax.inject.Inject; import java.util.Set; /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 070d19219cc..7f1d70cf7ad 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -212,6 +212,7 @@ private void updateDeparture(double time, UpdateEvent event) { TrainState state = event.state; state.timestamp = time; + state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); state.headPosition = resources.getLink(state.headLink).length; state.tailPosition = resources.getLink(state.headLink).length - state.train.length(); @@ -259,15 +260,6 @@ private boolean blockLinkTracks(double time, int idx, TrainState state) { List blocked = disposition.blockRailSegment(time, state.driver, links); - // Block the approved links - for (RailLink link : blocked) { - if (link.isBlockedBy(state.driver)) - continue; - - int track = link.blockTrack(state.driver); - createEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), - state.driver.getVehicle().getId(), TrackState.BLOCKED, track)); - } // Only continue successfully if all requested link have been blocked return links.size() == blocked.size(); @@ -300,11 +292,6 @@ private void enterLink(double time, UpdateEvent event) { // Free all reservations for (RailLink link : state.route) { if (link.isBlockedBy(state.driver)) { - - int track = link.releaseTrack(state.driver); - createEvent(new RailsimLinkStateChangeEvent(time, link.getLinkId(), state.driver.getVehicle().getId(), - TrackState.FREE, track)); - disposition.unblockRailLink(time, state.driver, link); } } @@ -368,10 +355,6 @@ private void leaveLink(double time, UpdateEvent event) { createEvent(new RailsimTrainLeavesLinkEvent(time, state.driver.getVehicle().getId(), state.tailLink)); // TODO: link should be released after headway time - int track = tailLink.releaseTrack(state.driver); - createEvent(new RailsimLinkStateChangeEvent(time, state.tailLink, state.driver.getVehicle().getId(), - TrackState.FREE, track)); - disposition.unblockRailLink(time, state.driver, resources.getLink(state.tailLink)); state.tailLink = nextTailLink.getLinkId(); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index 32f5a24c325..33488e1562d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -20,6 +20,7 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import com.google.inject.Inject; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.population.Leg; @@ -38,7 +39,6 @@ import org.matsim.core.mobsim.qsim.qnetsimengine.QLinkI; import org.matsim.core.population.routes.NetworkRoute; -import javax.inject.Inject; import java.util.Set; /** @@ -69,7 +69,8 @@ public void setInternalInterface(InternalInterface internalInterface) { @Override public void onPrepareSim() { - engine = new RailsimEngine(qsim.getEventsManager(), config, new RailResourceManager(config, qsim.getScenario().getNetwork())); + engine = new RailsimEngine(qsim.getEventsManager(), config, + new RailResourceManager(qsim.getEventsManager(), config, qsim.getScenario().getNetwork())); } @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java index bc2d670a6f3..81d15787ac9 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java @@ -52,7 +52,7 @@ public List blockRailSegment(double time, MobsimDriverAgent driver, Li } // Check if single link can be reserved - if (link.hasFreeTrack()) { + if (resources.tryBlockTrack(time, driver, link)) { blocked.add(link); } else break; @@ -63,6 +63,7 @@ public List blockRailSegment(double time, MobsimDriverAgent driver, Li @Override public void unblockRailLink(double time, MobsimDriverAgent driver, RailLink link) { + resources.releaseTrack(time, driver, link); // Release held resources if (link.getResourceId() != null) { diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index a38f2234a0a..2b2a87d9e69 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -37,7 +37,7 @@ private RailsimTestUtils.Holder getTestEngine(String network) { collector.clear(); return new RailsimTestUtils.Holder(new RailsimEngine( - eventsManager, config, new RailResourceManager(config, net)), net); + eventsManager, config, new RailResourceManager(eventsManager, config, net)), net); } @Test From 724628c6c18bfc96e9d2a23a8f1ed892fd339dfb Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 2 Jun 2023 11:32:20 +0200 Subject: [PATCH 051/258] fix small error from last commit --- .../railsim/qsimengine/diposition/SimpleDisposition.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java index 81d15787ac9..333614a4e4d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java @@ -43,6 +43,10 @@ public List blockRailSegment(double time, MobsimDriverAgent driver, Li RailResource resource = resources.getResource(resourceId); if (resources.tryBlockResource(resource, driver)) { + + boolean b = resources.tryBlockTrack(time, driver, link); + assert b : "Link blocked by resource must be free"; + blocked.add(link); continue; } From 4c508673d0e28d4682f88e66f4a5becf94e10f9f Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 2 Jun 2023 15:57:16 +0200 Subject: [PATCH 052/258] added third integration tests, all tests now producing reasonable results --- contribs/railsim/pom.xml | 8 ++++ .../railsim/qsimengine/RailsimCalc.java | 13 ++--- .../railsim/qsimengine/RailsimEngine.java | 5 +- .../integration/RailsimIntegrationTest.java | 45 ++++++++++++++++++ .../railsim/qsimengine/network_genf.xml.gz | Bin 159748 -> 160331 bytes 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/contribs/railsim/pom.xml b/contribs/railsim/pom.xml index 3f184d39bb0..24bad934ca2 100644 --- a/contribs/railsim/pom.xml +++ b/contribs/railsim/pom.xml @@ -24,6 +24,14 @@ compile + + + org.matsim.contrib + vsp + 16.0-SNAPSHOT + test + + org.assertj assertj-core diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 181693c9074..5217b42d098 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -110,10 +110,6 @@ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double dece static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) { TrainState state = event.state; - - if (state.speed == 0) - return Double.POSITIVE_INFINITY; - double assumedSpeed = state.speed; double maxSpeed = Math.max(assumedSpeed, state.allowedMaxSpeed); @@ -148,7 +144,8 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) allowed = link.getAllowedFreespeed(state.driver); } - if (allowed < assumedSpeed) { + // Special case when accelerating from 0 and reaching 0 again + if (allowed < assumedSpeed || (allowed == 0 && allowed == assumedSpeed)) { SpeedTarget target = calcTargetSpeed(dist, state.acceleration, state.train.deceleration(), state.speed, state.targetSpeed, allowed); @@ -170,8 +167,12 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) } state.targetSpeed = targetSpeed; - event.newSpeed = speed; + + // No update required + if (FuzzyUtils.equals(event.newSpeed, state.targetSpeed)) + return Double.POSITIVE_INFINITY; + return decelDist; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 7f1d70cf7ad..ae6889665d9 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -340,7 +340,6 @@ private void leaveLink(double time, UpdateEvent event) { TrainState state = event.state; - RailLink tailLink = resources.getLink(state.tailLink); RailLink nextTailLink = null; // Find the next link in the route for (int i = state.routeIdx; i >= 1; i--) { @@ -448,6 +447,10 @@ private void decideNextUpdate(UpdateEvent event) { // if (state.routeIdx >= 877 && state.routeIdx <= 880 && state.timestamp >= 7300) // log.info("debug"); +// if (state.driver.getId().toString().equals("pt_Expresszug_BE_GE_train_0_train_Expresszug_BE_GE") && state.timestamp > 7000) +// log.info("debug"); + + // (1) max speed reached double accelDist = Double.POSITIVE_INFINITY; if (state.acceleration > 0 && FuzzyUtils.greaterThan(state.targetSpeed, state.speed)) { diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index fa73b4d2027..34b86574cc3 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -5,13 +5,21 @@ import org.junit.Rule; import org.junit.Test; import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.network.Link; +import org.matsim.contrib.vsp.scenario.SnzActivities; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.Controler; import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.io.IOUtils; +import org.matsim.examples.ExamplesUtils; import org.matsim.testcases.MatsimTestUtils; +import org.matsim.vehicles.VehicleType; import java.io.File; +import java.net.URL; +import java.util.Set; public class RailsimIntegrationTest { @@ -56,4 +64,41 @@ public void scenario_genf() { } + @Test + public void scenario_kelheim() { + + URL base = ExamplesUtils.getTestScenarioURL("kelheim"); + + Config config = ConfigUtils.loadConfig(IOUtils.extendUrl(base, "config.xml")); + config.controler().setLastIteration(1); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + + Scenario scenario = ScenarioUtils.loadScenario(config); + + // Convert all pt to rail + for (Link link : scenario.getNetwork().getLinks().values()) { + if (link.getAllowedModes().contains(TransportMode.car)) + link.setAllowedModes(Set.of(TransportMode.car, "ride", "freight")); + + if (link.getAllowedModes().contains(TransportMode.pt)) { + link.setFreespeed(50); + link.setAllowedModes(Set.of("rail")); + } + } + + // Maximum velocity must be configured + for (VehicleType type : scenario.getTransitVehicles().getVehicleTypes().values()) { + type.setMaximumVelocity(30); + type.setLength(100); + } + + SnzActivities.addScoringParams(config); + + Controler controler = new Controler(scenario); + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); + + controler.run(); + } + } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz index 22bff2fe04fd3fea4f9b7b5530299185c0586463..62d71612cfee807b4c1f2071d84a8eb764f42df5 100644 GIT binary patch delta 76381 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 delta 75793 zcmV)-K!?A}ByJ-tUV<2yn|YNHi5 z(q?Ae1~zZEFoOo#05nC-uOEN&oQlZ0_jRw6rzo?bl~W6$fqNsX-iq|_$HPC6WH$-| zHRGwN&1vSE%lCoDf@846GOJQ>|A#hH;>U~A`BX>vG?UH6h$E+XAVF)7>n0H6KIPjY z`SyRg**0nBn#-eP&D_w+uZ^-cdqu%nL#j-9uJ1`|%PwIW8ZK?oAnvp?T0oEuIpJHt)^m@uN$A5w_qF&TpL5&>TTcAyrDjM z?LCvtabAd4?-pZ4AkjNvyIG9qaxZ^ow)v4c@^wllniPV2-ZMI%`c`8ZrxuRX8?^oo zEB&k&&Sqx~B@g0a4`hNrhgf47Cv@S=mX8v4A9 zr+Sj4nQPuKAZmjvi%AG;V<1^@%YDaSj{aO!W@ei=-1f+!f$XoA;_6- z-q8JUdp8(yZ4AMq?Pf5Zij;rMfy2F{r9C{cbh=5+eV`3rL>tKblq21_&hN_B|E+$us2~>vy!J!4RO7)_YYjo(Opo7_<1?_nJ1a*5iM=AYq8|e%kD~ z-s&ACy`w$VvM|r2b9t5!j$mqzVXtF-FL|?Y@!3!?`c(8H@5>A}^g0pC-Vs!q=ZKtP z=xtSyyIP*g^T_)$gI%l;GFEe~`{_{)&8?7Y64O&vj(J~ZP+nMc%*vS0vyvd?Bt`~J zm2aEklM*gFII1{KVv2u|P(KT#pdh+ig(BzMbNyEGN;_|R%G*~qqoJ4ZHciG;-)bhE z2TX+=33IPbv66xbL1Kpoim%gCY5RF4og?de!l_1R zH%t{1{8-sPJtp+GLUJT#AU^e|X`RqVRZJcd7UD`+?HD-{LrQ-zB~O|6m2{4Tjt$oZ zNKK9FN#ah+Db~ej%1;%)=9P7hiXjYbLW|m>iLZZo<(-Fl z?I}6rJc)K6|*iY;y&|qL@U)T1-u1qJeBB?SJt_BxX6_P zmK<}J-XDc@JJ}L(MC9r`^{8o^dJN*qhymKes4I^YJ>#~caZ8_sR_|DW3XTtG#cGv=;eH~3IdT^6Ea6^=&7J_UYX~ZL%yL*vxtZ*bsrN{ zwvIs{;Qgn5td)6=PHvl=RUl@vH>&95X|w+sni=Bm4h@`odTK;; zUa5cQvL(hQ?{GX?9Lz_FYqkjv!Pkk;1%X!Pxi~P#h>sz!^h~1y(V@{*i81a^HD1Xp z@qEa-+s$Ca`J{N{f4mutr!qZehB1Z;%b5#A$fvto`F^-M$w&o%t|wVudFLY>JtZsV zDz5L80{UrHdDW1L`_z-BWf~Cym|aA|aI}B4o;A?sDipM9dM+-rGS7#cRrvW4t0;Bt zij3k5vN9^B_cXrhEy@a~9y36T?)}7C7jb)o!7Iy}jVZy3;*};yaojBmcwddj` zEBRbb!s8PEFd6IZ!btR+#duD_m3!Wf)NlEo?Ue0N0uuaCC)=W7;#<%XUZ=<4a zkLu`kg&N}+pPB-fXYx4=uAJ|q_VE@ldw�iv^U{3o<_!SD4x7a_H6_c}z%AZEFu= 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

2_kb?{|3s6a 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

-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 From dc0723082c3cc486396a0e1dcf707dcbfd7c6028 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 7 Jun 2023 12:30:12 +0200 Subject: [PATCH 053/258] fix typo in package name --- .../sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java | 5 ++--- .../{diposition => disposition}/SimpleDisposition.java | 2 +- .../{diposition => disposition}/TrainDisposition.java | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) rename contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/{diposition => disposition}/SimpleDisposition.java (96%) rename contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/{diposition => disposition}/TrainDisposition.java (93%) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index ae6889665d9..19ce254e60b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -1,10 +1,9 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; -import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; import ch.sbb.matsim.contrib.railsim.events.RailsimTrainLeavesLinkEvent; -import ch.sbb.matsim.contrib.railsim.qsimengine.diposition.SimpleDisposition; -import ch.sbb.matsim.contrib.railsim.qsimengine.diposition.TrainDisposition; +import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.SimpleDisposition; +import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.TrainDisposition; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java similarity index 96% rename from contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java rename to contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java index 333614a4e4d..6ee4d37fee1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/SimpleDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java @@ -1,4 +1,4 @@ -package ch.sbb.matsim.contrib.railsim.qsimengine.diposition; +package ch.sbb.matsim.contrib.railsim.qsimengine.disposition; import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; import ch.sbb.matsim.contrib.railsim.qsimengine.RailResource; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/TrainDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java similarity index 93% rename from contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/TrainDisposition.java rename to contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java index c10a71b8eb5..ea804ae83f4 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/diposition/TrainDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java @@ -1,4 +1,4 @@ -package ch.sbb.matsim.contrib.railsim.qsimengine.diposition; +package ch.sbb.matsim.contrib.railsim.qsimengine.disposition; import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; import org.matsim.core.mobsim.framework.MobsimDriverAgent; From 9faad615ad0fc463c140b064242ace7fe9875c62 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 7 Jun 2023 17:14:05 +0200 Subject: [PATCH 054/258] blocking and releasing links now handles resources as well, fix target speed when leaving links --- .../railsim/qsimengine/RailResource.java | 4 +- .../qsimengine/RailResourceManager.java | 24 ++++++- .../railsim/qsimengine/RailsimCalc.java | 21 +++++- .../railsim/qsimengine/RailsimEngine.java | 66 ++++++++++++++----- .../disposition/SimpleDisposition.java | 29 +------- .../railsim/qsimengine/RailsimCalcTest.java | 10 +++ 6 files changed, 104 insertions(+), 50 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java index d10602ebf1f..03e52c839fd 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java @@ -13,7 +13,7 @@ public class RailResource { /** - * Links beloning to this resource. + * Links belonging to this resource. */ final List links; @@ -30,6 +30,8 @@ public class RailResource { public RailResource(List links) { capacity = links.stream().mapToInt(RailLink::getNumberOfTracks).min().orElseThrow(); this.links = links; + + // TODO: this is not necessarily needed and can be computed implicitly reservations = new HashSet<>(); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java index c080136fccb..7318a32e775 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -71,7 +71,7 @@ public RailResource getResource(Id id) { * * @return true if the resource is now blocked or was blocked for this driver already. */ - public boolean tryBlockResource(RailResource resource, MobsimDriverAgent driver) { + private boolean tryBlockResource(RailResource resource, MobsimDriverAgent driver) { if (resource.reservations.contains(driver)) return true; @@ -89,7 +89,7 @@ public boolean tryBlockResource(RailResource resource, MobsimDriverAgent driver) * * @return whether driver is still blocking this resource. */ - public boolean tryReleaseResource(RailResource resource, MobsimDriverAgent driver) { + private boolean tryReleaseResource(RailResource resource, MobsimDriverAgent driver) { if (resource.links.stream().noneMatch(l -> l.isBlockedBy(driver))) { resource.reservations.remove(driver); @@ -100,13 +100,24 @@ public boolean tryReleaseResource(RailResource resource, MobsimDriverAgent drive } /** - * Try to block a track and return whether it was successful. + * Try to block a track and the underlying resource and return whether it was successful. */ public boolean tryBlockTrack(double time, MobsimDriverAgent driver, RailLink link) { if (link.isBlockedBy(driver)) return true; + Id resourceId = link.getResourceId(); + if (resourceId != null) { + + RailResource resource = getResource(resourceId); + + // resource is required + if (!tryBlockResource(resource, driver)) { + return false; + } + } + if (link.hasFreeTrack()) { int track = link.blockTrack(driver); eventsManager.processEvent(new RailsimLinkStateChangeEvent(Math.ceil(time), link.getLinkId(), @@ -114,6 +125,7 @@ public boolean tryBlockTrack(double time, MobsimDriverAgent driver, RailLink lin return true; } + return false; } @@ -124,6 +136,12 @@ public void releaseTrack(double time, MobsimDriverAgent driver, RailLink link) { int track = link.releaseTrack(driver); eventsManager.processEvent(new RailsimLinkStateChangeEvent(Math.ceil(time), link.getLinkId(), driver.getVehicle().getId(), TrackState.FREE, track)); + + // Release held resources + if (link.getResourceId() != null) { + tryReleaseResource(getResource(link.getResourceId()), driver); + } + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 5217b42d098..fc4f4056cc5 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -19,7 +19,7 @@ static double calcTraveledDist(double speed, double elapsedTime, double accelera } /** - * Inverse of {@link #calcTraveledDist(double, double, double)}, solves for distance. + * Inverse of {@link #calcTraveledDist(double, double, double)}, solves for distance, returns needed time. */ static double solveTraveledDist(double speed, double dist, double acceleration) { if (acceleration == 0) @@ -215,6 +215,25 @@ public static double nextLinkReservation(TrainState state, RailLink currentLink) return Double.POSITIVE_INFINITY; } + /** + * Calculate the deceleration needed to come to halt exactly after {@code dist}. + * + * @return negative acceleration, always a negative number. + */ + static double calcTargetDecel(double dist, double currentSpeed) { + return -currentSpeed * currentSpeed / (2 * dist); + } + + /** + * Calculate the maximum speed that can be achieved if trains want to stop after dist. + */ + static double calcTargetSpeedForStop(double dist, TrainState state) { + + // TODO + + return 0; + } + /** * Links that need to be blocked or otherwise stop needs to be initiated. */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 19ce254e60b..350a063e2c2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -165,11 +165,30 @@ private void blockTrack(double time, UpdateEvent event) { if (!blockLinkTracks(time, state.routeIdx, state)) { - // TODO: calculate speed to arrival at the last blocked link + // Calculate the acceleration needed to stop + if (state.acceleration >= 0) { + // unoccupied space ahead + double dist = resources.getLink(state.headLink).length - state.headPosition; + for (int i = state.routeIdx; i < state.route.size(); i++) { + RailLink link = state.route.get(i); + if (link.isBlockedBy(state.driver)) + dist += link.length; + } - // Break when reservation is not possible - state.targetSpeed = 0; - state.acceleration = -state.train.deceleration(); + assert FuzzyUtils.greaterEqualThan(dist, 1): "Small head room needed to stop before end of link"; + + // Trains try to stop shortly before the next link + // Removing this would lead to train trying to enter the next link already + + // Break when reservation is not possible + state.targetSpeed = 0; + state.acceleration = -state.train.deceleration(); + + // TODO: use other method +// state.acceleration = RailsimCalc.calcTargetDecel(dist - 1, state.speed); + assert state.acceleration <= 0 && + FuzzyUtils.lessEqualThan(Math.abs(state.acceleration), state.train.deceleration()): "Train deceleration must be within specification, but was " + state.acceleration; + } event.checkReservation = time + POLL_INTERVAL; decideNextUpdate(event); @@ -187,9 +206,8 @@ private void checkTrackReservation(double time, UpdateEvent event) { if (blockLinkTracks(time, state.routeIdx, state)) { updatePosition(time, event); - // TODO: maximum speed could be lower - // see enterLink as well + // target speed could be lower, but this should be updated calc decel distance later state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); state.targetSpeed = state.allowedMaxSpeed; state.acceleration = state.train.acceleration(); @@ -259,7 +277,6 @@ private boolean blockLinkTracks(double time, int idx, TrainState state) { List blocked = disposition.blockRailSegment(time, state.driver, links); - // Only continue successfully if all requested link have been blocked return links.size() == blocked.size(); } @@ -318,17 +335,7 @@ private void enterLink(double time, UpdateEvent event) { assert link.isBlockedBy(state.driver) : "Link has to be blocked by driver when entered"; - // TODO: this probably needs to be a separate function to calculate possible target speed more accurately - if (RailsimCalc.calcDecelDistanceAndSpeed(link, event) == Double.POSITIVE_INFINITY && - !event.isAwaitingReservation()) { - - state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); - - if (state.allowedMaxSpeed > state.targetSpeed) { - state.targetSpeed = state.allowedMaxSpeed; - state.acceleration = state.train.acceleration(); - } - } + updateTargetSpeed(event, state, link); createEvent(state.asEvent(time)); @@ -358,6 +365,8 @@ private void leaveLink(double time, UpdateEvent event) { state.tailLink = nextTailLink.getLinkId(); state.tailPosition = 0; + updateTargetSpeed(event, state, resources.getLink(state.headLink)); + decideNextUpdate(event); } @@ -435,6 +444,9 @@ private double handleTransitStop(double time, TrainState state) { return stopTime; } + // TODO: increase speed when leaving a link + + /** * Decide which update is the earliest and needs to be the next. */ @@ -515,6 +527,24 @@ private void decideNextUpdate(UpdateEvent event) { assert Double.isFinite(event.plannedTime) : "Planned update time must be finite, but was " + event.plannedTime; } + /** + * Calculate the possible target speed. This can be lower than allowed if links in front are blocked. + */ + private void updateTargetSpeed(UpdateEvent event, TrainState state, RailLink headLink) { + + // TODO: this probably needs to be a separate function to calculate possible target speed more accurately + if (RailsimCalc.calcDecelDistanceAndSpeed(headLink, event) == Double.POSITIVE_INFINITY && + !event.isAwaitingReservation()) { + + state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); + + if (state.allowedMaxSpeed > state.targetSpeed) { + state.targetSpeed = state.allowedMaxSpeed; + state.acceleration = state.train.acceleration(); + } + } + + } /** * Allowed speed for the train. diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java index 6ee4d37fee1..3738196d986 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java @@ -33,28 +33,6 @@ public List blockRailSegment(double time, MobsimDriverAgent driver, Li // Iterate all links that need to be blocked for (RailLink link : segment) { - if (link.isBlockedBy(driver)) { - blocked.add(link); - continue; - } - - Id resourceId = link.getResourceId(); - if (resourceId != null) { - - RailResource resource = resources.getResource(resourceId); - if (resources.tryBlockResource(resource, driver)) { - - boolean b = resources.tryBlockTrack(time, driver, link); - assert b : "Link blocked by resource must be free"; - - blocked.add(link); - continue; - } - - // Could not reserve resource - break; - } - // Check if single link can be reserved if (resources.tryBlockTrack(time, driver, link)) { blocked.add(link); @@ -67,11 +45,8 @@ public List blockRailSegment(double time, MobsimDriverAgent driver, Li @Override public void unblockRailLink(double time, MobsimDriverAgent driver, RailLink link) { - resources.releaseTrack(time, driver, link); - // Release held resources - if (link.getResourceId() != null) { - resources.tryReleaseResource(resources.getResource(link.getResourceId()), driver); - } + // put resource handling into release track + resources.releaseTrack(time, driver, link); } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index ac420a46327..e438b164ea1 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -67,4 +67,14 @@ public void maxSpeed() { .isCloseTo(dist, Offset.offset(0.001)); } + + @Test + public void decel() { + + double d = RailsimCalc.calcTargetDecel(1000, 10); + + assertThat(RailsimCalc.calcTraveledDist(10, -10 / d, d)) + .isCloseTo(1000, Offset.offset(0.001)); + + } } From a651c834f5f67c0967ceb78a8229cf9362d07d95 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Thu, 8 Jun 2023 12:02:39 +0200 Subject: [PATCH 055/258] additional tests for IntegrationTest --- .../integration/RailsimIntegrationTest.java | 248 +++++++++- .../0_varyingCapacities/config.xml | 40 ++ .../0_varyingCapacities/trainNetwork.xml | 57 +++ .../0_varyingCapacities/transitSchedule.xml | 45 ++ .../0_varyingCapacities/transitVehicles.xml | 36 ++ .../railsim/integration/10_cross/config.xml | 38 ++ .../integration/10_cross/trainNetwork.xml | 84 ++++ .../integration/10_cross/transitSchedule.xml | 52 +++ .../integration/10_cross/transitVehicles.xml | 29 ++ .../integration/11_mesoStation/config.xml | 39 ++ .../11_mesoStation/trainNetwork.xml | 79 ++++ .../11_mesoStation/transitSchedule.xml | 76 ++++ .../11_mesoStation/transitVehicles.xml | 33 ++ .../integration/12_mesoStation2/config.xml | 39 ++ .../12_mesoStation2/trainNetwork.xml | 79 ++++ .../12_mesoStation2/transitSchedule.xml | 76 ++++ .../12_mesoStation2/transitVehicles.xml | 33 ++ .../railsim/integration/13_Y/config.xml | 39 ++ .../railsim/integration/13_Y/trainNetwork.xml | 104 +++++ .../integration/13_Y/transitSchedule.xml | 56 +++ .../integration/13_Y/transitVehicles.xml | 48 ++ .../integration/14_mesoStations/config.xml | 39 ++ .../14_mesoStations/trainNetwork.xml | 85 ++++ .../14_mesoStations/transitSchedule.xml | 46 ++ .../14_mesoStations/transitVehicles.xml | 28 ++ .../integration/1_oppositeTraffic/config.xml | 39 ++ .../1_oppositeTraffic/trainNetwork.xml | 73 +++ .../1_oppositeTraffic/transitSchedule.xml | 58 +++ .../1_oppositeTraffic/transitVehicles.xml | 36 ++ .../2_multipleOppositeTraffic/config.xml | 39 ++ .../trainNetwork.xml | 85 ++++ .../transitSchedule.xml | 64 +++ .../transitVehicles.xml | 54 +++ .../integration/3_twoSources/config.xml | 39 ++ .../integration/3_twoSources/trainNetwork.xml | 123 +++++ .../3_twoSources/transitSchedule.xml | 67 +++ .../3_twoSources/transitVehicles.xml | 36 ++ .../integration/4_genf_bern/config.xml | 38 ++ .../4_genf_bern/trainNetwork.xml.gz | Bin 0 -> 160314 bytes .../4_genf_bern/transitSchedule.xml.gz | Bin 0 -> 70575 bytes .../4_genf_bern/transitVehicles.xml.gz | Bin 0 -> 1366 bytes .../5_complexTwoSources/config.xml | 39 ++ .../5_complexTwoSources/trainNetwork.xml | 96 ++++ .../5_complexTwoSources/transitSchedule.xml | 62 +++ .../5_complexTwoSources/transitVehicles.xml | 36 ++ .../6_threeTracksMicroscopic/config.xml | 39 ++ .../6_threeTracksMicroscopic/trainNetwork.xml | 196 ++++++++ .../transitSchedule.xml | 65 +++ .../transitVehicles.xml | 28 ++ .../integration/7_trainFollowing/config.xml | 39 ++ .../7_trainFollowing/trainNetwork.xml | 178 ++++++++ .../7_trainFollowing/transitSchedule.xml | 47 ++ .../7_trainFollowing/transitVehicles.xml | 50 +++ .../integration/8_microStation/config.xml | 39 ++ .../8_microStation/trainNetwork.xml | 423 ++++++++++++++++++ .../8_microStation/transitSchedule.xml | 105 +++++ .../8_microStation/transitVehicles.xml | 29 ++ .../integration/9_microStation2/config.xml | 39 ++ .../9_microStation2/trainNetwork.xml | 423 ++++++++++++++++++ .../9_microStation2/transitSchedule.xml | 104 +++++ .../9_microStation2/transitVehicles.xml | 29 ++ .../railsim/integration/test_genf/config.xml | 2 +- 62 files changed, 4345 insertions(+), 2 deletions(-) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/trainNetwork.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitSchedule.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitVehicles.xml.gz create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 34b86574cc3..0d27a524b36 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -1,25 +1,34 @@ package ch.sbb.matsim.contrib.railsim.integration; import ch.sbb.matsim.contrib.railsim.RailsimModule; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; +import org.junit.Assert; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.vsp.scenario.SnzActivities; +import org.matsim.core.api.experimental.events.VehicleArrivesAtFacilityEvent; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; +import org.matsim.core.controler.AbstractModule; import org.matsim.core.controler.Controler; import org.matsim.core.scenario.ScenarioUtils; import org.matsim.core.utils.io.IOUtils; +import org.matsim.core.utils.misc.Time; import org.matsim.examples.ExamplesUtils; import org.matsim.testcases.MatsimTestUtils; +import org.matsim.testcases.utils.EventsCollector; import org.matsim.vehicles.VehicleType; import java.io.File; import java.net.URL; +import java.util.List; import java.util.Set; +import java.util.stream.Stream; public class RailsimIntegrationTest { @@ -53,6 +62,9 @@ public void scenario_genf() { Config config = ConfigUtils.loadConfig(new File(dir, "config.xml").toString()); config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setLastIteration(0); + config.controler().setCreateGraphs(false); + config.controler().setDumpDataAtEnd(false); Scenario scenario = ScenarioUtils.loadScenario(config); Controler controler = new Controler(scenario); @@ -70,8 +82,10 @@ public void scenario_kelheim() { URL base = ExamplesUtils.getTestScenarioURL("kelheim"); Config config = ConfigUtils.loadConfig(IOUtils.extendUrl(base, "config.xml")); - config.controler().setLastIteration(1); + config.controler().setLastIteration(0); config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setCreateGraphs(false); + config.controler().setDumpDataAtEnd(false); Scenario scenario = ScenarioUtils.loadScenario(config); @@ -101,4 +115,236 @@ public void scenario_kelheim() { controler.run(); } + private EventsCollector runSimulation(File scenarioDir) { + Config config = ConfigUtils.loadConfig(new File(scenarioDir, "config.xml").toString()); + + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setDumpDataAtEnd(false); + config.controler().setCreateGraphs(false); + config.controler().setLastIteration(0); + + Scenario scenario = ScenarioUtils.loadScenario(config); + Controler controler = new Controler(scenario); + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); + + EventsCollector collector = new EventsCollector(); + controler.addOverridingModule(new AbstractModule() { + @Override + public void install() { + this.addEventHandlerBinding().toInstance(collector); + } + }); + + controler.run(); + + return collector; + } + + private double timeToAccelerate(double v0, double v, double a) { + return (v - v0) / a; + } + + private double distanceTravelled(double v0, double a, double t) { + return 0.5 * a * t * t + v0 * t; + } + + private double timeForDistance(double d, double v) { + return d / v; + } + + private void assertTrainState(double time, double speed, double targetSpeed, double acceleration, double headPosition, RailsimTrainStateEvent event) { + Assert.assertEquals(Math.ceil(time), event.getTime(), 1e-7); + Assert.assertEquals(speed, event.getSpeed(), 1e-5); + Assert.assertEquals(targetSpeed, event.getTargetSpeed(), 1e-7); + Assert.assertEquals(acceleration, event.getAcceleration(), 1e-5); + Assert.assertEquals(headPosition, event.getHeadPosition(), 1e-5); + } + + @Test + public void test0_varyingCapacities() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "0_varyingCapacities")); + + // print events of train1 for debugging + List train1events = collector.getEvents().stream().filter(event -> event instanceof RailsimTrainStateEvent).map(event -> (RailsimTrainStateEvent) event).filter(event -> event.getVehicleId().toString().equals("train1")).toList(); + train1events.forEach(System.out::println); + + // calculation of expected time for train1: acceleration = 0.5, length = 100 + // route: - station: 2.77777m/s + // - link: 50000m, 13.8889m/s + // - station: 200m, 2.777777m/s + // - link: 50000m, 13.8889m/s + // - station: 200m, 2.777777m/s + + double departureTime = 8 * 3600; + double trainLength = 100; + double acceleration = 0.5; + double stationSpeed = 2.7777777777777777; + double stationLength = 200; + double linkSpeed = 13.8889; + double linkLength = 50000; + + double currentTime = departureTime; + assertTrainState(currentTime, 0, 0, 0, stationLength, train1events.get(0)); + + // train starts in the station, accelerates to station speed and continues until the train has left the station link + assertTrainState(currentTime, 0, stationSpeed, acceleration, 0, train1events.get(1)); + + double accTime1 = timeToAccelerate(0, stationSpeed, acceleration); + double accDistance1 = distanceTravelled(0, acceleration, accTime1); + currentTime += accTime1; + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, accDistance1, train1events.get(2)); + + double cruiseTime2 = timeForDistance(trainLength - accDistance1, stationSpeed); + double cruiseDistance2 = distanceTravelled(stationSpeed, 0, cruiseTime2); // should be = trainLength - accDistance1 + currentTime += cruiseTime2; + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, accDistance1 + cruiseDistance2, train1events.get(3)); + + // train further accelerates to link speed + double accTime3 = timeToAccelerate(stationSpeed, linkSpeed, acceleration); + double accDistance3 = distanceTravelled(stationSpeed, acceleration, accTime3); + currentTime += accTime3; + assertTrainState(currentTime, linkSpeed, linkSpeed, 0, accDistance1 + cruiseDistance2 + accDistance3, train1events.get(4)); + + + // train can cruise with link speed until it needs to decelerate for next station + + double decTime5 = timeToAccelerate(linkSpeed, stationSpeed, -acceleration); + double decDistance5 = distanceTravelled(linkSpeed, -acceleration, decTime5); + + double cruiseDistance4 = linkLength - accDistance1 - cruiseDistance2 - accDistance3 - decDistance5; + double cruiseTime4 = timeForDistance(cruiseDistance4, linkSpeed); + currentTime += cruiseTime4; + assertTrainState(currentTime, linkSpeed, linkSpeed, 0, linkLength - decDistance5, train1events.get(6)); + // start deceleration + assertTrainState(currentTime, linkSpeed, stationSpeed, -acceleration, linkLength - decDistance5, train1events.get(7)); + currentTime += decTime5; + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, linkLength, train1events.get(8)); + + // train passes station completely + double cruiseDistance6 = stationLength + trainLength; + double cruiseTime6 = timeForDistance(cruiseDistance6, stationSpeed); + currentTime += cruiseTime6; + + // TODO from here on forward, train-events are not yet correctly matched and the test will fail + + // train can accelerate again to link speed +// assertTrainState(currentTime, stationSpeed, linkSpeed, acceleration, trainLength, train1events.get(10)); // TODO + double accTime7 = timeToAccelerate(stationSpeed, linkSpeed, acceleration); + double accDistance7 = distanceTravelled(stationSpeed, accDistance1, accTime7); + currentTime += accTime7; +// assertTrainState(currentTime, linkSpeed, linkSpeed, 0, trainLength + accDistance7, train1events.get(11)); // TODO + + // train can cruise with link speed until it needs to decelerate for final station + double decTime9 = timeToAccelerate(linkSpeed, stationSpeed, -acceleration); + double decDistance9 = distanceTravelled(linkSpeed, -acceleration, decTime9); + + double cruiseDistance8 = linkLength - trainLength - accDistance7 - decDistance9; + double cruiseTime8 = timeForDistance(cruiseDistance8, linkSpeed); + + currentTime += cruiseTime8; +// assertTrainState(currentTime, linkSpeed, stationSpeed, -acceleration, trainLength + accDistance7, train1events.get(12)); // TODO + currentTime += decTime9; +// assertTrainState(currentTime, stationSpeed, stationSpeed, 0, linkLength, train1events.get(13)); // TODO + + // train can cruise into station link until it needs to fully brake + double decTime11 = timeToAccelerate(stationSpeed, 0, -acceleration); + double decDistance11 = distanceTravelled(stationSpeed, -acceleration, decTime11); + + double cruiseDistance10 = stationLength - decDistance11; + double cruiseTime10 = timeForDistance(cruiseDistance10, stationSpeed); + currentTime += cruiseTime10; +// assertTrainState(currentTime, stationSpeed, 0, -acceleration, stationLength - cruiseDistance10, train1events.get(14)); // TODO + // final train arrival + currentTime += decTime11; +// assertTrainState(currentTime, 0, 0, 0, stationLength, train1events.get(15)); // TODO + } + + @Test @Ignore(value="no assert statements yet") + public void test1_oppositeTraffic() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "1_oppositeTraffic")); + +// List arrivalEvents = collector.getEvents() +// .stream() +// .filter(event -> event instanceof VehicleArrivesAtFacilityEvent) +// .map(event -> (VehicleArrivesAtFacilityEvent) event) +// .filter(event -> event.getFacilityId().toString().equals("t3_A-B")) +// .toList(); +// VehicleArrivesAtFacilityEvent train0Arrival = arrivalEvents.stream() +// .filter(event -> event.getFacilityId().toString().equals("t3_A-B")) +// .filter(event -> event.getVehicleId().toString().equals("train1")) +// .findFirst().orElseThrow(); +// Assert.assertEquals("train1 should arrive at 10:00:00", 36000.0, train0Arrival.getTime(), 1e-7); // TODO fix times +// VehicleArrivesAtFacilityEvent train10Arrival = arrivalEvents.stream() +// .filter(event -> event.getFacilityId().toString().equals("t1_B-A")) +// .filter(event -> event.getVehicleId().toString().equals("train2")) +// .findFirst().orElseThrow(); +// Assert.assertEquals("train2 should arrive at 10:00:00", 36000.0, train0Arrival.getTime(), 1e-7); // TODO fix times + } + + @Test @Ignore(value="no assert statements yet") + public void test2_oppositeTraffic_multipleTrains_oneSlowTrain() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "2_multipleOppositeTraffic")); + } + + @Test @Ignore(value="no assert statements yet") + public void test3_twoSources() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "3_twoSources")); + } + + @Test @Ignore(value="no assert statements yet") + public void test4_genf_bern() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "4_genf_bern")); + } + + @Test @Ignore(value="no assert statements yet") + public void test5_complexTwoSources() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "5_complexTwoSources")); + } + + @Test @Ignore(value="no assert statements yet") + public void test6_threeTracksMicroscopic() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "6_threeTracksMicroscopic")); + } + + @Test @Ignore(value="no assert statements yet") + public void test7_trainFollowing() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "7_trainFollowing")); + } + + @Test @Ignore(value="no assert statements yet") + public void test8_microStation() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "8_microStation")); + } + + @Test @Ignore(value="no assert statements yet") + public void test9_microStation2() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "9_microStation2")); + } + + @Test @Ignore(value="no assert statements yet") + public void test10_cross() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "10_cross")); + } + + @Test @Ignore(value="no assert statements yet") + public void test11_mesoStation() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "11_mesoStation")); + } + + @Test @Ignore(value="no assert statements yet") + public void test12_mesoStation2() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "12_mesoStation2")); + } + + @Test @Ignore(value="no assert statements yet") + public void test13_Y() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "13_Y")); + } + + @Test @Ignore(value="no assert statements yet") + public void test14_mesoStations() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "14_mesoStations")); + } + } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/config.xml new file mode 100644 index 00000000000..922013d2df8 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/config.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/trainNetwork.xml new file mode 100644 index 00000000000..ff528739548 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/trainNetwork.xml @@ -0,0 +1,57 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + 2 + + + + + 999 + + + + + 5 + + + + + 999 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitSchedule.xml new file mode 100644 index 00000000000..be9b4aedcc4 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitSchedule.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitVehicles.xml new file mode 100644 index 00000000000..678eb942a96 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitVehicles.xml @@ -0,0 +1,36 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/config.xml new file mode 100644 index 00000000000..5b7aa89fbeb --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/config.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml new file mode 100644 index 00000000000..b535f2608d8 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml @@ -0,0 +1,84 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + 2 + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml new file mode 100644 index 00000000000..d1c6aa9a2fc --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml new file mode 100644 index 00000000000..c86eff16d18 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml @@ -0,0 +1,29 @@ + + + + + + + 5.0 + serial + 5.0 + 10.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml new file mode 100644 index 00000000000..e137ae8df83 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml @@ -0,0 +1,79 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitSchedule.xml new file mode 100644 index 00000000000..5f1443a7b3a --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitSchedule.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml new file mode 100644 index 00000000000..103d0251dad --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml @@ -0,0 +1,33 @@ + + + + + + + 5.0 + serial + 5.0 + 1.0 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml new file mode 100644 index 00000000000..48ffeea845c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml @@ -0,0 +1,79 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitSchedule.xml new file mode 100644 index 00000000000..5f1443a7b3a --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitSchedule.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml new file mode 100644 index 00000000000..103d0251dad --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml @@ -0,0 +1,33 @@ + + + + + + + 5.0 + serial + 5.0 + 1.0 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml new file mode 100644 index 00000000000..ac32e44354f --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml @@ -0,0 +1,104 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + + 1 + + + + + 1 + + + + + + 999 + + + + + + 1 + + + + + 1 + + + + + + 3 + + + + + + 1 + + + + + + 999 + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitSchedule.xml new file mode 100644 index 00000000000..fd97728b28d --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitSchedule.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitVehicles.xml new file mode 100644 index 00000000000..a2056088fd3 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitVehicles.xml @@ -0,0 +1,48 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml new file mode 100644 index 00000000000..26256d2f907 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + + 2 + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitSchedule.xml new file mode 100644 index 00000000000..17d4fe0d429 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitSchedule.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitVehicles.xml new file mode 100644 index 00000000000..de64a5a9958 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitVehicles.xml @@ -0,0 +1,28 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml new file mode 100644 index 00000000000..d0ee3e64435 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml @@ -0,0 +1,73 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + t2_A-t2_B + + + + + 1 + t2_A-t2_B + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitSchedule.xml new file mode 100644 index 00000000000..cc3851daffe --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitSchedule.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitVehicles.xml new file mode 100644 index 00000000000..f3f7106bd31 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitVehicles.xml @@ -0,0 +1,36 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml new file mode 100644 index 00000000000..0cf9845e575 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml @@ -0,0 +1,85 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 5 + + + + + + 1 + t2_A-t2_B + + + + + 1 + t2_A-t2_B + + + + + + 5 + + + + + 5 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitSchedule.xml new file mode 100644 index 00000000000..5d12af79465 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitSchedule.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitVehicles.xml new file mode 100644 index 00000000000..af93d6d5830 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitVehicles.xml @@ -0,0 +1,54 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml new file mode 100644 index 00000000000..15df575ca5b --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml @@ -0,0 +1,123 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + 999 + + + + + + 1 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + 999 + + + + + 999 + + + + + + 2 + + + + + 1 + + + + + + 999 + + + + + 999 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitSchedule.xml new file mode 100644 index 00000000000..92e5e3b25ae --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitSchedule.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitVehicles.xml new file mode 100644 index 00000000000..f9585b6060c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitVehicles.xml @@ -0,0 +1,36 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/config.xml new file mode 100644 index 00000000000..b67f48f3942 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/config.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/trainNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/trainNetwork.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..17ecdf4768f2230c02cc77b58b7c9d5d6a46ca6b GIT binary patch literal 160314 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�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 literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitSchedule.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitSchedule.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..5989dd29a2da50987ae36f330d55f9c789b06a11 GIT binary patch literal 70575 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 literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitVehicles.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..25ad72a9773194a1f7bddab3c9f2e74ec47735fd GIT binary patch literal 1366 zcmb2|=3sz;w{vp+Z+nQe+8WtG7dpIZW5%5%)s>QGW>C=PbCIYUO zdzF_P_0{bETqO9|`u3}+*ELD2T<6)W`}C|%vA4j;q}!+E zgYR%^3!OZ(%WL|5rOdchTs&6(aaFH9XQl1eO$mE}_=I=@xmwKLV{l0C@VFdwh!Iq@unhnJPBXC++B+j#S0X3oFwq)0W%p<5J&!SUPB1 zd-6uU$Eh{ii?`SmS~|>4+U@Rqd7JL<2VWO(U)r<7Z&{|;^s5zfPJUke>VZ>k`>e@l z>O^n8c%NFKenq7FP6SI)y;7%6Smw9u%hnvbciq`o;O5~8nN!xeUCxwf;rjIZZJqSx zPnXYSUbuO9fsmNITUU=*HwdPzNi+7oZGCiqkIVP{CtS{N|I-})|9R7*z5ZJQwglKX z8b;}x&CICZm~nE2Yuoe4d;d=V=RBB^FR3e`E76}2Q@<}aUsP96SCHLh%lD3H9sdml zRe8_c7h~@Bbn`Hz-=r&gA!%e-32ajz?NRBvg<@Ui14-z5@40J3O?1eKW#AJB5`Px{0 z7RK;!>$S0pA8zVSJjj}B2xA`I@E|GTz?%*2i3bmFc#s(J!9b@%Ld+&$DTL(s}CTxASvq6n{7Vk`(d0<>A`t0U} z@Wgq%l`9|KoM7`Y$74Z-nM$CJv`@~9b-d@<4l~`|`tW8##Dd*sHqt!YCD+^1n-d$~ z-TIJYpp)>*w>-DPOhOE-a*H{Xfusd!%tFG(l+A#ccOh1K9&a1#uI$5`8AG{Hj5%`+ zN%G)^22Wl!lRIbL*gjnP{>B@7rZC65*~{|Eigr)DmARx$dgWfXto;*zt(my%zDMo3 zu=nk;v!{hO*Inq(1S!f5(O$hZaP69FZd*4+Nnbscg(?v0^!HDuMr~ZYjdAGJfgz?wodiST^b$z@@m+xty9ytPW^l9RPnS=Q>A~u^E|oO%lFSJt=IEI sg!gv=nOa(=A6Kuqa%BCYAXI_;coEQo;t0MYE9%K!iX literal 0 HcmV?d00001 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml new file mode 100644 index 00000000000..62c9de23de0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml @@ -0,0 +1,96 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitSchedule.xml new file mode 100644 index 00000000000..09d5c02e458 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitSchedule.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitVehicles.xml new file mode 100644 index 00000000000..a5f1849ff76 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitVehicles.xml @@ -0,0 +1,36 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml new file mode 100644 index 00000000000..f868c244985 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml @@ -0,0 +1,196 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitSchedule.xml new file mode 100644 index 00000000000..0d6c5472cca --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitSchedule.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitVehicles.xml new file mode 100644 index 00000000000..05c114f2309 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitVehicles.xml @@ -0,0 +1,28 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/trainNetwork.xml new file mode 100644 index 00000000000..e58d928f24a --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/trainNetwork.xml @@ -0,0 +1,178 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml new file mode 100644 index 00000000000..33a88a1cc16 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml new file mode 100644 index 00000000000..c900e79b69c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml @@ -0,0 +1,50 @@ + + + + + + + 5.0 + serial + 5.0 + 5.0 + + + + + + + + + + + + + + + + + + 5.0 + serial + 5.0 + 5.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml new file mode 100644 index 00000000000..6222a2999f1 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml @@ -0,0 +1,423 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitSchedule.xml new file mode 100644 index 00000000000..9cc7ea08496 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitSchedule.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml new file mode 100644 index 00000000000..2642a38d9e0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml @@ -0,0 +1,29 @@ + + + + + + + 5.0 + serial + 5.0 + 2.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml new file mode 100644 index 00000000000..5f1fc638a6c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml @@ -0,0 +1,423 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml new file mode 100644 index 00000000000..1feafed0f84 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml new file mode 100644 index 00000000000..2642a38d9e0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml @@ -0,0 +1,29 @@ + + + + + + + 5.0 + serial + 5.0 + 2.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml index 3448f158baf..6421c870fdc 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml @@ -10,7 +10,7 @@ - + From 502b33b17667f8e4e92ee1dba14fe7e531b32052 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 8 Jun 2023 15:04:02 +0200 Subject: [PATCH 056/258] fix train not increase speed when leaving link, added todos and functions regarding target speed handling --- .../railsim/qsimengine/RailsimCalc.java | 101 ++++++++++++++---- .../railsim/qsimengine/RailsimEngine.java | 59 ++++++---- .../railsim/qsimengine/RailsimCalcTest.java | 15 +++ .../railsim/qsimengine/RailsimEngineTest.java | 44 ++++++++ .../railsim/qsimengine/RailsimTestUtils.java | 2 +- .../contrib/railsim/qsimengine/network1.xml | 57 ++++++++++ 6 files changed, 239 insertions(+), 39 deletions(-) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index fc4f4056cc5..2f4b8a66087 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -104,6 +104,26 @@ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double dece return new SpeedTarget(v, distDecel); } + /** + * Calculate the deceleration needed to come to halt exactly after {@code dist}. + * + * @return negative acceleration, always a negative number. + */ + static double calcTargetDecel(double dist, double currentSpeed) { + return -currentSpeed * currentSpeed / (2 * dist); + } + + /** + * Calculate the maximum speed that can be achieved if trains want to stop after dist. + */ + static double calcTargetSpeedForStop(double dist, double acceleration, double deceleration, double currentSpeed) { + + double nom = 2 * acceleration * deceleration * dist + + deceleration * currentSpeed * currentSpeed; + + return Math.sqrt(nom / (acceleration + deceleration)); + } + /** * Calc the distance deceleration needs to start and the target speed. */ @@ -140,6 +160,8 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) // train stops at the very end of a link if (i > 0 && state.isStop(state.route.get(i - 1).getLinkId())) allowed = 0; + else if (!link.isBlockedBy(state.driver) && !link.hasFreeTrack()) + allowed = 0; else allowed = link.getAllowedFreespeed(state.driver); } @@ -176,6 +198,66 @@ static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) return decelDist; } + /** + * Calculate the possible target speed a train can safely achieve. + * Taking upcoming links and deceleration into account, target speed might be lower than the allowed. + */ + static double calcPossibleTargetSpeed(TrainState state, RailLink currentLink) { + + // TODO: not correct yet + + // Distance to the next speed change point (link) + double dist = currentLink.length - state.headPosition; + + // Lookahead window + double window = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, state.allowedMaxSpeed / state.train.deceleration(), + -state.train.deceleration()) + currentLink.length; + + double targetSpeed = Double.POSITIVE_INFINITY; + + for (int i = state.routeIdx; i <= state.route.size(); i++) { + + RailLink link; + double allowed; + if (i == state.route.size()) { + link = null; + allowed = 0; + } else { + link = state.route.get(i); + + // If the previous link is a transit stop the speed needs to be 0 at the next link + // train stops at the very end of a link + if (i > 0 && state.isStop(state.route.get(i - 1).getLinkId())) + allowed = 0; + else if (!link.isBlockedBy(state.driver) && !link.hasFreeTrack()) + allowed = 0; + else + allowed = link.getAllowedFreespeed(state.driver); + } + + // only need to consider reduction + if (allowed < state.allowedMaxSpeed) { + + double timeDecel = (state.allowedMaxSpeed - allowed) / state.train.deceleration(); + double distDecel = calcTraveledDist(targetSpeed, timeDecel, -state.train.deceleration()); + + // There would not be enough headroom to stop + // target speed is reduced to this speed link. + if (distDecel > dist && allowed < targetSpeed) { + targetSpeed = allowed; + } + } + + if (link != null) + dist += link.length; + + if (dist >= window) + break; + } + + return Math.min(targetSpeed, state.allowedMaxSpeed); + } + /** * Calculate when the reservation function should be triggered. * Should return {@link Double#POSITIVE_INFINITY} if this distance is far in the future and can be checked at later point. @@ -215,25 +297,6 @@ public static double nextLinkReservation(TrainState state, RailLink currentLink) return Double.POSITIVE_INFINITY; } - /** - * Calculate the deceleration needed to come to halt exactly after {@code dist}. - * - * @return negative acceleration, always a negative number. - */ - static double calcTargetDecel(double dist, double currentSpeed) { - return -currentSpeed * currentSpeed / (2 * dist); - } - - /** - * Calculate the maximum speed that can be achieved if trains want to stop after dist. - */ - static double calcTargetSpeedForStop(double dist, TrainState state) { - - // TODO - - return 0; - } - /** * Links that need to be blocked or otherwise stop needs to be initiated. */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 350a063e2c2..6b89ce4ac37 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -168,26 +168,40 @@ private void blockTrack(double time, UpdateEvent event) { // Calculate the acceleration needed to stop if (state.acceleration >= 0) { // unoccupied space ahead - double dist = resources.getLink(state.headLink).length - state.headPosition; + // 1 meter is reserved to prevent trains from going on the next occupied link + double dist = resources.getLink(state.headLink).length - state.headPosition - 1; for (int i = state.routeIdx; i < state.route.size(); i++) { RailLink link = state.route.get(i); if (link.isBlockedBy(state.driver)) dist += link.length; } - assert FuzzyUtils.greaterEqualThan(dist, 1): "Small head room needed to stop before end of link"; + assert FuzzyUtils.greaterEqualThan(dist, 0) : "Small head room needed to stop before end of link"; - // Trains try to stop shortly before the next link - // Removing this would lead to train trying to enter the next link already - // Break when reservation is not possible state.targetSpeed = 0; state.acceleration = -state.train.deceleration(); - // TODO: use other method -// state.acceleration = RailsimCalc.calcTargetDecel(dist - 1, state.speed); + /* + TODO: there might be pt stop on a reserved segment, which would be missed + // Train can reduce speed smoothly + if (state.acceleration >= 0) { + state.targetSpeed = 0; + state.acceleration = RailsimCalc.calcTargetDecel(dist, state.speed); + }*/ + + /* + TODO: It might be possible to still accelerate + else if (state.acceleration >= 0) { + + // Train is still accelerating, maximum speed target will be calculated in this case + // deceleration will start at a later point + state.targetSpeed = RailsimCalc.calcTargetSpeedForStop(dist, state.train.acceleration(), state.train.deceleration(), state.speed); + } + */ + assert state.acceleration <= 0 && - FuzzyUtils.lessEqualThan(Math.abs(state.acceleration), state.train.deceleration()): "Train deceleration must be within specification, but was " + state.acceleration; + FuzzyUtils.lessEqualThan(Math.abs(state.acceleration), state.train.deceleration()) : "Train deceleration must be within specification, but was " + state.acceleration; } event.checkReservation = time + POLL_INTERVAL; @@ -213,6 +227,10 @@ private void checkTrackReservation(double time, UpdateEvent event) { state.acceleration = state.train.acceleration(); event.checkReservation = -1; + + // TODO: target speed could be too fast + //updateTargetSpeed(event, state); + decideNextUpdate(event); } else { @@ -335,7 +353,10 @@ private void enterLink(double time, UpdateEvent event) { assert link.isBlockedBy(state.driver) : "Link has to be blocked by driver when entered"; - updateTargetSpeed(event, state, link); + // TODO: might be too restrictive, target speed logic should take care of that + if (RailsimCalc.calcDecelDistanceAndSpeed(resources.getLink(state.headLink), event) == Double.POSITIVE_INFINITY ) { + updateTargetSpeed(event, state); + } createEvent(state.asEvent(time)); @@ -365,7 +386,7 @@ private void leaveLink(double time, UpdateEvent event) { state.tailLink = nextTailLink.getLinkId(); state.tailPosition = 0; - updateTargetSpeed(event, state, resources.getLink(state.headLink)); + updateTargetSpeed(event, state); decideNextUpdate(event); } @@ -420,7 +441,10 @@ private void updatePosition(double time, UpdateEvent event) { state.tailPosition += dist; state.timestamp = time; - assert state.routeIdx <= 1 || FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; + // When trains are put into the network their tail may be longer than the current link + // this assertion may not hold depending on the network, should possibly be removed + assert state.routeIdx <= 2 || FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; + assert FuzzyUtils.lessEqualThan(state.headPosition, resources.getLink(state.headLink).length) : "Illegal state update. Head position must be smaller than link length"; assert FuzzyUtils.greaterEqualThan(state.headPosition, 0) : "Head position must be positive"; assert FuzzyUtils.lessEqualThan(state.speed, state.allowedMaxSpeed) : "Speed must be less equal than the allowed speed"; @@ -444,9 +468,6 @@ private double handleTransitStop(double time, TrainState state) { return stopTime; } - // TODO: increase speed when leaving a link - - /** * Decide which update is the earliest and needs to be the next. */ @@ -473,7 +494,7 @@ private void decideNextUpdate(UpdateEvent event) { Double.POSITIVE_INFINITY : RailsimCalc.calcDecelDistanceAndSpeed(currentLink, event); - assert FuzzyUtils.greaterEqualThan(decelDist, 0) : "Deceleration distance must be larger than 0, but was" + decelDist; + assert FuzzyUtils.greaterEqualThan(decelDist, 0) : "Deceleration distance must be larger than 0, but was " + decelDist; // (3) next link needs reservation double reserveDist = Double.POSITIVE_INFINITY; @@ -530,11 +551,11 @@ private void decideNextUpdate(UpdateEvent event) { /** * Calculate the possible target speed. This can be lower than allowed if links in front are blocked. */ - private void updateTargetSpeed(UpdateEvent event, TrainState state, RailLink headLink) { + private void updateTargetSpeed(UpdateEvent event, TrainState state) { + + // TODO: needs to account for many more factors such as pt stops, possibly blocked links, possible speed under deceleration - // TODO: this probably needs to be a separate function to calculate possible target speed more accurately - if (RailsimCalc.calcDecelDistanceAndSpeed(headLink, event) == Double.POSITIVE_INFINITY && - !event.isAwaitingReservation()) { + if (!event.isAwaitingReservation()) { state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index e438b164ea1..1233611d110 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -77,4 +77,19 @@ public void decel() { .isCloseTo(1000, Offset.offset(0.001)); } + + @Test + public void speedForStop() { + + double v = RailsimCalc.calcTargetSpeedForStop(1000, 0.5, 0.5, 0); + + double accelTime = v / 0.5; + + double d1 = RailsimCalc.calcTraveledDist(0, accelTime, 0.5); + double d2 = RailsimCalc.calcTraveledDist(v, accelTime, -0.5); + + assertThat(d1 + d2) + .isCloseTo(1000, Offset.offset(0.0001)); + + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 2b2a87d9e69..b92a41af5dc 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -108,4 +108,48 @@ public void opposite() { .hasTrainState("regio2", 358, 1000, 0); } + + @Test + public void varyingSpeed_one() { + + RailsimTestUtils.Holder test = getTestEngine("network1.xml"); + + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 0, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); + + test.doSimStepUntil(10000); + + test.debug(collector, "varyingSpeed"); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio", 7599, 0, 2.7777777) + .hasTrainState("regio", 7674, 200, 0); + + test = getTestEngine("network1.xml"); + + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 0, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); + + test.doStateUpdatesUntil(10000, 1); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio", 7599, 0, 2.7777777) + .hasTrainState("regio", 7674, 200, 0); + +// test.debug(collector, "varyingSpeed_detailed"); + + } + + @Test + public void varyingSpeed_many() { + + RailsimTestUtils.Holder test = getTestEngine("network1.xml"); + + for (int i = 0; i < 10; i++) { + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio" + i, 60 * i, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); + } + + test.doSimStepUntil(50000); + + test.debug(collector, "speedDiff_many"); + + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java index 33c961c57b1..b97a6eb694d 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java @@ -58,7 +58,7 @@ public static void createDeparture(Holder test, TestVehicle type, String veh, d LeastCostPathCalculator.Path path = lcp.calcLeastCostPath(fromLink.getFromNode(), toLink.getToNode(), 0, null, null); NetworkRoute route = RouteUtils.createNetworkRoute(path.links.stream().map(Link::getId).toList()); - System.out.println("Creating departure with route " + route); + System.out.println("Creating departure with route" + route); // Setup mocks for driver and vehicle Id vehicleId = Id.createVehicleId(veh); diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml new file mode 100644 index 00000000000..ff528739548 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml @@ -0,0 +1,57 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + 999 + + + + + 2 + + + + + 999 + + + + + 5 + + + + + 999 + + + + + + + From 399bc242672aadc79a95f7668c9a8ed09ec9a1fb Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Fri, 9 Jun 2023 10:30:06 +0200 Subject: [PATCH 057/258] add test files for station rerouting --- .../integration/RailsimIntegrationTest.java | 5 + .../integration/station_rerouting/config.xml | 39 +++++++ .../station_rerouting/trainNetwork.xml | 108 ++++++++++++++++++ .../station_rerouting/transitSchedule.xml | 58 ++++++++++ .../station_rerouting/transitVehicles.xml | 30 +++++ 5 files changed, 240 insertions(+) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitVehicles.xml diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 0d27a524b36..fa0beb1d398 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -347,4 +347,9 @@ public void test14_mesoStations() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "14_mesoStations")); } + @Test @Ignore(value="no assert statements yet") + public void test_station_rerouting() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "station_rerouting")); + } + } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/trainNetwork.xml new file mode 100644 index 00000000000..835e19b2945 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/trainNetwork.xml @@ -0,0 +1,108 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + 2 + true + + + + + + + + + + + + + + 2 + true + + + + + 2 + + + + + + 2 + true + + + + + 2 + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitSchedule.xml new file mode 100644 index 00000000000..396197eee57 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitSchedule.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitVehicles.xml new file mode 100644 index 00000000000..9ac5b25f004 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitVehicles.xml @@ -0,0 +1,30 @@ + + + + + + + 5.0 + serial + 5.0 + + + + + + + + + + + + + + + + + + + + + From ab8295f99e40960ff75ba9a651904c87cc2d22cb Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 9 Jun 2023 11:48:41 +0200 Subject: [PATCH 058/258] reworked the speed calculation and target, should be more consistent now --- .../qsimengine/RailResourceManager.java | 18 +- .../railsim/qsimengine/RailsimCalc.java | 140 +----------- .../railsim/qsimengine/RailsimEngine.java | 212 ++++++++++++------ .../railsim/qsimengine/TrainState.java | 6 + .../railsim/qsimengine/UpdateEvent.java | 10 +- .../integration/RailsimIntegrationTest.java | 77 +++---- .../railsim/qsimengine/RailsimCalcTest.java | 7 +- .../railsim/qsimengine/RailsimEngineTest.java | 28 ++- .../{test0 => 0_simple}/config.xml | 0 .../{test0 => 0_simple}/transitSchedule.xml | 0 .../{test0 => 0_simple}/transitVehicles.xml | 0 11 files changed, 238 insertions(+), 260 deletions(-) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{test0 => 0_simple}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{test0 => 0_simple}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{test0 => 0_simple}/transitVehicles.xml (100%) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java index 7318a32e775..0ba8299bb2b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -129,6 +129,23 @@ public boolean tryBlockTrack(double time, MobsimDriverAgent driver, RailLink lin return false; } + /** + * Whether a driver already reserved a link or would be able to reserve it. + */ + public boolean canBeBlockedBy(RailLink link, MobsimDriverAgent driver) { + + if (link.isBlockedBy(driver)) + return true; + + Id resourceId = link.getResourceId(); + if (resourceId != null) { + RailResource resource = getResource(resourceId); + return resource.reservations.contains(driver) || resource.hasCapacity(); + } + + return link.hasFreeTrack(); + } + /** * Release a non-free track to be free again. */ @@ -143,5 +160,4 @@ public void releaseTrack(double time, MobsimDriverAgent driver, RailLink link) { } } - } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 2f4b8a66087..bfea36299fa 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -105,12 +105,12 @@ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double dece } /** - * Calculate the deceleration needed to come to halt exactly after {@code dist}. + * Calculate the deceleration needed to arrive at {@code targetSpeed} exactly after {@code dist}. * * @return negative acceleration, always a negative number. */ - static double calcTargetDecel(double dist, double currentSpeed) { - return -currentSpeed * currentSpeed / (2 * dist); + static double calcTargetDecel(double dist, double targetSpeed, double currentSpeed) { + return -(currentSpeed * currentSpeed - targetSpeed * targetSpeed) / (2 * dist); } /** @@ -124,140 +124,6 @@ static double calcTargetSpeedForStop(double dist, double acceleration, double de return Math.sqrt(nom / (acceleration + deceleration)); } - /** - * Calc the distance deceleration needs to start and the target speed. - */ - static double calcDecelDistanceAndSpeed(RailLink currentLink, UpdateEvent event) { - - TrainState state = event.state; - double assumedSpeed = state.speed; - - double maxSpeed = Math.max(assumedSpeed, state.allowedMaxSpeed); - - // Lookahead window - double window = RailsimCalc.calcTraveledDist(maxSpeed, maxSpeed / state.train.deceleration(), - -state.train.deceleration()) + currentLink.length; - - // Distance to the next speed change point (link) - double dist = currentLink.length - state.headPosition; - - double decelDist = Double.POSITIVE_INFINITY; - double targetSpeed = state.targetSpeed; - double speed = 0; - - for (int i = state.routeIdx; i <= state.route.size(); i++) { - - RailLink link; - double allowed; - // Last track where train comes to halt - if (i == state.route.size()) { - link = null; - allowed = 0; - } else { - link = state.route.get(i); - - // If the previous link is a transit stop the speed needs to be 0 at the next link - // train stops at the very end of a link - if (i > 0 && state.isStop(state.route.get(i - 1).getLinkId())) - allowed = 0; - else if (!link.isBlockedBy(state.driver) && !link.hasFreeTrack()) - allowed = 0; - else - allowed = link.getAllowedFreespeed(state.driver); - } - - // Special case when accelerating from 0 and reaching 0 again - if (allowed < assumedSpeed || (allowed == 0 && allowed == assumedSpeed)) { - - SpeedTarget target = calcTargetSpeed(dist, state.acceleration, state.train.deceleration(), state.speed, state.targetSpeed, allowed); - - double newDecelDist = dist - target.decelDist; - - if (newDecelDist < decelDist) { - decelDist = newDecelDist; - targetSpeed = target.targetSpeed; - speed = allowed; - } - } - - if (link != null) - dist += link.length; - - // don't need to look further than distance needed for full stop - if (dist >= window) - break; - } - - state.targetSpeed = targetSpeed; - event.newSpeed = speed; - - // No update required - if (FuzzyUtils.equals(event.newSpeed, state.targetSpeed)) - return Double.POSITIVE_INFINITY; - - return decelDist; - } - - /** - * Calculate the possible target speed a train can safely achieve. - * Taking upcoming links and deceleration into account, target speed might be lower than the allowed. - */ - static double calcPossibleTargetSpeed(TrainState state, RailLink currentLink) { - - // TODO: not correct yet - - // Distance to the next speed change point (link) - double dist = currentLink.length - state.headPosition; - - // Lookahead window - double window = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, state.allowedMaxSpeed / state.train.deceleration(), - -state.train.deceleration()) + currentLink.length; - - double targetSpeed = Double.POSITIVE_INFINITY; - - for (int i = state.routeIdx; i <= state.route.size(); i++) { - - RailLink link; - double allowed; - if (i == state.route.size()) { - link = null; - allowed = 0; - } else { - link = state.route.get(i); - - // If the previous link is a transit stop the speed needs to be 0 at the next link - // train stops at the very end of a link - if (i > 0 && state.isStop(state.route.get(i - 1).getLinkId())) - allowed = 0; - else if (!link.isBlockedBy(state.driver) && !link.hasFreeTrack()) - allowed = 0; - else - allowed = link.getAllowedFreespeed(state.driver); - } - - // only need to consider reduction - if (allowed < state.allowedMaxSpeed) { - - double timeDecel = (state.allowedMaxSpeed - allowed) / state.train.deceleration(); - double distDecel = calcTraveledDist(targetSpeed, timeDecel, -state.train.deceleration()); - - // There would not be enough headroom to stop - // target speed is reduced to this speed link. - if (distDecel > dist && allowed < targetSpeed) { - targetSpeed = allowed; - } - } - - if (link != null) - dist += link.length; - - if (dist >= window) - break; - } - - return Math.min(targetSpeed, state.allowedMaxSpeed); - } - /** * Calculate when the reservation function should be triggered. * Should return {@link Double#POSITIVE_INFINITY} if this distance is far in the future and can be checked at later point. diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 6b89ce4ac37..c8cddb712ea 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -26,7 +26,7 @@ final class RailsimEngine implements Steppable { private static final Logger log = LogManager.getLogger(RailsimEngine.class); /** - * If trains need to wait, they will check every x seconds if they can proceeed. + * If trains need to wait, they will check every x seconds if they can proceed. */ private static final double POLL_INTERVAL = 10; @@ -143,14 +143,7 @@ private void updateSpeed(double time, UpdateEvent event) { updatePosition(time, event); - state.targetSpeed = event.newSpeed; - if (state.targetSpeed < state.speed) - state.acceleration = -state.train.deceleration(); - else if (state.targetSpeed > state.speed) - state.acceleration = state.train.acceleration(); - - // Remove update information - event.newSpeed = -1; + decideTargetSpeed(event, state); createEvent(state.asEvent(time)); @@ -165,44 +158,7 @@ private void blockTrack(double time, UpdateEvent event) { if (!blockLinkTracks(time, state.routeIdx, state)) { - // Calculate the acceleration needed to stop - if (state.acceleration >= 0) { - // unoccupied space ahead - // 1 meter is reserved to prevent trains from going on the next occupied link - double dist = resources.getLink(state.headLink).length - state.headPosition - 1; - for (int i = state.routeIdx; i < state.route.size(); i++) { - RailLink link = state.route.get(i); - if (link.isBlockedBy(state.driver)) - dist += link.length; - } - - assert FuzzyUtils.greaterEqualThan(dist, 0) : "Small head room needed to stop before end of link"; - - - state.targetSpeed = 0; - state.acceleration = -state.train.deceleration(); - - /* - TODO: there might be pt stop on a reserved segment, which would be missed - // Train can reduce speed smoothly - if (state.acceleration >= 0) { - state.targetSpeed = 0; - state.acceleration = RailsimCalc.calcTargetDecel(dist, state.speed); - }*/ - - /* - TODO: It might be possible to still accelerate - else if (state.acceleration >= 0) { - - // Train is still accelerating, maximum speed target will be calculated in this case - // deceleration will start at a later point - state.targetSpeed = RailsimCalc.calcTargetSpeedForStop(dist, state.train.acceleration(), state.train.deceleration(), state.speed); - } - */ - - assert state.acceleration <= 0 && - FuzzyUtils.lessEqualThan(Math.abs(state.acceleration), state.train.deceleration()) : "Train deceleration must be within specification, but was " + state.acceleration; - } + decideTargetSpeed(event, state); event.checkReservation = time + POLL_INTERVAL; decideNextUpdate(event); @@ -217,26 +173,39 @@ private void checkTrackReservation(double time, UpdateEvent event) { TrainState state = event.state; + // TODO: some parts of the route might be available already + // train could advance and then stop again + // currently waits for whole segment to be unblocked + if (blockLinkTracks(time, state.routeIdx, state)) { - updatePosition(time, event); + event.checkReservation = -1; - // target speed could be lower, but this should be updated calc decel distance later - state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); - state.targetSpeed = state.allowedMaxSpeed; - state.acceleration = state.train.acceleration(); + // Train already waits at the end of previous link + if (event.waitingForLink) { - event.checkReservation = -1; + enterLink(time, event); - // TODO: target speed could be too fast - //updateTargetSpeed(event, state); + event.waitingForLink = false; - decideNextUpdate(event); + } else { + + updatePosition(time, event); + decideTargetSpeed(event, state); + decideNextUpdate(event); + + } } else { event.checkReservation = time + POLL_INTERVAL; - decideNextUpdate(event); + + // If train is already standing still and waiting, there is no update needed. + if (event.waitingForLink) { + event.plannedTime = time + POLL_INTERVAL; + } else { + decideNextUpdate(event); + } } @@ -269,7 +238,7 @@ private void updateDeparture(double time, UpdateEvent event) { createEvent(state.asEvent(time)); - if (stopTime == 0) { + if (stopTime <= 0) { // Call enter link logic immediately enterLink(time, event); } else { @@ -306,10 +275,11 @@ private void enterLink(double time, UpdateEvent event) { updatePosition(time, event); // current head link is the pt stop, which means the train is at the end of the link when this is called - if (state.isStop(state.headLink)) { + if (!event.waitingForLink && state.isStop(state.headLink)) { double stopTime = handleTransitStop(time, state); + assert stopTime >= 0: "Stop time must be positive"; assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at pt stop, but was " + state.speed; // Same event is re-scheduled after stopping, @@ -319,7 +289,7 @@ private void enterLink(double time, UpdateEvent event) { } // Arrival at destination - if (state.isRouteAtEnd()) { + if (!event.waitingForLink && state.isRouteAtEnd()) { assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at end, but was " + state.speed; @@ -339,6 +309,21 @@ private void enterLink(double time, UpdateEvent event) { return; } + RailLink nextHeadLink = state.route.get(state.routeIdx); + + // Train stops and wait for next link to be unblocked + if (!nextHeadLink.isBlockedBy(state.driver)) { + + assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 when waiting for next link, but was " + state.speed; + + event.waitingForLink = true; + event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; + event.plannedTime = time + POLL_INTERVAL; + + return; + } + + // On route departure the head link is null createEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); @@ -353,10 +338,7 @@ private void enterLink(double time, UpdateEvent event) { assert link.isBlockedBy(state.driver) : "Link has to be blocked by driver when entered"; - // TODO: might be too restrictive, target speed logic should take care of that - if (RailsimCalc.calcDecelDistanceAndSpeed(resources.getLink(state.headLink), event) == Double.POSITIVE_INFINITY ) { - updateTargetSpeed(event, state); - } + decideTargetSpeed(event, state); createEvent(state.asEvent(time)); @@ -386,7 +368,7 @@ private void leaveLink(double time, UpdateEvent event) { state.tailLink = nextTailLink.getLinkId(); state.tailPosition = 0; - updateTargetSpeed(event, state); + decideTargetSpeed(event, state); decideNextUpdate(event); } @@ -439,7 +421,11 @@ private void updatePosition(double time, UpdateEvent event) { state.headPosition += dist; state.tailPosition += dist; - state.timestamp = time; + + if (Double.isFinite(state.targetDecelDist)) { + state.targetDecelDist -= dist; + } + // When trains are put into the network their tail may be longer than the current link // this assertion may not hold depending on the network, should possibly be removed @@ -449,6 +435,8 @@ private void updatePosition(double time, UpdateEvent event) { assert FuzzyUtils.greaterEqualThan(state.headPosition, 0) : "Head position must be positive"; assert FuzzyUtils.lessEqualThan(state.speed, state.allowedMaxSpeed) : "Speed must be less equal than the allowed speed"; + state.timestamp = time; + createEvent(state.asEvent(time)); } @@ -490,9 +478,7 @@ private void decideNextUpdate(UpdateEvent event) { } // (2) start deceleration - double decelDist = (event.newSpeed == state.targetSpeed) ? - Double.POSITIVE_INFINITY : - RailsimCalc.calcDecelDistanceAndSpeed(currentLink, event); + double decelDist = state.targetDecelDist; assert FuzzyUtils.greaterEqualThan(decelDist, 0) : "Deceleration distance must be larger than 0, but was " + decelDist; @@ -540,17 +526,103 @@ private void decideNextUpdate(UpdateEvent event) { event.plannedTime = state.timestamp + RailsimCalc.calcRequiredTime(state, dist); // insert reservation event if necessary - if (event.checkReservation >= 0 && event.plannedTime > event.checkReservation) { + if (event.checkReservation >= 0 && event.checkReservation > state.timestamp && + event.plannedTime > event.checkReservation) { + + // TODO how could the check smaller than timestamp + event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; event.plannedTime = event.checkReservation; } assert Double.isFinite(event.plannedTime) : "Planned update time must be finite, but was " + event.plannedTime; + assert event.plannedTime >= state.timestamp : "Planned time must be after current time"; + + } + + /** + * Calculates possible target speed, consequential acceleration depending on current state. + */ + private void decideTargetSpeed(UpdateEvent event, TrainState state) { + + // Distance to next link + RailLink currentLink = resources.getLink(state.headLink); + + double dist = currentLink.length - state.headPosition; + state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); + + // Lookahead window + double window = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, state.allowedMaxSpeed / state.train.deceleration(), + -state.train.deceleration()) + currentLink.length; + + state.targetSpeed = state.allowedMaxSpeed; + state.targetDecelDist = Double.POSITIVE_INFINITY; + double targetDist = Double.POSITIVE_INFINITY; + + + for (int i = state.routeIdx; i <= state.route.size(); i++) { + + RailLink link; + double allowed; + if (i == state.route.size()) { + link = null; + allowed = 0; + } else { + link = state.route.get(i); + + // If the previous link is a transit stop the speed needs to be 0 at the next link + // train stops at the very end of a link + if (i > 0 && state.isStop(state.route.get(i - 1).getLinkId())) + allowed = 0; + else if (!resources.canBeBlockedBy(link, state.driver)) + allowed = 0; + else + allowed = link.getAllowedFreespeed(state.driver); + } + + if (allowed < state.allowedMaxSpeed) { + + RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(dist, state.train.acceleration(), state.train.deceleration(), state.speed, state.allowedMaxSpeed, allowed); + + double newDecelDist = dist - target.decelDist(); + + assert FuzzyUtils.greaterEqualThan(newDecelDist, 0) : "Decel dist must be greater than 0, or stopping is not possible"; + + // Need to decelerate now + if (FuzzyUtils.equals(newDecelDist, 0)) { + state.targetSpeed = allowed; + state.targetDecelDist = Double.POSITIVE_INFINITY; + targetDist = dist; + break; + } else if (newDecelDist < state.targetDecelDist) { + state.targetSpeed = target.targetSpeed(); + state.targetDecelDist = newDecelDist; + targetDist = dist; + } + } + + + if (link != null) + dist += link.length; + + if (dist >= window) + break; + + } + + if (FuzzyUtils.equals(state.speed, state.targetSpeed)) { + state.acceleration = 0; + } else if (FuzzyUtils.lessThan(state.speed, state.targetSpeed)) { + state.acceleration = state.train.acceleration(); + } else { + state.acceleration = RailsimCalc.calcTargetDecel(targetDist, state.targetSpeed, state.speed); + } } /** * Calculate the possible target speed. This can be lower than allowed if links in front are blocked. */ + @Deprecated private void updateTargetSpeed(UpdateEvent event, TrainState state) { // TODO: needs to account for many more factors such as pt stops, possibly blocked links, possible speed under deceleration diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 2fd786dbf14..e5c604c6f73 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -69,6 +69,11 @@ final class TrainState { */ double targetSpeed; + /** + * Train plans to decelerate after this distance. + */ + double targetDecelDist; + /** * Current allowed speed, which depends on train type, links, but not on other trains or speed needed to stop. */ @@ -103,6 +108,7 @@ final class TrainState { this.timestamp = timestamp; this.headLink = linkId; this.tailLink = linkId; + this.targetDecelDist = Double.POSITIVE_INFINITY; } @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index c2b3fd383c8..98c3dc9ca46 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -11,13 +11,21 @@ final class UpdateEvent implements Comparable { double plannedTime; Type type; - double newSpeed = -1; + /** + * Timestamp when next reservation will be checked. + */ double checkReservation = -1; + /** + * Whether train is waiting on the very link end for the next to be unblocked. + */ + boolean waitingForLink; + public UpdateEvent(TrainState state, Type type) { this.state = state; this.plannedTime = state.timestamp; this.type = type; + this.waitingForLink = false; } @Override diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index fa0beb1d398..aabc57c3afc 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -11,14 +11,12 @@ import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.vsp.scenario.SnzActivities; -import org.matsim.core.api.experimental.events.VehicleArrivesAtFacilityEvent; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.AbstractModule; import org.matsim.core.controler.Controler; import org.matsim.core.scenario.ScenarioUtils; import org.matsim.core.utils.io.IOUtils; -import org.matsim.core.utils.misc.Time; import org.matsim.examples.ExamplesUtils; import org.matsim.testcases.MatsimTestUtils; import org.matsim.testcases.utils.EventsCollector; @@ -28,54 +26,12 @@ import java.net.URL; import java.util.List; import java.util.Set; -import java.util.stream.Stream; public class RailsimIntegrationTest { @Rule public MatsimTestUtils utils = new MatsimTestUtils(); - @Test - public void scenario0() { - - File dir = new File(utils.getPackageInputDirectory(), "test0"); - - Config config = ConfigUtils.loadConfig(new File(dir, "config.xml").toString()); - - config.controler().setOutputDirectory(utils.getOutputDirectory()); - - Scenario scenario = ScenarioUtils.loadScenario(config); - Controler controler = new Controler(scenario); - - controler.addOverridingModule(new RailsimModule()); - controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); - - controler.run(); - - } - - @Test - public void scenario_genf() { - - File dir = new File(utils.getPackageInputDirectory(), "test_genf"); - - Config config = ConfigUtils.loadConfig(new File(dir, "config.xml").toString()); - - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setLastIteration(0); - config.controler().setCreateGraphs(false); - config.controler().setDumpDataAtEnd(false); - - Scenario scenario = ScenarioUtils.loadScenario(config); - Controler controler = new Controler(scenario); - - controler.addOverridingModule(new RailsimModule()); - controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); - - controler.run(); - - } - @Test public void scenario_kelheim() { @@ -115,6 +71,38 @@ public void scenario_kelheim() { controler.run(); } + @Test + public void test0_simple() { + + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "0_simple")); + + + } + + @Test + public void scenario_genf() { + + // TODO: can probably be replaced by the genf_bern test + + File dir = new File(utils.getPackageInputDirectory(), "test_genf"); + + Config config = ConfigUtils.loadConfig(new File(dir, "config.xml").toString()); + + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setLastIteration(0); + config.controler().setCreateGraphs(false); + config.controler().setDumpDataAtEnd(false); + + Scenario scenario = ScenarioUtils.loadScenario(config); + Controler controler = new Controler(scenario); + + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); + + controler.run(); + + } + private EventsCollector runSimulation(File scenarioDir) { Config config = ConfigUtils.loadConfig(new File(scenarioDir, "config.xml").toString()); @@ -206,7 +194,6 @@ public void test0_varyingCapacities() { currentTime += accTime3; assertTrainState(currentTime, linkSpeed, linkSpeed, 0, accDistance1 + cruiseDistance2 + accDistance3, train1events.get(4)); - // train can cruise with link speed until it needs to decelerate for next station double decTime5 = timeToAccelerate(linkSpeed, stationSpeed, -acceleration); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index 1233611d110..c2a06e2d17d 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -71,11 +71,16 @@ public void maxSpeed() { @Test public void decel() { - double d = RailsimCalc.calcTargetDecel(1000, 10); + double d = RailsimCalc.calcTargetDecel(1000, 0,10); assertThat(RailsimCalc.calcTraveledDist(10, -10 / d, d)) .isCloseTo(1000, Offset.offset(0.001)); + d = RailsimCalc.calcTargetDecel(1000, 5,10); + + assertThat(RailsimCalc.calcTraveledDist(10, -5 / d, d)) + .isCloseTo(1000, Offset.offset(0.001)); + } @Test diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index b92a41af5dc..3f8d8fd205f 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -91,7 +91,7 @@ public void opposite() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 293, 600, 0) - .hasTrainState("regio2", 358, 1000, 0); + .hasTrainState("regio2", 353, 1000, 0); test = getTestEngine("network0.xml"); @@ -105,7 +105,7 @@ public void opposite() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 293, 600, 0) - .hasTrainState("regio2", 358, 1000, 0); + .hasTrainState("regio2", 353, 1000, 0); } @@ -118,7 +118,7 @@ public void varyingSpeed_one() { test.doSimStepUntil(10000); - test.debug(collector, "varyingSpeed"); +// test.debug(collector, "varyingSpeed"); RailsimTestUtils.assertThat(collector) .hasTrainState("regio", 7599, 0, 2.7777777) @@ -147,9 +147,27 @@ public void varyingSpeed_many() { RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio" + i, 60 * i, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); } - test.doSimStepUntil(50000); + test.doSimStepUntil(30000); - test.debug(collector, "speedDiff_many"); + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio0", 7599, 0, 2.7777777) + .hasTrainState("regio0", 7674, 200, 0) + .hasTrainState("regio1", 7734, 200, 0); + +// test.debug(collector, "varyingSpeed_many"); + + test = getTestEngine("network1.xml"); + + for (int i = 0; i < 10; i++) { + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio" + i, 60 * i, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); + } + + test.doStateUpdatesUntil(30000, 1); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio0", 7599, 0, 2.7777777) + .hasTrainState("regio0", 7674, 200, 0) + .hasTrainState("regio1", 7734, 200, 0); } } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test0/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml From 7bb7de07d35b9f9e4c9525c1ba5fcf645ff067e7 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 9 Jun 2023 17:56:23 +0200 Subject: [PATCH 059/258] fix assertions in simple integration test 0, enabled all tests to see if they run through, genf_bern still has some issues --- .../railsim/qsimengine/RailsimEngine.java | 41 ++----- .../integration/RailsimIntegrationTest.java | 113 +++++++++++------- .../railsim/qsimengine/RailsimCalcTest.java | 16 +++ 3 files changed, 99 insertions(+), 71 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index c8cddb712ea..b802e1964f6 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -206,9 +206,7 @@ private void checkTrackReservation(double time, UpdateEvent event) { } else { decideNextUpdate(event); } - } - } private void updateDeparture(double time, UpdateEvent event) { @@ -279,7 +277,7 @@ private void enterLink(double time, UpdateEvent event) { double stopTime = handleTransitStop(time, state); - assert stopTime >= 0: "Stop time must be positive"; + assert stopTime >= 0 : "Stop time must be positive"; assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 at pt stop, but was " + state.speed; // Same event is re-scheduled after stopping, @@ -464,12 +462,7 @@ private void decideNextUpdate(UpdateEvent event) { TrainState state = event.state; RailLink currentLink = resources.getLink(state.headLink); -// if (state.routeIdx >= 877 && state.routeIdx <= 880 && state.timestamp >= 7300) -// log.info("debug"); - -// if (state.driver.getId().toString().equals("pt_Expresszug_BE_GE_train_0_train_Expresszug_BE_GE") && state.timestamp > 7000) -// log.info("debug"); - +// assert debug(state); // (1) max speed reached double accelDist = Double.POSITIVE_INFINITY; @@ -525,11 +518,13 @@ private void decideNextUpdate(UpdateEvent event) { // dist is the minimum of all supplied distances event.plannedTime = state.timestamp + RailsimCalc.calcRequiredTime(state, dist); - // insert reservation event if necessary - if (event.checkReservation >= 0 && event.checkReservation > state.timestamp && - event.plannedTime > event.checkReservation) { + // There could be old reservations events that need to be checked first + if (event.isAwaitingReservation() && event.checkReservation < state.timestamp) { + event.checkReservation = state.timestamp; + } - // TODO how could the check smaller than timestamp + // insert reservation event if necessary + if (event.isAwaitingReservation() && event.plannedTime > event.checkReservation) { event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; event.plannedTime = event.checkReservation; @@ -620,23 +615,13 @@ else if (!resources.canBeBlockedBy(link, state.driver)) } /** - * Calculate the possible target speed. This can be lower than allowed if links in front are blocked. + * Debug helper function to create breakpoints. */ - @Deprecated - private void updateTargetSpeed(UpdateEvent event, TrainState state) { - - // TODO: needs to account for many more factors such as pt stops, possibly blocked links, possible speed under deceleration - - if (!event.isAwaitingReservation()) { - - state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); - - if (state.allowedMaxSpeed > state.targetSpeed) { - state.targetSpeed = state.allowedMaxSpeed; - state.acceleration = state.train.acceleration(); - } - } + private static boolean debug(TrainState state) { + if (state.driver.getId().toString().equals("pt_Expresszug_GE_BE_train_3_train_Expresszug_GE_BE") && state.routeIdx > 550) + log.info("debug"); + return true; } /** diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index aabc57c3afc..dbaf23b8fd4 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -76,7 +76,6 @@ public void test0_simple() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "0_simple")); - } @Test @@ -141,12 +140,30 @@ private double timeForDistance(double d, double v) { return d / v; } - private void assertTrainState(double time, double speed, double targetSpeed, double acceleration, double headPosition, RailsimTrainStateEvent event) { - Assert.assertEquals(Math.ceil(time), event.getTime(), 1e-7); - Assert.assertEquals(speed, event.getSpeed(), 1e-5); - Assert.assertEquals(targetSpeed, event.getTargetSpeed(), 1e-7); - Assert.assertEquals(acceleration, event.getAcceleration(), 1e-5); - Assert.assertEquals(headPosition, event.getHeadPosition(), 1e-5); + private void assertTrainState(double time, double speed, double targetSpeed, double acceleration, double headPosition, + List events) { + + RailsimTrainStateEvent prev = null; + for (RailsimTrainStateEvent event : events) { + + if (event.getTime() > Math.ceil(time)) { + Assert.fail(String.format("No matching event found for time %f, speed %f pos %f, Closest event is%s", time, speed, headPosition, prev)); + } + + // If all assertions are true, returns successfully + try { + Assert.assertEquals(Math.ceil(time), event.getTime(), 1e-7); + Assert.assertEquals(speed, event.getSpeed(), 1e-5); + Assert.assertEquals(targetSpeed, event.getTargetSpeed(), 1e-7); + Assert.assertEquals(acceleration, event.getAcceleration(), 1e-5); + Assert.assertEquals(headPosition, event.getHeadPosition(), 1e-5); + return; + } catch (AssertionError e) { + // Check further events in loop + } + + prev = event; + } } @Test @@ -173,26 +190,26 @@ public void test0_varyingCapacities() { double linkLength = 50000; double currentTime = departureTime; - assertTrainState(currentTime, 0, 0, 0, stationLength, train1events.get(0)); + assertTrainState(currentTime, 0, 0, 0, stationLength, train1events); // train starts in the station, accelerates to station speed and continues until the train has left the station link - assertTrainState(currentTime, 0, stationSpeed, acceleration, 0, train1events.get(1)); + assertTrainState(currentTime, 0, stationSpeed, acceleration, 0, train1events); double accTime1 = timeToAccelerate(0, stationSpeed, acceleration); double accDistance1 = distanceTravelled(0, acceleration, accTime1); currentTime += accTime1; - assertTrainState(currentTime, stationSpeed, stationSpeed, 0, accDistance1, train1events.get(2)); + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, accDistance1, train1events); double cruiseTime2 = timeForDistance(trainLength - accDistance1, stationSpeed); double cruiseDistance2 = distanceTravelled(stationSpeed, 0, cruiseTime2); // should be = trainLength - accDistance1 currentTime += cruiseTime2; - assertTrainState(currentTime, stationSpeed, stationSpeed, 0, accDistance1 + cruiseDistance2, train1events.get(3)); + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, accDistance1 + cruiseDistance2, train1events); // train further accelerates to link speed double accTime3 = timeToAccelerate(stationSpeed, linkSpeed, acceleration); double accDistance3 = distanceTravelled(stationSpeed, acceleration, accTime3); currentTime += accTime3; - assertTrainState(currentTime, linkSpeed, linkSpeed, 0, accDistance1 + cruiseDistance2 + accDistance3, train1events.get(4)); + assertTrainState(currentTime, linkSpeed, linkSpeed, 0, accDistance1 + cruiseDistance2 + accDistance3, train1events); // train can cruise with link speed until it needs to decelerate for next station @@ -202,25 +219,33 @@ public void test0_varyingCapacities() { double cruiseDistance4 = linkLength - accDistance1 - cruiseDistance2 - accDistance3 - decDistance5; double cruiseTime4 = timeForDistance(cruiseDistance4, linkSpeed); currentTime += cruiseTime4; - assertTrainState(currentTime, linkSpeed, linkSpeed, 0, linkLength - decDistance5, train1events.get(6)); + assertTrainState(currentTime, linkSpeed, linkSpeed, 0, linkLength - decDistance5, train1events); // start deceleration - assertTrainState(currentTime, linkSpeed, stationSpeed, -acceleration, linkLength - decDistance5, train1events.get(7)); + assertTrainState(currentTime, linkSpeed, stationSpeed, -acceleration, linkLength - decDistance5, train1events); currentTime += decTime5; - assertTrainState(currentTime, stationSpeed, stationSpeed, 0, linkLength, train1events.get(8)); + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, linkLength, train1events); + + // Trains stops at station in the middle, calculated directly + currentTime = 32524.2; + assertTrainState(currentTime, 0, 0, 0, stationLength, train1events); + + double accelAfterStation = timeToAccelerate(0, stationSpeed, acceleration); + double distAfterStation = distanceTravelled(0, acceleration, accelAfterStation); + + currentTime += accelAfterStation; + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, distAfterStation, train1events); // train passes station completely - double cruiseDistance6 = stationLength + trainLength; - double cruiseTime6 = timeForDistance(cruiseDistance6, stationSpeed); - currentTime += cruiseTime6; + double leaveStation = timeForDistance(trainLength - distAfterStation, stationSpeed); - // TODO from here on forward, train-events are not yet correctly matched and the test will fail + currentTime += leaveStation; + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, trainLength, train1events); // train can accelerate again to link speed -// assertTrainState(currentTime, stationSpeed, linkSpeed, acceleration, trainLength, train1events.get(10)); // TODO double accTime7 = timeToAccelerate(stationSpeed, linkSpeed, acceleration); - double accDistance7 = distanceTravelled(stationSpeed, accDistance1, accTime7); + double accDistance7 = distanceTravelled(stationSpeed, acceleration, accTime7); currentTime += accTime7; -// assertTrainState(currentTime, linkSpeed, linkSpeed, 0, trainLength + accDistance7, train1events.get(11)); // TODO + assertTrainState(currentTime, linkSpeed, linkSpeed, 0, trainLength + accDistance7, train1events); // train can cruise with link speed until it needs to decelerate for final station double decTime9 = timeToAccelerate(linkSpeed, stationSpeed, -acceleration); @@ -229,10 +254,10 @@ public void test0_varyingCapacities() { double cruiseDistance8 = linkLength - trainLength - accDistance7 - decDistance9; double cruiseTime8 = timeForDistance(cruiseDistance8, linkSpeed); - currentTime += cruiseTime8; -// assertTrainState(currentTime, linkSpeed, stationSpeed, -acceleration, trainLength + accDistance7, train1events.get(12)); // TODO - currentTime += decTime9; -// assertTrainState(currentTime, stationSpeed, stationSpeed, 0, linkLength, train1events.get(13)); // TODO + currentTime += cruiseTime8 + decTime9; + + // end of link, entering station + assertTrainState(currentTime, stationSpeed, stationSpeed, 0, linkLength, train1events); // train can cruise into station link until it needs to fully brake double decTime11 = timeToAccelerate(stationSpeed, 0, -acceleration); @@ -240,14 +265,16 @@ public void test0_varyingCapacities() { double cruiseDistance10 = stationLength - decDistance11; double cruiseTime10 = timeForDistance(cruiseDistance10, stationSpeed); + currentTime += cruiseTime10; -// assertTrainState(currentTime, stationSpeed, 0, -acceleration, stationLength - cruiseDistance10, train1events.get(14)); // TODO + assertTrainState(currentTime, stationSpeed, 0, -acceleration, cruiseDistance10, train1events); + // final train arrival currentTime += decTime11; -// assertTrainState(currentTime, 0, 0, 0, stationLength, train1events.get(15)); // TODO + assertTrainState(currentTime, 0, 0, 0, stationLength, train1events); } - @Test @Ignore(value="no assert statements yet") + @Test public void test1_oppositeTraffic() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "1_oppositeTraffic")); @@ -269,72 +296,72 @@ public void test1_oppositeTraffic() { // Assert.assertEquals("train2 should arrive at 10:00:00", 36000.0, train0Arrival.getTime(), 1e-7); // TODO fix times } - @Test @Ignore(value="no assert statements yet") + @Test public void test2_oppositeTraffic_multipleTrains_oneSlowTrain() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "2_multipleOppositeTraffic")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test3_twoSources() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "3_twoSources")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test4_genf_bern() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "4_genf_bern")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test5_complexTwoSources() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "5_complexTwoSources")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test6_threeTracksMicroscopic() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "6_threeTracksMicroscopic")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test7_trainFollowing() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "7_trainFollowing")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test8_microStation() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "8_microStation")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test9_microStation2() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "9_microStation2")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test10_cross() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "10_cross")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test11_mesoStation() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "11_mesoStation")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test12_mesoStation2() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "12_mesoStation2")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test13_Y() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "13_Y")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test14_mesoStations() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "14_mesoStations")); } - @Test @Ignore(value="no assert statements yet") + @Test public void test_station_rerouting() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "station_rerouting")); } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index c2a06e2d17d..6fcd9decffc 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -83,6 +83,22 @@ public void decel() { } + @Test + public void targetSpeed() { + + RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(100, 0.5, 0.5, 0, 23, 0); + + assertThat(target.decelDist()) + .isCloseTo(50, Offset.offset(0.0001)); + + + target = RailsimCalc.calcTargetSpeed(200, 0.5, 0.5, 13, 13, 0); + + assertThat(target.decelDist()) + .isCloseTo(169, Offset.offset(0.0001)); + + } + @Test public void speedForStop() { From 7da8cf6d2ea89450ad1a051ade97aaff3ddcce40 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 14 Jun 2023 12:10:45 +0200 Subject: [PATCH 060/258] adding more tests and assertions --- .../qsimengine/RailResourceManager.java | 15 +---- .../railsim/qsimengine/RailsimCalc.java | 40 +++++++++---- .../railsim/qsimengine/RailsimEngine.java | 57 ++++++++++++------- .../railsim/qsimengine/RailsimEngineTest.java | 16 ++++++ 4 files changed, 85 insertions(+), 43 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java index 0ba8299bb2b..a2a46424206 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -132,18 +132,9 @@ public boolean tryBlockTrack(double time, MobsimDriverAgent driver, RailLink lin /** * Whether a driver already reserved a link or would be able to reserve it. */ - public boolean canBeBlockedBy(RailLink link, MobsimDriverAgent driver) { - - if (link.isBlockedBy(driver)) - return true; - - Id resourceId = link.getResourceId(); - if (resourceId != null) { - RailResource resource = getResource(resourceId); - return resource.reservations.contains(driver) || resource.hasCapacity(); - } - - return link.hasFreeTrack(); + public boolean isBlockedBy(RailLink link, MobsimDriverAgent driver) { + // If a link is blocked, the resource must be blocked as well + return link.isBlockedBy(driver); } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index bfea36299fa..0272fda19f3 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -74,23 +74,40 @@ static double calcRequiredTime(TrainState state, double dist) { * again after traveled {@code dist}. */ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double deceleration, - double currentSpeed, double targetSpeed, double finalSpeed) { + double currentSpeed, double allowedSpeed, double finalSpeed) { - double timeDecel = (targetSpeed - finalSpeed) / deceleration; - double distDecel = calcTraveledDist(targetSpeed, timeDecel, -deceleration); + // Calculation is simplified if target is the same + if (FuzzyUtils.equals(allowedSpeed, finalSpeed)) { + return new SpeedTarget(finalSpeed, Double.POSITIVE_INFINITY); + } + + double timeDecel = (allowedSpeed - finalSpeed) / deceleration; + double distDecel = calcTraveledDist(allowedSpeed, timeDecel, -deceleration); + + // No further acceleration needed + if (FuzzyUtils.equals(currentSpeed, allowedSpeed)) { + double decelDist = dist - distDecel; + + // Start to stop now + if (FuzzyUtils.equals(decelDist, 0)) { + return new SpeedTarget(finalSpeed, 0); + } + + // Decelerate later + return new SpeedTarget(allowedSpeed, decelDist); + } - // This code below only works during deceleration - if (acceleration <= 0 || currentSpeed >= targetSpeed) - return new SpeedTarget(targetSpeed, distDecel); - assert FuzzyUtils.greaterEqualThan(targetSpeed, finalSpeed) : "Final speed must be smaller than target"; + assert FuzzyUtils.greaterEqualThan(allowedSpeed, currentSpeed) : "Current speed must be lower than allowed"; + assert FuzzyUtils.greaterEqualThan(allowedSpeed, finalSpeed) : "Final speed must be smaller than target"; - double timeAccel = (targetSpeed - currentSpeed) / acceleration; + double timeAccel = (allowedSpeed - currentSpeed) / acceleration; double distAccel = calcTraveledDist(currentSpeed, timeAccel, acceleration); // there is enough distance to accelerate to the target speed - if (distAccel + distDecel < dist) - return new SpeedTarget(targetSpeed, distDecel); + if (FuzzyUtils.lessThan(distAccel + distDecel, dist)) { + return new SpeedTarget(allowedSpeed, dist - distDecel); + } double nom = 2 * acceleration * deceleration * dist + acceleration * finalSpeed * finalSpeed @@ -101,9 +118,10 @@ static SpeedTarget calcTargetSpeed(double dist, double acceleration, double dece timeDecel = (v - finalSpeed) / deceleration; distDecel = calcTraveledDist(v, timeDecel, -deceleration); - return new SpeedTarget(v, distDecel); + return new SpeedTarget(v, dist - distDecel); } + /** * Calculate the deceleration needed to arrive at {@code targetSpeed} exactly after {@code dist}. * diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index b802e1964f6..2a6b09be27f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -498,7 +498,7 @@ private void decideNextUpdate(UpdateEvent event) { double dist; if (accelDist <= decelDist && accelDist <= reserveDist && accelDist <= tailDist && accelDist <= headDist) { dist = accelDist; - event.type = UpdateEvent.Type.POSITION; + event.type = UpdateEvent.Type.SPEED_CHANGE; } else if (decelDist <= accelDist && decelDist <= reserveDist && decelDist <= tailDist && decelDist <= headDist) { dist = decelDist; event.type = UpdateEvent.Type.SPEED_CHANGE; @@ -550,10 +550,10 @@ private void decideTargetSpeed(UpdateEvent event, TrainState state) { double window = RailsimCalc.calcTraveledDist(state.allowedMaxSpeed, state.allowedMaxSpeed / state.train.deceleration(), -state.train.deceleration()) + currentLink.length; + double minAllowed = state.allowedMaxSpeed; + state.targetSpeed = state.allowedMaxSpeed; state.targetDecelDist = Double.POSITIVE_INFINITY; - double targetDist = Double.POSITIVE_INFINITY; - for (int i = state.routeIdx; i <= state.route.size(); i++) { @@ -569,33 +569,44 @@ private void decideTargetSpeed(UpdateEvent event, TrainState state) { // train stops at the very end of a link if (i > 0 && state.isStop(state.route.get(i - 1).getLinkId())) allowed = 0; - else if (!resources.canBeBlockedBy(link, state.driver)) + else if (!resources.isBlockedBy(link, state.driver)) allowed = 0; else allowed = link.getAllowedFreespeed(state.driver); } - if (allowed < state.allowedMaxSpeed) { + // If speed is higher than seen limits, no further links need to be considered + if (allowed > minAllowed) { + break; + } + + // Might happen if train is on the very end + if (FuzzyUtils.equals(dist, 0)) { + assert FuzzyUtils.lessEqualThan(state.speed, allowed): "Speed must be smaller than allowed"; + continue; + } - RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(dist, state.train.acceleration(), state.train.deceleration(), state.speed, state.allowedMaxSpeed, allowed); + RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(dist, state.train.acceleration(), state.train.deceleration(), state.speed, state.allowedMaxSpeed, allowed); - double newDecelDist = dist - target.decelDist(); + assert FuzzyUtils.greaterEqualThan(target.decelDist(), 0) : "Decel dist must be greater than 0, or stopping is not possible"; - assert FuzzyUtils.greaterEqualThan(newDecelDist, 0) : "Decel dist must be greater than 0, or stopping is not possible"; + if (FuzzyUtils.equals(target.decelDist(), 0)) { // Need to decelerate now - if (FuzzyUtils.equals(newDecelDist, 0)) { - state.targetSpeed = allowed; - state.targetDecelDist = Double.POSITIVE_INFINITY; - targetDist = dist; - break; - } else if (newDecelDist < state.targetDecelDist) { - state.targetSpeed = target.targetSpeed(); - state.targetDecelDist = newDecelDist; - targetDist = dist; - } - } + state.targetSpeed = allowed; + state.targetDecelDist = Double.POSITIVE_INFINITY; + break; + } else if (target.decelDist() < state.targetDecelDist && target.targetSpeed() <= state.targetSpeed) { + // Decelerate later + state.targetSpeed = target.targetSpeed(); + state.targetDecelDist = target.decelDist(); + + } else if (target.targetSpeed() > state.targetSpeed && target.decelDist() >= state.targetDecelDist) { + // Acceleration is required + state.targetSpeed = target.targetSpeed(); + state.targetDecelDist = target.decelDist(); + } if (link != null) dist += link.length; @@ -603,15 +614,21 @@ else if (!resources.canBeBlockedBy(link, state.driver)) if (dist >= window) break; + minAllowed = allowed; } + // Calc accel depending on target if (FuzzyUtils.equals(state.speed, state.targetSpeed)) { state.acceleration = 0; } else if (FuzzyUtils.lessThan(state.speed, state.targetSpeed)) { state.acceleration = state.train.acceleration(); } else { - state.acceleration = RailsimCalc.calcTargetDecel(targetDist, state.targetSpeed, state.speed); + state.acceleration = -state.train.deceleration(); } + + assert FuzzyUtils.equals(state.targetSpeed, state.speed) || state.acceleration != 0 : "Acceleration must be set if target speed is different than current"; + assert FuzzyUtils.greaterThan(state.targetDecelDist, 0) : "Target decel must be greater than 0 after updating"; + } /** diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 3f8d8fd205f..ed795b103bc 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -170,4 +170,20 @@ public void varyingSpeed_many() { .hasTrainState("regio1", 7734, 200, 0); } + + @Test + public void trainFollowing() { + + RailsimTestUtils.Holder test = getTestEngine("../integration/7_trainFollowing/trainNetwork.xml"); + + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "1-2", "20-21"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "1-2", "20-21"); + + test.doSimStepUntil(10000); + +// test.debug(collector, "trainFollowing"); + + + } + } From 7d34e13d9fac4ad2505ff734525280bf4672efb8 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 14 Jun 2023 15:55:52 +0200 Subject: [PATCH 061/258] reworked link reservation to be more consistent, all tests running now --- .../railsim/qsimengine/RailsimCalc.java | 58 +++++++++---- .../railsim/qsimengine/RailsimEngine.java | 82 +++++++++--------- .../integration/RailsimIntegrationTest.java | 25 ------ .../railsim/qsimengine/RailsimEngineTest.java | 8 +- .../railsim/integration/test_genf/config.xml | 37 -------- .../test_genf/transitSchedule.xml.gz | Bin 70575 -> 0 bytes .../test_genf/transitVehicles.xml.gz | Bin 1366 -> 0 bytes 7 files changed, 84 insertions(+), 126 deletions(-) delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitSchedule.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitVehicles.xml.gz diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 0272fda19f3..49c28046f9d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -152,59 +152,79 @@ static double calcTargetSpeedForStop(double dist, double acceleration, double de */ public static double nextLinkReservation(TrainState state, RailLink currentLink) { + double assumedSpeed = calcPossibleMaxSpeed(state); + // time needed for full stop - double assumedSpeed = state.allowedMaxSpeed; double stopTime = assumedSpeed / state.train.deceleration(); assert stopTime > 0 : "Stop time can not be negative"; - // TODO: there is an additional safety factor (also in links to block) - // this might be reduced, but currently the case when a train stops exactly before the not reserved link is not handled - // safety distance - double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) * 1.5; + double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()); + int idx = state.routeIdx; double dist = currentLink.length - state.headPosition; - int idx = state.routeIdx; - do { - RailLink nextLink = state.route.get(idx++); + RailLink nextLink = null; + // need to check beyond safety distance + while (FuzzyUtils.lessEqualThan(dist, safety * 2) && idx < state.route.size()) { + nextLink = state.route.get(idx++); if (!nextLink.isBlockedBy(state.driver)) return dist - safety; + // No reservation beyond pt stop + if (state.isStop(nextLink.getLinkId())) + break; + dist += nextLink.length; + } - } while (dist <= safety && idx < state.route.size()); + // No reservation needed after the end + if (idx == state.route.size() || (nextLink != null && state.isStop(nextLink.getLinkId()))) + return Double.POSITIVE_INFINITY; - // No need to reserve yet - return Double.POSITIVE_INFINITY; + return dist - safety; } /** * Links that need to be blocked or otherwise stop needs to be initiated. */ - public static List calcLinksToBlock(int idx, TrainState state) { + public static List calcLinksToBlock(TrainState state, RailLink currentLink) { List result = new ArrayList<>(); - // safety distance - double assumedSpeed = state.allowedMaxSpeed; - + double assumedSpeed = calcPossibleMaxSpeed(state); double stopTime = assumedSpeed / state.train.deceleration(); + // safety distance - double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()) * 1.5 + state.headPosition; + double safety = RailsimCalc.calcTraveledDist(assumedSpeed, stopTime, -state.train.deceleration()); - double reserved = 0; - while (reserved < safety && idx < state.route.size()) { + int idx = state.routeIdx; + + // dist to next + double dist = currentLink.length - state.headPosition; + + while (FuzzyUtils.lessEqualThan(dist, safety) && idx < state.route.size()) { RailLink nextLink = state.route.get(idx++); result.add(nextLink); - reserved += nextLink.length; + dist += nextLink.length; + + // Beyond pt stop links don't need to be reserved + if (state.isStop(nextLink.getLinkId())) + break; } return result; } + private static double calcPossibleMaxSpeed(TrainState state) { + + // TODO better would be the maximum that is possible over the next upcoming links + // taking safety distance into account + + return state.train.maxVelocity(); + } record SpeedTarget(double targetSpeed, double decelDist) implements Comparable { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 2a6b09be27f..869130d4071 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -156,7 +156,7 @@ private void blockTrack(double time, UpdateEvent event) { updatePosition(time, event); - if (!blockLinkTracks(time, state.routeIdx, state)) { + if (!blockLinkTracks(time, state)) { decideTargetSpeed(event, state); @@ -177,7 +177,7 @@ private void checkTrackReservation(double time, UpdateEvent event) { // train could advance and then stop again // currently waits for whole segment to be unblocked - if (blockLinkTracks(time, state.routeIdx, state)) { + if (blockLinkTracks(time, state)) { event.checkReservation = -1; @@ -218,7 +218,7 @@ private void updateDeparture(double time, UpdateEvent event) { state.headPosition = resources.getLink(state.headLink).length; state.tailPosition = resources.getLink(state.headLink).length - state.train.length(); - if (blockLinkTracks(time, 0, state)) { + if (blockLinkTracks(time, state)) { createEvent(new PersonEntersVehicleEvent(time, state.driver.getId(), state.driver.getVehicle().getId())); createEvent(new VehicleEntersTrafficEvent(time, state.driver.getId(), @@ -253,9 +253,9 @@ private void updateDeparture(double time, UpdateEvent event) { /** * Reserve links in advance as necessary. */ - private boolean blockLinkTracks(double time, int idx, TrainState state) { + private boolean blockLinkTracks(double time, TrainState state) { - List links = RailsimCalc.calcLinksToBlock(idx, state); + List links = RailsimCalc.calcLinksToBlock(state, resources.getLink(state.headLink)); if (links.isEmpty()) return true; @@ -307,13 +307,8 @@ private void enterLink(double time, UpdateEvent event) { return; } - RailLink nextHeadLink = state.route.get(state.routeIdx); - // Train stops and wait for next link to be unblocked - if (!nextHeadLink.isBlockedBy(state.driver)) { - - assert FuzzyUtils.equals(state.speed, 0) : "Speed must be 0 when waiting for next link, but was " + state.speed; - + if (FuzzyUtils.equals(state.speed, 0) && !blockLinkTracks(time, state)) { event.waitingForLink = true; event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; event.plannedTime = time + POLL_INTERVAL; @@ -482,6 +477,16 @@ private void decideNextUpdate(UpdateEvent event) { if (reserveDist < 0) reserveDist = 0; + + // Outside of block track the reserve distance is always greater 0 + // infinite loops would occur otherwise + if (!(event.type != UpdateEvent.Type.BLOCK_TRACK || FuzzyUtils.greaterThan(reserveDist, 0))) { + // There are here for debugging + List tmp = RailsimCalc.calcLinksToBlock(state, currentLink); + double r = RailsimCalc.nextLinkReservation(state, currentLink); + + throw new AssertionError("Reserve distance must be positive, but was" + r); + } } // (4) tail link changes @@ -496,15 +501,15 @@ private void decideNextUpdate(UpdateEvent event) { // Find the earliest required update double dist; - if (accelDist <= decelDist && accelDist <= reserveDist && accelDist <= tailDist && accelDist <= headDist) { + if (reserveDist <= accelDist && reserveDist <= decelDist && reserveDist <= tailDist && reserveDist <= headDist) { + dist = reserveDist; + event.type = UpdateEvent.Type.BLOCK_TRACK; + } else if (accelDist <= decelDist && accelDist <= reserveDist && accelDist <= tailDist && accelDist <= headDist) { dist = accelDist; event.type = UpdateEvent.Type.SPEED_CHANGE; } else if (decelDist <= accelDist && decelDist <= reserveDist && decelDist <= tailDist && decelDist <= headDist) { dist = decelDist; event.type = UpdateEvent.Type.SPEED_CHANGE; - } else if (reserveDist <= accelDist && reserveDist <= decelDist && reserveDist <= tailDist && reserveDist <= headDist) { - dist = reserveDist; - event.type = UpdateEvent.Type.BLOCK_TRACK; } else if (tailDist <= decelDist && tailDist <= reserveDist && tailDist <= headDist) { dist = tailDist; event.type = UpdateEvent.Type.LEAVE_LINK; @@ -575,37 +580,29 @@ else if (!resources.isBlockedBy(link, state.driver)) allowed = link.getAllowedFreespeed(state.driver); } - // If speed is higher than seen limits, no further links need to be considered - if (allowed > minAllowed) { - break; - } + // Only need to consider if speed is lower than the allowed speed + if (!FuzzyUtils.equals(dist, 0) && allowed <= minAllowed) { + RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(dist, state.train.acceleration(), state.train.deceleration(), state.speed, state.allowedMaxSpeed, allowed); - // Might happen if train is on the very end - if (FuzzyUtils.equals(dist, 0)) { - assert FuzzyUtils.lessEqualThan(state.speed, allowed): "Speed must be smaller than allowed"; - continue; - } + assert FuzzyUtils.greaterEqualThan(target.decelDist(), 0) : "Decel dist must be greater than 0, or stopping is not possible"; - RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(dist, state.train.acceleration(), state.train.deceleration(), state.speed, state.allowedMaxSpeed, allowed); + if (FuzzyUtils.equals(target.decelDist(), 0)) { - assert FuzzyUtils.greaterEqualThan(target.decelDist(), 0) : "Decel dist must be greater than 0, or stopping is not possible"; + // Need to decelerate now + state.targetSpeed = allowed; + state.targetDecelDist = Double.POSITIVE_INFINITY; + break; + } else if (target.decelDist() < state.targetDecelDist && target.targetSpeed() <= state.targetSpeed) { - if (FuzzyUtils.equals(target.decelDist(), 0)) { - - // Need to decelerate now - state.targetSpeed = allowed; - state.targetDecelDist = Double.POSITIVE_INFINITY; - break; - } else if (target.decelDist() < state.targetDecelDist && target.targetSpeed() <= state.targetSpeed) { + // Decelerate later + state.targetSpeed = target.targetSpeed(); + state.targetDecelDist = target.decelDist(); - // Decelerate later - state.targetSpeed = target.targetSpeed(); - state.targetDecelDist = target.decelDist(); - - } else if (target.targetSpeed() > state.targetSpeed && target.decelDist() >= state.targetDecelDist) { - // Acceleration is required - state.targetSpeed = target.targetSpeed(); - state.targetDecelDist = target.decelDist(); + } else if (target.targetSpeed() > state.targetSpeed && !Double.isFinite(state.targetDecelDist)) { + // Acceleration is required + state.targetSpeed = target.targetSpeed(); + state.targetDecelDist = target.decelDist(); + } } if (link != null) @@ -638,6 +635,9 @@ private static boolean debug(TrainState state) { if (state.driver.getId().toString().equals("pt_Expresszug_GE_BE_train_3_train_Expresszug_GE_BE") && state.routeIdx > 550) log.info("debug"); + if (state.driver.getVehicle().getId().toString().equals("regio1")) + log.info("debug"); + return true; } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index dbaf23b8fd4..69a766999a5 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -4,7 +4,6 @@ import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.matsim.api.core.v01.Scenario; @@ -78,30 +77,6 @@ public void test0_simple() { } - @Test - public void scenario_genf() { - - // TODO: can probably be replaced by the genf_bern test - - File dir = new File(utils.getPackageInputDirectory(), "test_genf"); - - Config config = ConfigUtils.loadConfig(new File(dir, "config.xml").toString()); - - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setLastIteration(0); - config.controler().setCreateGraphs(false); - config.controler().setDumpDataAtEnd(false); - - Scenario scenario = ScenarioUtils.loadScenario(config); - Controler controler = new Controler(scenario); - - controler.addOverridingModule(new RailsimModule()); - controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); - - controler.run(); - - } - private EventsCollector runSimulation(File scenarioDir) { Config config = ConfigUtils.loadConfig(new File(scenarioDir, "config.xml").toString()); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index ed795b103bc..13435ecbad6 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -91,7 +91,7 @@ public void opposite() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 293, 600, 0) - .hasTrainState("regio2", 353, 1000, 0); + .hasTrainState("regio2", 358, 1000, 0); test = getTestEngine("network0.xml"); @@ -105,7 +105,7 @@ public void opposite() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 293, 600, 0) - .hasTrainState("regio2", 353, 1000, 0); + .hasTrainState("regio2", 358, 1000, 0); } @@ -154,7 +154,7 @@ public void varyingSpeed_many() { .hasTrainState("regio0", 7674, 200, 0) .hasTrainState("regio1", 7734, 200, 0); -// test.debug(collector, "varyingSpeed_many"); + test.debug(collector, "varyingSpeed_many"); test = getTestEngine("network1.xml"); @@ -179,7 +179,7 @@ public void trainFollowing() { RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "1-2", "20-21"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "1-2", "20-21"); - test.doSimStepUntil(10000); + test.doSimStepUntil(5000); // test.debug(collector, "trainFollowing"); diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml deleted file mode 100644 index 6421c870fdc..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/config.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitSchedule.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitSchedule.xml.gz deleted file mode 100644 index 5989dd29a2da50987ae36f330d55f9c789b06a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70575 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 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/test_genf/transitVehicles.xml.gz deleted file mode 100644 index 25ad72a9773194a1f7bddab3c9f2e74ec47735fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1366 zcmb2|=3sz;w{vp+Z+nQe+8WtG7dpIZW5%5%)s>QGW>C=PbCIYUO zdzF_P_0{bETqO9|`u3}+*ELD2T<6)W`}C|%vA4j;q}!+E zgYR%^3!OZ(%WL|5rOdchTs&6(aaFH9XQl1eO$mE}_=I=@xmwKLV{l0C@VFdwh!Iq@unhnJPBXC++B+j#S0X3oFwq)0W%p<5J&!SUPB1 zd-6uU$Eh{ii?`SmS~|>4+U@Rqd7JL<2VWO(U)r<7Z&{|;^s5zfPJUke>VZ>k`>e@l z>O^n8c%NFKenq7FP6SI)y;7%6Smw9u%hnvbciq`o;O5~8nN!xeUCxwf;rjIZZJqSx zPnXYSUbuO9fsmNITUU=*HwdPzNi+7oZGCiqkIVP{CtS{N|I-})|9R7*z5ZJQwglKX z8b;}x&CICZm~nE2Yuoe4d;d=V=RBB^FR3e`E76}2Q@<}aUsP96SCHLh%lD3H9sdml zRe8_c7h~@Bbn`Hz-=r&gA!%e-32ajz?NRBvg<@Ui14-z5@40J3O?1eKW#AJB5`Px{0 z7RK;!>$S0pA8zVSJjj}B2xA`I@E|GTz?%*2i3bmFc#s(J!9b@%Ld+&$DTL(s}CTxASvq6n{7Vk`(d0<>A`t0U} z@Wgq%l`9|KoM7`Y$74Z-nM$CJv`@~9b-d@<4l~`|`tW8##Dd*sHqt!YCD+^1n-d$~ z-TIJYpp)>*w>-DPOhOE-a*H{Xfusd!%tFG(l+A#ccOh1K9&a1#uI$5`8AG{Hj5%`+ zN%G)^22Wl!lRIbL*gjnP{>B@7rZC65*~{|Eigr)DmARx$dgWfXto;*zt(my%zDMo3 zu=nk;v!{hO*Inq(1S!f5(O$hZaP69FZd*4+Nnbscg(?v0^!HDuMr~ZYjdAGJfgz?wodiST^b$z@@m+xty9ytPW^l9RPnS=Q>A~u^E|oO%lFSJt=IEI sg!gv=nOa(=A6Kuqa%BCYAXI_;coEQo;t0MYE9%K!iX From bf2964c75ac35cb078375ad2c4f81485b26accc9 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 14 Jun 2023 16:59:33 +0200 Subject: [PATCH 062/258] trains advance if link in front is already unblocked, added asserts to tests --- .../railsim/qsimengine/RailsimCalc.java | 17 ++++++++-- .../railsim/qsimengine/RailsimEngine.java | 32 ++++++++++++------- .../railsim/qsimengine/RailsimEngineTest.java | 27 +++++++++++++--- .../railsim/qsimengine/RailsimTestUtils.java | 2 +- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 49c28046f9d..0b7ae04621d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -218,12 +218,23 @@ public static List calcLinksToBlock(TrainState state, RailLink current return result; } + /** + * Maximum speed of the next upcoming links. + */ private static double calcPossibleMaxSpeed(TrainState state) { - // TODO better would be the maximum that is possible over the next upcoming links - // taking safety distance into account + double safety = RailsimCalc.calcTraveledDist(state.train.maxVelocity(), state.train.maxVelocity() / state.train.deceleration(), -state.train.deceleration()); + double maxSpeed = state.allowedMaxSpeed; + + double dist = 0; + for (int i = 0; i < state.route.size() && dist < safety; i++) { + RailLink link = state.route.get(i); + maxSpeed = Math.max(maxSpeed, link.getAllowedFreespeed(state.driver)); + + dist += link.length; + } - return state.train.maxVelocity(); + return maxSpeed; } record SpeedTarget(double targetSpeed, double decelDist) implements Comparable { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 869130d4071..5bb7a5fcc6c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -173,19 +173,23 @@ private void checkTrackReservation(double time, UpdateEvent event) { TrainState state = event.state; - // TODO: some parts of the route might be available already - // train could advance and then stop again - // currently waits for whole segment to be unblocked + RailLink nextLink = state.route.get(state.routeIdx); - if (blockLinkTracks(time, state)) { + boolean allBlocked = blockLinkTracks(time, state); - event.checkReservation = -1; + // Driver can advance if the next link is already free + if (allBlocked || nextLink.isBlockedBy(state.driver)) { + + if (allBlocked) + event.checkReservation = -1; + else { + event.checkReservation = time + POLL_INTERVAL; + } // Train already waits at the end of previous link if (event.waitingForLink) { enterLink(time, event); - event.waitingForLink = false; } else { @@ -307,13 +311,17 @@ private void enterLink(double time, UpdateEvent event) { return; } - // Train stops and wait for next link to be unblocked + // Train stopped and reserves next links if (FuzzyUtils.equals(state.speed, 0) && !blockLinkTracks(time, state)) { - event.waitingForLink = true; - event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; - event.plannedTime = time + POLL_INTERVAL; - return; + RailLink currentLink = state.route.get(state.routeIdx); + // If this linked is blocked the driver can continue + if (!currentLink.isBlockedBy(state.driver)) { + event.waitingForLink = true; + event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; + event.plannedTime = time + POLL_INTERVAL; + return; + } } @@ -635,7 +643,7 @@ private static boolean debug(TrainState state) { if (state.driver.getId().toString().equals("pt_Expresszug_GE_BE_train_3_train_Expresszug_GE_BE") && state.routeIdx > 550) log.info("debug"); - if (state.driver.getVehicle().getId().toString().equals("regio1")) + if (state.driver.getVehicle().getId().toString().equals("regio5")) log.info("debug"); return true; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 13435ecbad6..89e0a145671 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -152,9 +152,10 @@ public void varyingSpeed_many() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio0", 7599, 0, 2.7777777) .hasTrainState("regio0", 7674, 200, 0) - .hasTrainState("regio1", 7734, 200, 0); + .hasTrainState("regio1", 7734, 200, 0) + .hasTrainState("regio9", 23107, 200, 0); - test.debug(collector, "varyingSpeed_many"); +// test.debug(collector, "varyingSpeed_many"); test = getTestEngine("network1.xml"); @@ -167,7 +168,8 @@ public void varyingSpeed_many() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio0", 7599, 0, 2.7777777) .hasTrainState("regio0", 7674, 200, 0) - .hasTrainState("regio1", 7734, 200, 0); + .hasTrainState("regio1", 7734, 200, 0) + .hasTrainState("regio9", 23107, 200, 0); } @@ -175,13 +177,28 @@ public void varyingSpeed_many() { public void trainFollowing() { RailsimTestUtils.Holder test = getTestEngine("../integration/7_trainFollowing/trainNetwork.xml"); - RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "1-2", "20-21"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "1-2", "20-21"); test.doSimStepUntil(5000); -// test.debug(collector, "trainFollowing"); +// test.debugFiles(collector, "trainFollowing"); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio1", 1138, 1000, 0) + .hasTrainState("regio2", 1558, 1000, 0); + + test = getTestEngine("../integration/7_trainFollowing/trainNetwork.xml"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "1-2", "20-21"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "1-2", "20-21"); + + test.doStateUpdatesUntil(5000, 1); + +// test.debugFiles(collector, "trainFollowing_detailed"); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("regio1", 1138, 1000, 0) + .hasTrainState("regio2", 1558, 1000, 0); } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java index b97a6eb694d..9334c62982a 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java @@ -122,7 +122,7 @@ public void doStateUpdatesUntil(double time, double interval) { } } - public void debug(EventCollector collector, String out) { + public void debugFiles(EventCollector collector, String out) { RailsimCsvWriter.writeTrainStatesCsv( collector.events.stream().filter(ev -> ev instanceof RailsimTrainStateEvent) .map(ev -> (RailsimTrainStateEvent) ev) From 3ffbed8015d92342db2be46a32795a5c77528616 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 14 Jun 2023 17:11:46 +0200 Subject: [PATCH 063/258] trains depart when first link is unblocked --- .../contrib/railsim/qsimengine/RailsimEngine.java | 10 +++++++--- .../contrib/railsim/qsimengine/RailsimEngineTest.java | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 5bb7a5fcc6c..43d53cb17a2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -219,10 +219,14 @@ private void updateDeparture(double time, UpdateEvent event) { state.timestamp = time; state.allowedMaxSpeed = retrieveAllowedMaxSpeed(state); - state.headPosition = resources.getLink(state.headLink).length; - state.tailPosition = resources.getLink(state.headLink).length - state.train.length(); - if (blockLinkTracks(time, state)) { + RailLink firstLink = resources.getLink(state.headLink); + + state.headPosition = firstLink.length; + state.tailPosition = firstLink.length - state.train.length(); + + // reserve links and start if first one is free + if (blockLinkTracks(time, state) || resources.isBlockedBy(firstLink, state.driver)) { createEvent(new PersonEntersVehicleEvent(time, state.driver.getId(), state.driver.getVehicle().getId())); createEvent(new VehicleEntersTrafficEvent(time, state.driver.getId(), diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 89e0a145671..d86c343f9d9 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -186,7 +186,7 @@ public void trainFollowing() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 1138, 1000, 0) - .hasTrainState("regio2", 1558, 1000, 0); + .hasTrainState("regio2", 1517, 1000, 0); test = getTestEngine("../integration/7_trainFollowing/trainNetwork.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "1-2", "20-21"); @@ -198,7 +198,7 @@ public void trainFollowing() { RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 1138, 1000, 0) - .hasTrainState("regio2", 1558, 1000, 0); + .hasTrainState("regio2", 1517, 1000, 0); } From 8c6093356c03072da525ca5dd291055d43349a35 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 15 Jun 2023 16:47:48 +0200 Subject: [PATCH 064/258] created classes and methods for re-routing, involved component now use guice --- .../matsim/contrib/railsim/RailsimUtils.java | 17 +++++- .../railsim/qsimengine/FuzzyUtils.java | 6 +- .../contrib/railsim/qsimengine/RailLink.java | 22 ++++++- .../qsimengine/RailResourceManager.java | 32 +++++++++- .../qsimengine/RailsimDriverAgentFactory.java | 3 +- .../railsim/qsimengine/RailsimEngine.java | 40 ++++++++++++- .../railsim/qsimengine/RailsimQSimEngine.java | 47 ++++----------- .../railsim/qsimengine/RailsimQSimModule.java | 12 +++- .../qsimengine/RailsimTransitDriverAgent.java | 15 +++++ .../railsim/qsimengine/TrainState.java | 5 +- .../disposition/SimpleDisposition.java | 17 +++++- .../disposition/TrainDisposition.java | 12 ++++ .../qsimengine/router/TrainRouter.java | 60 ++++++++++++++++++- .../railsim/qsimengine/RailsimEngineTest.java | 8 ++- 14 files changed, 239 insertions(+), 57 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 78332055772..956d3f9f6c1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -68,7 +68,22 @@ public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGrou * Resource id or null if there is none. */ public static String getResourceId(Link link) { - return (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_RESOURCE_ID); + return (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_RESOURCE_ID); + } + + /** + * Whether this link is an entry link applicable for re routing. + */ + public static boolean isEntryLink(Link link) { + return Boolean.TRUE.equals(link.getAttributes().getAttribute("railsimEntry")); + } + + + /** + * Exit link used for re routing. + */ + public static boolean isExitLink(Link link) { + return Boolean.TRUE.equals(link.getAttributes().getAttribute("railsimExit")); } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java index a67741db085..d9ab3bc5029 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java @@ -3,13 +3,13 @@ /** * Util class for fuzzy comparisons. */ -class FuzzyUtils { +final class FuzzyUtils { + + private static final double EPSILON = 1E-5; private FuzzyUtils() { } - private final static double EPSILON = 1E-5; - /** * Returns true if two doubles are approximately equal. diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index 3f59510ba4f..a8a033e451e 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -22,6 +22,9 @@ public final class RailLink implements HasLinkId { */ private final TrackState[] state; + private final boolean isEntryLink; + private final boolean isExitLink; + /** * Reservations held for each track. */ @@ -47,6 +50,8 @@ public RailLink(Link link) { minimumHeadwayTime = RailsimUtils.getMinimumTrainHeadwayTime(link); String resourceId = RailsimUtils.getResourceId(link); resource = resourceId != null ? Id.create(resourceId, RailResource.class) : null; + isEntryLink = RailsimUtils.isEntryLink(link); + isExitLink = RailsimUtils.isExitLink(link); } @Override @@ -62,7 +67,7 @@ public Id getResourceId() { /** * Number of tracks on this link. */ - public int getNumberOfTracks(){ + public int getNumberOfTracks() { return state.length; } @@ -127,6 +132,21 @@ int releaseTrack(MobsimDriverAgent driver) { throw new AssertionError("Driver " + driver + " has not reserved the track."); } + + /** + * Entry link of a station relevant for re-routing. + */ + public boolean isEntryLink() { + return isEntryLink; + } + + /** + * Exit link of a station for re-routing. + */ + public boolean isExitLink() { + return isExitLink; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java index a2a46424206..08ab37036d4 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -2,12 +2,15 @@ import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; +import jakarta.inject.Inject; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.config.ConfigUtils; import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import org.matsim.core.mobsim.qsim.QSim; import java.util.List; import java.util.Map; @@ -20,14 +23,20 @@ public final class RailResourceManager { private final EventsManager eventsManager; + /** - * Rail links + * Rail links. */ private final Map, RailLink> links; private final Map, RailResource> resources; + @Inject + public RailResourceManager(QSim qsim) { + this(qsim.getEventsManager(), ConfigUtils.addOrGetModule(qsim.getScenario().getConfig(), RailsimConfigGroup.class), qsim.getScenario().getNetwork()); + } + /** * Construct resources from network. */ @@ -63,6 +72,7 @@ public RailLink getLink(Id id) { * Return the resource for a given id. */ public RailResource getResource(Id id) { + if (id == null) return null; return resources.get(id); } @@ -110,7 +120,7 @@ public boolean tryBlockTrack(double time, MobsimDriverAgent driver, RailLink lin Id resourceId = link.getResourceId(); if (resourceId != null) { - RailResource resource = getResource(resourceId); + RailResource resource = getResource(resourceId); // resource is required if (!tryBlockResource(resource, driver)) { @@ -129,6 +139,24 @@ public boolean tryBlockTrack(double time, MobsimDriverAgent driver, RailLink lin return false; } + /** + * Checks whether a link or underlying resource has remaining capacity. + */ + public boolean hasCapacity(Id link) { + + RailLink l = getLink(link); + + if (!l.hasFreeTrack()) + return false; + + RailResource res = getResource(l.getResourceId()); + if (res != null) { + return res.hasCapacity(); + } + + return true; + } + /** * Whether a driver already reserved a link or would be able to reserve it. */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java index 4d29e93ec1f..c04dee6ffc1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java @@ -32,8 +32,7 @@ public AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf, InternalInt String mode = umlauf.getUmlaufStuecke().get(0).getRoute().getTransportMode(); if (this.modes.contains(mode)) { - // TODO: Can be a specific driver agent later - return new TransitDriverAgentImpl(umlauf, mode, transitStopAgentTracker, internalInterface); + return new RailsimTransitDriverAgent(umlauf, mode, transitStopAgentTracker, internalInterface); } return new TransitDriverAgentImpl(umlauf, TransportMode.car, transitStopAgentTracker, internalInterface); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 43d53cb17a2..316fa66e57a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -4,6 +4,7 @@ import ch.sbb.matsim.contrib.railsim.events.RailsimTrainLeavesLinkEvent; import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.SimpleDisposition; import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.TrainDisposition; +import ch.sbb.matsim.contrib.railsim.qsimengine.router.TrainRouter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; @@ -38,14 +39,13 @@ final class RailsimEngine implements Steppable { private final Queue updateQueue = new PriorityQueue<>(); private final RailResourceManager resources; - private final TrainDisposition disposition; - public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, RailResourceManager resources) { + public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, RailResourceManager resources, TrainDisposition disposition) { this.eventsManager = eventsManager; this.config = config; this.resources = resources; - this.disposition = new SimpleDisposition(resources); + this.disposition = disposition; } @Override @@ -268,6 +268,40 @@ private boolean blockLinkTracks(double time, TrainState state) { if (links.isEmpty()) return true; + Optional entry = links.stream().filter(l -> l.isEntryLink() && !l.isBlockedBy(state.driver)).findFirst(); + if (state.pt != null && entry.isPresent()) { + + int start = -1; + int end = -1; + RailLink exit = null; + + for (int i = state.routeIdx; i < state.route.size(); i++) { + RailLink l = state.route.get(i); + + if (l == entry.get()) + start = i; + + if (start > -1 && l.isExitLink()) { + exit = l; + end = i; + break; + } + } + + // there might be no exit link if this is the end of the route + // network could be wrong as well, but hard to verify + if (exit != null) { + List detour = disposition.requestRoute(time, state.pt, entry.get(), exit); + + // check if this route is different + if (detour != null && !state.route.subList(start + 1, end).equals(detour)) { + // TODO: apply the detour + // TODO: throw event + + } + } + } + List blocked = disposition.blockRailSegment(time, state.driver, links); // Only continue successfully if all requested link have been blocked diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index 33488e1562d..5ddb6dca27c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -20,6 +20,7 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.TrainDisposition; import com.google.inject.Inject; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; @@ -48,6 +49,8 @@ public class RailsimQSimEngine implements DepartureHandler, MobsimEngine { private final QSim qsim; private final RailsimConfigGroup config; + private final RailResourceManager res; + private final TrainDisposition disposition; private final Set modes; private final TransitStopAgentTracker agentTracker; private InternalInterface internalInterface; @@ -55,9 +58,12 @@ public class RailsimQSimEngine implements DepartureHandler, MobsimEngine { private RailsimEngine engine; @Inject - public RailsimQSimEngine(QSim qsim, TransitStopAgentTracker agentTracker) { + public RailsimQSimEngine(QSim qsim, RailResourceManager res, TrainDisposition disposition, + TransitStopAgentTracker agentTracker) { this.qsim = qsim; this.config = ConfigUtils.addOrGetModule(qsim.getScenario().getConfig(), RailsimConfigGroup.class); + this.res = res; + this.disposition = disposition; this.modes = config.getRailNetworkModes(); this.agentTracker = agentTracker; } @@ -69,8 +75,7 @@ public void setInternalInterface(InternalInterface internalInterface) { @Override public void onPrepareSim() { - engine = new RailsimEngine(qsim.getEventsManager(), config, - new RailResourceManager(qsim.getEventsManager(), config, qsim.getScenario().getNetwork())); + engine = new RailsimEngine(qsim.getEventsManager(), config, res, disposition); } @Override @@ -117,28 +122,8 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id linkId) { return engine.handleDeparture(now, driver, linkId, networkRoute); } - // Get inspiration from SBBTransitQSimEngine on what methods to overwrite - - // TODO: Questions - // Is the rail engine a passenger engine in itself ? - // Should maybe be lower level like qsim ? - - /* some implementation notes and ideas: - - data structure to store a track-state per link (or multiple track-state if a link depicts multiple parallel tracks) - - track-state can be `free`, `blocked` (only a single train can be in a blocked track), or `reserved` (multiple trains can reserve the same track) - - data structure to store position and other data of trains - - head position, given as meters from fromNode on a link - - current route (ordered list of links) - - length of the train - - current speed of the train - - current acceleration/deceleration of the train - - additional attributes as required, e.g. required stopping distance ("bremsweg", emergency braking distance, maxDeceleration) given the current speed - - in each simStep (may be optimized later to a lower interval), the position of each train is updated - - each train tries to block as many links in front of the train to cover the stopping distance - - if not enough links can be blocked, the train must decelerate accordingly (normal braking distance, deceleration) - - a train can only accelerate, if the complete train is on links with the higher allowed speed - (e.g. train cannot accelerate if only the engine is on a faster link, but the rest of the train are still on links with lower freespeed) + /* For visualization purposes, the following output should be produced: - CSV containing time-dependent link-attributes depicting the track-state (needs discussion what we output when a link contains multiple tracks) - optionally include information which trains (vehicleId) blocked or reserved a track? @@ -155,18 +140,8 @@ instead of XYT (see above), we could think about if we could create linkEnter/li This is where the `reserved` track-state might come into play. - TODO Implementation steps: - 0. RailsimConfigGroup - 1. RailsimQSimEngine extracts all "rail"-links (depending on config) and builds a data structure to store track-states - 2. RailsimQSimEngine handles all "rail"-vehicles (also depending on config) and moves the trains each second - First implementation can be very basic, e.g. constant speed per link according to freespeed, no checks if track is free - 3. Handle passengers at stops, see SBBTransitQSimEngine how to do this - 4. Each train blocks the links it currently occupies, plus 1 link in front of it if possible - 5. A train can only move to the next link it that link is blocked by that train - 6. Each train tries to block as many link in front of it along the route as it needs for the stopping distance - 7. Trains accelerate smoothly when entering links with a higher freespeed - 8. Trains decelerate smoothly before entering links with a lower freespeed - 9. Trains decelerate smoothly if they cannot block enough links in front of them + TODO: + 9. Re routing 10. Deadlock Prevention */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java index 79e16fd68ac..c3fe61f4939 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java @@ -19,6 +19,9 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.SimpleDisposition; +import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.TrainDisposition; +import ch.sbb.matsim.contrib.railsim.qsimengine.router.TrainRouter; import com.google.inject.multibindings.OptionalBinder; import org.matsim.core.mobsim.qsim.AbstractQSimModule; import org.matsim.core.mobsim.qsim.components.QSimComponentsConfig; @@ -39,9 +42,16 @@ public void configure(QSimComponentsConfig components) { @Override protected void configureQSim() { bind(RailsimQSimEngine.class).asEagerSingleton(); + + bind(TrainRouter.class).asEagerSingleton(); + bind(RailResourceManager.class).asEagerSingleton(); + + // This Interface might be replaced with other implementations + bind(TrainDisposition.class).to(SimpleDisposition.class).asEagerSingleton(); + addQSimComponentBinding(COMPONENT_NAME).to(RailsimQSimEngine.class); OptionalBinder.newOptionalBinder(binder(), TransitDriverAgentFactory.class) - .setBinding().to( RailsimDriverAgentFactory.class ); + .setBinding().to(RailsimDriverAgentFactory.class); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java new file mode 100644 index 00000000000..1a46122c9ba --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java @@ -0,0 +1,15 @@ +package ch.sbb.matsim.contrib.railsim.qsimengine; + +import org.matsim.core.mobsim.qsim.InternalInterface; +import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentImpl; +import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; +import org.matsim.pt.Umlauf; + +/** + * Railsim specific transit driver that can be re-routed. + */ +public final class RailsimTransitDriverAgent extends TransitDriverAgentImpl { + public RailsimTransitDriverAgent(Umlauf umlauf, String transportMode, TransitStopAgentTracker thisAgentTracker, InternalInterface internalInterface) { + super(umlauf, transportMode, thisAgentTracker, internalInterface); + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index e5c604c6f73..6ccdb5f3df4 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -9,6 +9,7 @@ import javax.annotation.Nullable; import java.util.List; +import java.util.Set; /** * Stores the mutable current state of a train. @@ -24,7 +25,7 @@ final class TrainState { * Transit agent, if this is a pt transit. */ @Nullable - final TransitDriverAgent pt; + final RailsimTransitDriverAgent pt; /** * Next transit stop. @@ -101,7 +102,7 @@ final class TrainState { TrainState(MobsimDriverAgent driver, TrainInfo train, double timestamp, @Nullable Id linkId, List route) { this.driver = driver; - this.pt = driver instanceof TransitDriverAgent ptDriver ? ptDriver : null; + this.pt = driver instanceof RailsimTransitDriverAgent ptDriver ? ptDriver : null; this.nextStop = pt != null ? pt.getNextTransitStop() : null; this.train = train; this.route = route; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java index 3738196d986..8cd9c9a6a50 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java @@ -1,11 +1,13 @@ package ch.sbb.matsim.contrib.railsim.qsimengine.disposition; import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; -import ch.sbb.matsim.contrib.railsim.qsimengine.RailResource; import ch.sbb.matsim.contrib.railsim.qsimengine.RailResourceManager; -import org.matsim.api.core.v01.Id; +import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimTransitDriverAgent; +import ch.sbb.matsim.contrib.railsim.qsimengine.router.TrainRouter; +import jakarta.inject.Inject; import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; @@ -15,9 +17,12 @@ public class SimpleDisposition implements TrainDisposition { private final RailResourceManager resources; + private final TrainRouter router; - public SimpleDisposition(RailResourceManager resources) { + @Inject + public SimpleDisposition(RailResourceManager resources, TrainRouter router) { this.resources = resources; + this.router = router; } @Override @@ -25,6 +30,12 @@ public void onDeparture(double time, MobsimDriverAgent driver, List ro // Nothing to do. } + @Nullable + @Override + public List requestRoute(double time, RailsimTransitDriverAgent driver, RailLink entry, RailLink exit) { + return router.calcRoute(entry, exit); + } + @Override public List blockRailSegment(double time, MobsimDriverAgent driver, List segment) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java index ea804ae83f4..fb065f501d7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java @@ -1,8 +1,10 @@ package ch.sbb.matsim.contrib.railsim.qsimengine.disposition; import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; +import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimTransitDriverAgent; import org.matsim.core.mobsim.framework.MobsimDriverAgent; +import javax.annotation.Nullable; import java.util.List; /** @@ -16,6 +18,16 @@ public interface TrainDisposition { */ void onDeparture(double time, MobsimDriverAgent driver, List route); + /** + * Called by the driver when an entry link is within stop distance. + * + * @return the route change, or null if nothing should be changed + */ + @Nullable + default List requestRoute(double time, RailsimTransitDriverAgent driver, RailLink entry, RailLink exit) { + return null; + } + /** * Train is reaching the given links and is trying to block them. * diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java index 831b2ee3487..6388c51d09d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java @@ -1,10 +1,68 @@ package ch.sbb.matsim.contrib.railsim.qsimengine.router; -public interface TrainRouter { +import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; +import ch.sbb.matsim.contrib.railsim.qsimengine.RailResourceManager; +import jakarta.inject.Inject; +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.api.core.v01.population.Person; +import org.matsim.core.mobsim.qsim.QSim; +import org.matsim.core.router.FastDijkstraFactory; +import org.matsim.core.router.util.LeastCostPathCalculator; +import org.matsim.core.router.util.TravelDisutility; +import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; +import org.matsim.vehicles.Vehicle; + +import java.util.List; + +public final class TrainRouter { // TODO Placeholder for a routing interface // Train stations or other areas are modeled as block with entry and exit links // within these blocks the train can be rerouted depending on track availability + private final Network network; + private final RailResourceManager resources; + private final LeastCostPathCalculator lpc; + + @Inject + public TrainRouter(QSim qsim, RailResourceManager resources) { + this(qsim.getScenario().getNetwork(), resources); + } + + public TrainRouter(Network network, RailResourceManager resources) { + this.network = network; + this.resources = resources; + + // TODO: filter rail network + + this.lpc = new FastDijkstraFactory(false).createPathCalculator(network, new DisUtility(), new FreeSpeedTravelTime()); + } + + /** + * Calculate the shortest path between two links. + */ + public List calcRoute(RailLink from, RailLink to) { + + Node fromNode = network.getLinks().get(from.getLinkId()).getToNode(); + Node toNode = network.getLinks().get(to.getLinkId()).getFromNode(); + + LeastCostPathCalculator.Path path = lpc.calcLeastCostPath(fromNode, toNode, 0, null, null); + + return path.links.stream().map(l -> resources.getLink(l.getId())).toList(); + } + + private final class DisUtility implements TravelDisutility { + @Override + public double getLinkTravelDisutility(Link link, double time, Person person, Vehicle vehicle) { + return resources.hasCapacity(link.getId()) ? 0 : 1; + } + + @Override + public double getLinkMinimumTravelDisutility(Link link) { + return 0; + } + } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index d86c343f9d9..dbd2ff7b794 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -1,6 +1,8 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.SimpleDisposition; +import ch.sbb.matsim.contrib.railsim.qsimengine.router.TrainRouter; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -36,8 +38,10 @@ private RailsimTestUtils.Holder getTestEngine(String network) { collector.clear(); - return new RailsimTestUtils.Holder(new RailsimEngine( - eventsManager, config, new RailResourceManager(eventsManager, config, net)), net); + RailResourceManager res = new RailResourceManager(eventsManager, config, net); + TrainRouter router = new TrainRouter(net, res); + + return new RailsimTestUtils.Holder(new RailsimEngine(eventsManager, config, res, new SimpleDisposition(res, router)), net); } @Test From e1bb6aee73408fdda8c0e31599440d895a136fb4 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 16 Jun 2023 11:09:28 +0200 Subject: [PATCH 065/258] added check if train is at route end, some work for re-routing --- contribs/railsim/docs/events-specification.md | 6 +- .../railsim/events/RailsimDetourEvent.java | 55 +++++++++++++++++++ .../railsim/qsimengine/RailsimEngine.java | 33 ++++++++--- .../qsimengine/RailsimTransitDriverAgent.java | 25 +++++++++ .../disposition/SimpleDisposition.java | 11 +++- .../disposition/TrainDisposition.java | 4 +- 6 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index 2233840d21e..44c6851a477 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -30,4 +30,8 @@ compatibility with existing analysis and visualization tools. ### railsimTrainStateEvent This event is emitted every time there is a position update for a train. -This event contains detailed information about the trains position on a single link. \ No newline at end of file +This event contains detailed information about the trains position on a single link. + +### railsimDetourEvent + +This event is emitted when a train is re-routed and contains parts of the routes that have changed. \ No newline at end of file diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java new file mode 100644 index 00000000000..b022e3d57de --- /dev/null +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java @@ -0,0 +1,55 @@ +package ch.sbb.matsim.contrib.railsim.events; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasVehicleId; +import org.matsim.api.core.v01.network.Link; +import org.matsim.vehicles.Vehicle; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Event that occurs when a train was re-routed. + */ +public class RailsimDetourEvent extends Event implements HasVehicleId { + + public static final String EVENT_TYPE = "railsimDetourEvent"; + + private final Id vehicleId; + private final Id entry; + private final Id exit; + private final List> detour; + + public RailsimDetourEvent(double time, Id vehicleId, Id entry, Id exit, List> detour) { + super(time); + this.vehicleId = vehicleId; + this.entry = entry; + this.exit = exit; + this.detour = detour; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getVehicleId() { + return vehicleId; + } + + + @Override + public Map getAttributes() { + Map attributes = super.getAttributes(); + + attributes.put(HasVehicleId.ATTRIBUTE_VEHICLE, vehicleId.toString()); + attributes.put("entry", entry.toString()); + attributes.put("exit", exit.toString()); + attributes.put("detour", detour.stream().map(Object::toString).collect(Collectors.joining(","))); + + return attributes; + } +} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 316fa66e57a..a79828c88ee 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -1,10 +1,9 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.events.RailsimDetourEvent; import ch.sbb.matsim.contrib.railsim.events.RailsimTrainLeavesLinkEvent; -import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.SimpleDisposition; import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.TrainDisposition; -import ch.sbb.matsim.contrib.railsim.qsimengine.router.TrainRouter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; @@ -173,12 +172,13 @@ private void checkTrackReservation(double time, UpdateEvent event) { TrainState state = event.state; - RailLink nextLink = state.route.get(state.routeIdx); + // train might be at the end of route already + RailLink nextLink = state.isRouteAtEnd() ? null : state.route.get(state.routeIdx); boolean allBlocked = blockLinkTracks(time, state); // Driver can advance if the next link is already free - if (allBlocked || nextLink.isBlockedBy(state.driver)) { + if (allBlocked || (nextLink != null && nextLink.isBlockedBy(state.driver))) { if (allBlocked) event.checkReservation = -1; @@ -291,13 +291,26 @@ private boolean blockLinkTracks(double time, TrainState state) { // there might be no exit link if this is the end of the route // network could be wrong as well, but hard to verify if (exit != null) { - List detour = disposition.requestRoute(time, state.pt, entry.get(), exit); + List detour = disposition.requestRoute(time, state.pt, links, entry.get(), exit); // check if this route is different - if (detour != null && !state.route.subList(start + 1, end).equals(detour)) { - // TODO: apply the detour - // TODO: throw event + List subRoute = state.route.subList(start + 1, end); + if (detour != null && !subRoute.equals(detour)) { + + if (state.pt.addDetour(state.routeIdx, start, end, detour)) { + subRoute.clear(); + subRoute.addAll(detour); + + createEvent(new RailsimDetourEvent( + time, state.driver.getVehicle().getId(), + entry.get().getLinkId(), exit.getLinkId(), + detour.stream().map(RailLink::getLinkId).toList() + )); + + // Block links again using the updated route + links = RailsimCalc.calcLinksToBlock(state, resources.getLink(state.headLink)); + } } } } @@ -476,7 +489,9 @@ private void updatePosition(double time, UpdateEvent event) { state.timestamp = time; - createEvent(state.asEvent(time)); + // Only emit events on certain occasions + if (event.type == UpdateEvent.Type.ENTER_LINK || event.type == UpdateEvent.Type.LEAVE_LINK || event.type == UpdateEvent.Type.POSITION || event.type == UpdateEvent.Type.SPEED_CHANGE) + createEvent(state.asEvent(time)); } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java index 1a46122c9ba..4f4bf03254b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java @@ -1,15 +1,40 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.matsim.core.mobsim.qsim.InternalInterface; import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentImpl; import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; import org.matsim.pt.Umlauf; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +import java.util.List; /** * Railsim specific transit driver that can be re-routed. */ public final class RailsimTransitDriverAgent extends TransitDriverAgentImpl { + + private static final Logger log = LogManager.getLogger(RailsimTransitDriverAgent.class); + public RailsimTransitDriverAgent(Umlauf umlauf, String transportMode, TransitStopAgentTracker thisAgentTracker, InternalInterface internalInterface) { super(umlauf, transportMode, thisAgentTracker, internalInterface); } + + /** + * Add a detour to this driver schedule. + * @param currentIdx current route index + * @param start start of detour + * @param end end of the detour + * @return whether this detour is accepted and feasible. + */ + public boolean addDetour(int currentIdx, int start, int end, List detour) { + + TransitStopFacility nextStop = getNextTransitStop(); + + + // TODO: + + return true; + } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java index 8cd9c9a6a50..ef296159711 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java @@ -32,8 +32,15 @@ public void onDeparture(double time, MobsimDriverAgent driver, List ro @Nullable @Override - public List requestRoute(double time, RailsimTransitDriverAgent driver, RailLink entry, RailLink exit) { - return router.calcRoute(entry, exit); + public List requestRoute(double time, RailsimTransitDriverAgent driver, List segment, RailLink entry, RailLink exit) { + + // Only re-routes if the link segment is occupied + for (RailLink link : segment) { + if (!resources.isBlockedBy(link, driver) && !resources.hasCapacity(link.getLinkId())) + return router.calcRoute(entry, exit); + } + + return null; } @Override diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java index fb065f501d7..010eba9187a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java @@ -21,10 +21,12 @@ public interface TrainDisposition { /** * Called by the driver when an entry link is within stop distance. * + * @param segment the link segment the driver tried to block * @return the route change, or null if nothing should be changed */ @Nullable - default List requestRoute(double time, RailsimTransitDriverAgent driver, RailLink entry, RailLink exit) { + default List requestRoute(double time, RailsimTransitDriverAgent driver, List segment, + RailLink entry, RailLink exit) { return null; } From f05c120d2109745fc730f3142b2c0e0214356b87 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 16 Jun 2023 16:50:21 +0200 Subject: [PATCH 066/258] implemented detour pt stop handling, added assertions to the test --- .../railsim/events/RailsimDetourEvent.java | 8 +- .../railsim/qsimengine/RailsimCalc.java | 9 +++ .../qsimengine/RailsimDriverAgentFactory.java | 17 ++++- .../railsim/qsimengine/RailsimEngine.java | 48 +++++++----- .../qsimengine/RailsimTransitDriverAgent.java | 74 +++++++++++++++++-- .../disposition/TrainDisposition.java | 2 +- .../integration/RailsimIntegrationTest.java | 19 ++++- .../qsim/pt/AbstractTransitDriverAgent.java | 27 +++++-- 8 files changed, 165 insertions(+), 39 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java index b022e3d57de..3db37d3ffa0 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java @@ -4,10 +4,12 @@ import org.matsim.api.core.v01.events.Event; import org.matsim.api.core.v01.events.HasVehicleId; import org.matsim.api.core.v01.network.Link; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; import org.matsim.vehicles.Vehicle; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -21,13 +23,16 @@ public class RailsimDetourEvent extends Event implements HasVehicleId { private final Id entry; private final Id exit; private final List> detour; + private final Id newStop; - public RailsimDetourEvent(double time, Id vehicleId, Id entry, Id exit, List> detour) { + public RailsimDetourEvent(double time, Id vehicleId, Id entry, Id exit, List> detour, + Id newStop) { super(time); this.vehicleId = vehicleId; this.entry = entry; this.exit = exit; this.detour = detour; + this.newStop = newStop; } @Override @@ -49,6 +54,7 @@ public Map getAttributes() { attributes.put("entry", entry.toString()); attributes.put("exit", exit.toString()); attributes.put("detour", detour.stream().map(Object::toString).collect(Collectors.joining(","))); + attributes.put("newStop", Objects.toString(newStop)); return attributes; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 0b7ae04621d..46c1cded16a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -218,6 +218,15 @@ public static List calcLinksToBlock(TrainState state, RailLink current return result; } + /** + * Whether re-routing should be tried. + * + * @param upcoming the upcoming links the train tried to block. + */ + public static boolean considerReRouting(List upcoming, RailLink currentLink) { + return currentLink.isEntryLink() || upcoming.stream().anyMatch(RailLink::isEntryLink); + } + /** * Maximum speed of the next upcoming links. */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java index c04dee6ffc1..b8b29d25614 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java @@ -2,6 +2,8 @@ import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import com.google.inject.Inject; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; @@ -11,8 +13,13 @@ import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentImpl; import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; import org.matsim.pt.Umlauf; +import org.matsim.pt.transitSchedule.api.TransitStopArea; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * Factory to create specific drivers for the rail engine. @@ -21,9 +28,15 @@ public class RailsimDriverAgentFactory implements TransitDriverAgentFactory { private final Set modes; + private final Map, List> stopAreas; + @Inject - public RailsimDriverAgentFactory(Config config) { + public RailsimDriverAgentFactory(Config config, Scenario scenario) { this.modes = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class).getRailNetworkModes(); + + this.stopAreas = scenario.getTransitSchedule().getFacilities().values().stream() + .filter(t -> t.getStopAreaId() != null) + .collect(Collectors.groupingBy(TransitStopFacility::getStopAreaId, Collectors.toList())); } @Override @@ -32,7 +45,7 @@ public AbstractTransitDriverAgent createTransitDriver(Umlauf umlauf, InternalInt String mode = umlauf.getUmlaufStuecke().get(0).getRoute().getTransportMode(); if (this.modes.contains(mode)) { - return new RailsimTransitDriverAgent(umlauf, mode, transitStopAgentTracker, internalInterface); + return new RailsimTransitDriverAgent(stopAreas, umlauf, mode, transitStopAgentTracker, internalInterface); } return new TransitDriverAgentImpl(umlauf, TransportMode.car, transitStopAgentTracker, internalInterface); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index a79828c88ee..bf26df18130 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -13,6 +13,7 @@ import org.matsim.core.mobsim.framework.MobsimDriverAgent; import org.matsim.core.mobsim.framework.Steppable; import org.matsim.core.population.routes.NetworkRoute; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; import org.matsim.vehicles.VehicleType; import java.util.*; @@ -268,20 +269,25 @@ private boolean blockLinkTracks(double time, TrainState state) { if (links.isEmpty()) return true; - Optional entry = links.stream().filter(l -> l.isEntryLink() && !l.isBlockedBy(state.driver)).findFirst(); - if (state.pt != null && entry.isPresent()) { + if (state.pt != null && RailsimCalc.considerReRouting(links, resources.getLink(state.headLink))) { int start = -1; int end = -1; + RailLink entry = null; RailLink exit = null; - for (int i = state.routeIdx; i < state.route.size(); i++) { + for (int i = Math.max(0, state.routeIdx - 1); i < state.route.size(); i++) { RailLink l = state.route.get(i); - if (l == entry.get()) + if (l.isEntryLink()) { + entry = l; start = i; - - if (start > -1 && l.isExitLink()) { + } + else if (start > -1 && l.isBlockedBy(state.driver)) { + // check if any link beyond entry is already blocked + // if that is the case re-route is not possible anymore + break; + } else if (start > -1 && l.isExitLink()) { exit = l; end = i; break; @@ -289,28 +295,36 @@ private boolean blockLinkTracks(double time, TrainState state) { } // there might be no exit link if this is the end of the route + // exit will be set to null if re-route is too late // network could be wrong as well, but hard to verify if (exit != null) { - List detour = disposition.requestRoute(time, state.pt, links, entry.get(), exit); // check if this route is different List subRoute = state.route.subList(start + 1, end); + List detour = disposition.requestRoute(time, state.pt, subRoute, entry, exit); + if (detour != null && !subRoute.equals(detour)) { - if (state.pt.addDetour(state.routeIdx, start, end, detour)) { - subRoute.clear(); - subRoute.addAll(detour); + TransitStopFacility newStop = state.pt.addDetour(subRoute, detour); - createEvent(new RailsimDetourEvent( - time, state.driver.getVehicle().getId(), - entry.get().getLinkId(), exit.getLinkId(), - detour.stream().map(RailLink::getLinkId).toList() - )); + subRoute.clear(); + subRoute.addAll(detour); - // Block links again using the updated route - links = RailsimCalc.calcLinksToBlock(state, resources.getLink(state.headLink)); + if (newStop != null) { + state.nextStop = newStop; } + + createEvent(new RailsimDetourEvent( + time, state.driver.getVehicle().getId(), + entry.getLinkId(), exit.getLinkId(), + detour.stream().map(RailLink::getLinkId).toList(), + newStop != null ? newStop.getId() : null + )); + + // Block links again using the updated route + links = RailsimCalc.calcLinksToBlock(state, resources.getLink(state.headLink)); + } } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java index 4f4bf03254b..f4352b5b031 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java @@ -2,13 +2,16 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.Id; import org.matsim.core.mobsim.qsim.InternalInterface; import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentImpl; import org.matsim.core.mobsim.qsim.pt.TransitStopAgentTracker; import org.matsim.pt.Umlauf; +import org.matsim.pt.transitSchedule.api.TransitStopArea; import org.matsim.pt.transitSchedule.api.TransitStopFacility; import java.util.List; +import java.util.Map; /** * Railsim specific transit driver that can be re-routed. @@ -17,24 +20,79 @@ public final class RailsimTransitDriverAgent extends TransitDriverAgentImpl { private static final Logger log = LogManager.getLogger(RailsimTransitDriverAgent.class); - public RailsimTransitDriverAgent(Umlauf umlauf, String transportMode, TransitStopAgentTracker thisAgentTracker, InternalInterface internalInterface) { + /** + * Contains the original stop if it was overwritten by a detour. + */ + private TransitStopFacility overwrittenStop; + + private final Map, List> stopAreas; + + public RailsimTransitDriverAgent(Map, List> stopAreas, Umlauf umlauf, String transportMode, TransitStopAgentTracker thisAgentTracker, InternalInterface internalInterface) { super(umlauf, transportMode, thisAgentTracker, internalInterface); + this.stopAreas = stopAreas; } /** * Add a detour to this driver schedule. - * @param currentIdx current route index - * @param start start of detour - * @param end end of the detour - * @return whether this detour is accepted and feasible. + * + * @return next transit stop, if it needs to be changed. */ - public boolean addDetour(int currentIdx, int start, int end, List detour) { + public TransitStopFacility addDetour(List original, List detour) { TransitStopFacility nextStop = getNextTransitStop(); + // Adjust the link index so that it fits to the new size + // the original route inside this agent is not updated because it is currently not necessary + // after the detour the link index should be consistent again + setNextLinkIndex(getNextLinkIndex() + (original.size() - detour.size())); + + if (nextStop != null) { + + Id areaId = nextStop.getStopAreaId(); + + boolean adjust = false; + for (RailLink link : original) { + if (nextStop.getLinkId().equals(link.getLinkId())) { + adjust = true; + break; + } + } + + // pt stop needs to be remapped + if (adjust) { + + List inArea = stopAreas.get(areaId); + + for (TransitStopFacility stop : inArea) { + for (RailLink d : detour) { + + if (stop.getLinkId().equals(d.getLinkId())) { + this.overwrittenStop = nextStop; + return stop; + } + } + } + + log.warn("Could not re-route vehicle {} to a replacement transit stop", getVehicle().getId()); + } + } + + return null; + } + + @Override + public double handleTransitStop(TransitStopFacility stop, double now) { + + // This function will call the API with the original stop as if no reroute has happened - // TODO: + // use the original stop exactly one time + if (overwrittenStop != null) { + stop = overwrittenStop; + double t = super.handleTransitStop(stop, now); + overwrittenStop = null; + return t; + } - return true; + return super.handleTransitStop(stop, now); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java index 010eba9187a..fc175fbb890 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java @@ -21,7 +21,7 @@ public interface TrainDisposition { /** * Called by the driver when an entry link is within stop distance. * - * @param segment the link segment the driver tried to block + * @param segment the original link segment between entry and exit * @return the route change, or null if nothing should be changed */ @Nullable diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 69a766999a5..3a9142b7de4 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -141,12 +141,16 @@ private void assertTrainState(double time, double speed, double targetSpeed, dou } } + private List filterTrainEvents(EventsCollector collector, String train) { + return collector.getEvents().stream().filter(event -> event instanceof RailsimTrainStateEvent).map(event -> (RailsimTrainStateEvent) event).filter(event -> event.getVehicleId().toString().equals(train)).toList(); + } + @Test public void test0_varyingCapacities() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "0_varyingCapacities")); // print events of train1 for debugging - List train1events = collector.getEvents().stream().filter(event -> event instanceof RailsimTrainStateEvent).map(event -> (RailsimTrainStateEvent) event).filter(event -> event.getVehicleId().toString().equals("train1")).toList(); + List train1events = filterTrainEvents(collector, "train1"); train1events.forEach(System.out::println); // calculation of expected time for train1: acceleration = 0.5, length = 100 @@ -242,7 +246,7 @@ public void test0_varyingCapacities() { double cruiseTime10 = timeForDistance(cruiseDistance10, stationSpeed); currentTime += cruiseTime10; - assertTrainState(currentTime, stationSpeed, 0, -acceleration, cruiseDistance10, train1events); + assertTrainState(currentTime, stationSpeed, 0, -acceleration, cruiseDistance10, train1events); // final train arrival currentTime += decTime11; @@ -339,6 +343,17 @@ public void test14_mesoStations() { @Test public void test_station_rerouting() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "station_rerouting")); + + collector.getEvents().forEach(System.out::println); + + // Checks end times + assertTrainState(30854, 0, 0, 0, 400, filterTrainEvents(collector, "train1")); + // 1min later + assertTrainState(30914, 0, 0, 0, 400, filterTrainEvents(collector, "train2")); + + // These arrive closer together, because both waited + assertTrainState(30974, 0, 0, 0, 400, filterTrainEvents(collector, "train3")); + assertTrainState(30982, 0, 0, 0, 400, filterTrainEvents(collector, "train4")); } } diff --git a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/AbstractTransitDriverAgent.java b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/AbstractTransitDriverAgent.java index 6555514f4c1..d7dd5939154 100644 --- a/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/AbstractTransitDriverAgent.java +++ b/matsim/src/main/java/org/matsim/core/mobsim/qsim/pt/AbstractTransitDriverAgent.java @@ -65,7 +65,7 @@ public abstract class AbstractTransitDriverAgent implements TransitDriverAgent, private final PassengerAccessEgressImpl accessEgress; - /* package */ MobsimAgent.State state = MobsimAgent.State.ACTIVITY ; + /* package */ MobsimAgent.State state = MobsimAgent.State.ACTIVITY ; // yy not so great: implicit instantiation at activity. kai, nov'11 @Override public final MobsimAgent.State getState() { @@ -147,6 +147,17 @@ public final void notifyMoveOverNode(Id nextLinkId) { this.nextLinkIndex++; } + protected final int getNextLinkIndex() { + return nextLinkIndex; + } + + /** + * Overwrite the current link index. May be used by implementing classes, but should be handled with care. + */ + protected final void setNextLinkIndex(int idx) { + nextLinkIndex = idx; + } + @Override public final TransitStopFacility getNextTransitStop() { if (this.nextStop == null) { @@ -165,7 +176,7 @@ public double handleTransitStop(final TransitStopFacility stop, final double now TransitRoute route = this.getTransitRoute(); List stopsToCome = route.getStops().subList(stopIterator.nextIndex(), route.getStops().size()); /* - * If there are passengers leaving or entering, the stop time must be not greater than 1.0 in order to let them (de-)board every second. + * If there are passengers leaving or entering, the stop time must be not greater than 1.0 in order to let them (de-)board every second. * If a stopTime greater than 1.0 is used, this method is not necessarily triggered by the qsim, so (de-)boarding will not happen. Dg, 10-2012 */ double stopTime = this.accessEgress.calculateStopTimeAndTriggerBoarding(getTransitRoute(), getTransitLine(), this.vehicle, stop, stopsToCome, now); @@ -202,7 +213,7 @@ public void notifyArrivalOnLinkByNonNetworkMode(final Id linkId) { /**Design comments:

*/ @@ -276,7 +287,7 @@ private void depart(final double now) { private void assertAllStopsServed() { if (this.nextStop != null) { - RuntimeException e = new RuntimeException("Transit vehicle is not yet at last stop! vehicle-id = " + RuntimeException e = new RuntimeException("Transit vehicle is not yet at last stop! vehicle-id = " + this.vehicle.getVehicle().getId() + "; next-stop = " + this.nextStop.getStopFacility().getId()); log.error(e); throw e; @@ -377,17 +388,17 @@ public void setStartLinkId(final Id linkId) { public void setRouteDescription(String routeDescription) { throw new UnsupportedOperationException("read only route."); } - + @Override public String getRouteDescription() { return this.delegate.getRouteDescription(); } - + @Override public String getRouteType() { return this.delegate.getRouteType(); } - + @Override @Deprecated public double getDistance() { @@ -437,4 +448,4 @@ public NetworkRouteWrapper clone() { } -} \ No newline at end of file +} From 1799c6b31bca4a2f94debf6753aa13e1e635887d Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 21 Jun 2023 11:33:38 +0200 Subject: [PATCH 067/258] cleaned todos, separated unblock links to support headway time of links --- .../railsim/config/RailsimConfigGroup.java | 6 ----- .../railsim/qsimengine/RailsimEngine.java | 25 ++++++++++++++++--- .../railsim/qsimengine/UpdateEvent.java | 16 +++++++++++- .../qsimengine/router/TrainRouter.java | 8 +++--- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index 5d6e4c64cb0..060deba4b0b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -61,10 +61,4 @@ protected void checkConsistency(Config config) { } } } - - // TODO: add config parameters - // - "railVehicleModes", default "rail"(or "train"?) - // - "linkEventsInterval", default "10", the iteration-interval when link-events should be generated by railsim - // - "visualizationInterval", default "10", the iteration-interval when various csv-files should be generated used to visualize the results in Via - } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index bf26df18130..eb4b23967fa 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -133,6 +133,11 @@ private void updateState(double time, UpdateEvent event) { case LEAVE_LINK -> leaveLink(time, event); case BLOCK_TRACK -> blockTrack(time, event); case WAIT_FOR_RESERVATION -> checkTrackReservation(time, event); + case UNBLOCK_LINK -> { + unblockTrack(time, event.state, event.unblockLink); + // event will be removed + event.type = UpdateEvent.Type.IDLE; + } default -> throw new IllegalStateException("Unhandled update type " + event.type); } } @@ -282,8 +287,7 @@ private boolean blockLinkTracks(double time, TrainState state) { if (l.isEntryLink()) { entry = l; start = i; - } - else if (start > -1 && l.isBlockedBy(state.driver)) { + } else if (start > -1 && l.isBlockedBy(state.driver)) { // check if any link beyond entry is already blocked // if that is the case re-route is not possible anymore break; @@ -428,8 +432,8 @@ private void leaveLink(double time, UpdateEvent event) { updatePosition(time, event); createEvent(new RailsimTrainLeavesLinkEvent(time, state.driver.getVehicle().getId(), state.tailLink)); - // TODO: link should be released after headway time - disposition.unblockRailLink(time, state.driver, resources.getLink(state.tailLink)); + + RailLink tailLink = resources.getLink(state.tailLink); state.tailLink = nextTailLink.getLinkId(); state.tailPosition = 0; @@ -437,6 +441,19 @@ private void leaveLink(double time, UpdateEvent event) { decideTargetSpeed(event, state); decideNextUpdate(event); + + if (tailLink.minimumHeadwayTime == 0) + unblockTrack(time, state, tailLink); + else + updateQueue.add(new UpdateEvent(state, tailLink, time)); + + } + + /** + * Unblocks a link. + */ + private void unblockTrack(double time, TrainState state, RailLink unblockLink) { + disposition.unblockRailLink(time, state.driver, unblockLink); } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 98c3dc9ca46..e74f51e4193 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -21,11 +21,23 @@ final class UpdateEvent implements Comparable { */ boolean waitingForLink; + /** + * Stores a link that is should to be released. + */ + final RailLink unblockLink; + public UpdateEvent(TrainState state, Type type) { this.state = state; this.plannedTime = state.timestamp; this.type = type; this.waitingForLink = false; + this.unblockLink = null; + } + + public UpdateEvent(TrainState state, RailLink unblockLink, double time) { + this.state = state; + this.unblockLink = unblockLink; + this.plannedTime = time + unblockLink.minimumHeadwayTime; } @Override @@ -71,7 +83,9 @@ enum Type { RELEASE_TRACK, BLOCK_TRACK, WAIT_FOR_RESERVATION, - SPEED_CHANGE + SPEED_CHANGE, + + UNBLOCK_LINK } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java index 6388c51d09d..ab1811b927e 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java @@ -16,13 +16,11 @@ import java.util.List; +/** + * Calculates unblocked route between two {@link RailLink}. + */ public final class TrainRouter { - // TODO Placeholder for a routing interface - - // Train stations or other areas are modeled as block with entry and exit links - // within these blocks the train can be rerouted depending on track availability - private final Network network; private final RailResourceManager resources; private final LeastCostPathCalculator lpc; From b4347e863a87f86b06f2b37612e7b63eed516912 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 21 Jun 2023 12:49:03 +0200 Subject: [PATCH 068/258] adding test for links with headway time --- .../matsim/contrib/railsim/RailsimUtils.java | 9 ++++- .../railsim/qsimengine/UpdateEvent.java | 2 +- .../railsim/qsimengine/RailsimEngineTest.java | 36 ++++++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 956d3f9f6c1..aa831601f4d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -35,7 +35,7 @@ private RailsimUtils() { */ public static int getTrainCapacity(Link link) { Object attr = link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY); - return attr != null ? (int) attr: 1; + return attr != null ? (int) attr : 1; } /** @@ -46,6 +46,13 @@ public static double getMinimumTrainHeadwayTime(Link link) { return attr != null ? (double) attr : 0; } + /** + * Sets the minimum headway time after a link can be released. + */ + public static void setMinimumTrainHeadwayTime(Link link, double time) { + link.getAttributes().putAttribute(LINK_ATTRIBUTE_MINIMUM_TIME, time); + } + /** * @return the default deceleration time or the vehicle-specific value */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index e74f51e4193..ecebe3d6fdc 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -38,6 +38,7 @@ public UpdateEvent(TrainState state, RailLink unblockLink, double time) { this.state = state; this.unblockLink = unblockLink; this.plannedTime = time + unblockLink.minimumHeadwayTime; + this.type = Type.UNBLOCK_LINK; } @Override @@ -84,7 +85,6 @@ enum Type { BLOCK_TRACK, WAIT_FOR_RESERVATION, SPEED_CHANGE, - UNBLOCK_LINK } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index dbd2ff7b794..747230327d4 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -1,18 +1,22 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; +import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.SimpleDisposition; import ch.sbb.matsim.contrib.railsim.qsimengine.router.TrainRouter; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.events.EventsUtils; import org.matsim.core.network.NetworkUtils; import org.matsim.testcases.MatsimTestUtils; +import javax.annotation.Nullable; import java.io.File; +import java.util.function.Consumer; public class RailsimEngineTest { @@ -32,18 +36,27 @@ public void setUp() throws Exception { } - private RailsimTestUtils.Holder getTestEngine(String network) { + private RailsimTestUtils.Holder getTestEngine(String network, @Nullable Consumer f) { Network net = NetworkUtils.readNetwork(new File(utils.getPackageInputDirectory(), network).toString()); RailsimConfigGroup config = new RailsimConfigGroup(); collector.clear(); + if (f != null) { + for (Link link : net.getLinks().values()) { + f.accept(link); + } + } RailResourceManager res = new RailResourceManager(eventsManager, config, net); TrainRouter router = new TrainRouter(net, res); return new RailsimTestUtils.Holder(new RailsimEngine(eventsManager, config, res, new SimpleDisposition(res, router)), net); } + private RailsimTestUtils.Holder getTestEngine(String network) { + return getTestEngine(network, null); + } + @Test public void simple() { @@ -78,6 +91,27 @@ public void congested() { RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); test.doSimStepUntil(600); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("cargo", 359, 2000, 0) + .hasTrainState("regio", 474, 2000, 0); + + } + + @Test + public void congested_with_headway() { + + RailsimTestUtils.Holder test = getTestEngine("network0.xml", l -> RailsimUtils.setMinimumTrainHeadwayTime(l, 60)); + + RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo", 0, "l1-2", "l5-6"); + RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); + + test.doSimStepUntil(600); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("cargo", 359, 2000, 0) + .hasTrainState("regio", 485, 2000, 0); + } From 5a19a234d6fb5ed9c484e9362a505dc720a961ce Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 22 Jun 2023 17:18:45 +0200 Subject: [PATCH 069/258] changes required for merge --- .../prototype/RailsimLinkSpeedCalculatorImpl.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java index 6d24e9f4bce..f1c38f0deb8 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java @@ -10,8 +10,8 @@ import org.matsim.api.core.v01.events.handler.TransitDriverStartsEventHandler; import org.matsim.api.core.v01.network.Link; import org.matsim.core.config.ConfigUtils; +import org.matsim.core.mobsim.qsim.qnetsimengine.DefaultLinkSpeedCalculator; import org.matsim.core.mobsim.qsim.qnetsimengine.QVehicle; -import org.matsim.core.mobsim.qsim.qnetsimengine.linkspeedcalculator.DefaultLinkSpeedCalculator; import org.matsim.core.network.NetworkUtils; import org.matsim.pt.transitSchedule.api.TransitLine; import org.matsim.pt.transitSchedule.api.TransitRoute; @@ -29,7 +29,6 @@ public class RailsimLinkSpeedCalculatorImpl implements TransitDriverStartsEventHandler, RailsimLinkSpeedCalculator { private static final Logger log = LogManager.getLogger(RailsimLinkSpeedCalculatorImpl.class); - DefaultLinkSpeedCalculator defaultLinkSpeedCalculator = new DefaultLinkSpeedCalculator(); Set> transitVehicles = new HashSet<>(); @Inject @@ -38,6 +37,12 @@ public class RailsimLinkSpeedCalculatorImpl implements TransitDriverStartsEventH @Inject TrainStatistics statistics; + /** + * TODO: This was changed during merge and is now untested. + */ + @Inject + DefaultLinkSpeedCalculator defaultLinkSpeedCalculator; + @Override public double getMaximumVelocity(QVehicle vehicle, Link link, double time) { if (isTrain(vehicle)) { From 78e8665fc590df351e8fc638d900fc75dbf9a18f Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Fri, 23 Jun 2023 14:01:48 +0200 Subject: [PATCH 070/258] genf bern test with one train only --- .../integration/RailsimIntegrationTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 3a9142b7de4..bb42e8dfbde 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -6,6 +6,7 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; +import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; @@ -17,14 +18,22 @@ import org.matsim.core.scenario.ScenarioUtils; import org.matsim.core.utils.io.IOUtils; import org.matsim.examples.ExamplesUtils; +import org.matsim.pt.transitSchedule.api.Departure; +import org.matsim.pt.transitSchedule.api.TransitLine; +import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.testcases.MatsimTestUtils; import org.matsim.testcases.utils.EventsCollector; +import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.VehicleType; import java.io.File; import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; public class RailsimIntegrationTest { @@ -78,6 +87,10 @@ public void test0_simple() { } private EventsCollector runSimulation(File scenarioDir) { + return runSimulation(scenarioDir, null); + } + + private EventsCollector runSimulation(File scenarioDir, Consumer f) { Config config = ConfigUtils.loadConfig(new File(scenarioDir, "config.xml").toString()); config.controler().setOutputDirectory(utils.getOutputDirectory()); @@ -86,6 +99,10 @@ private EventsCollector runSimulation(File scenarioDir) { config.controler().setLastIteration(0); Scenario scenario = ScenarioUtils.loadScenario(config); + + if (f != null) + f.accept(scenario); + Controler controler = new Controler(scenario); controler.addOverridingModule(new RailsimModule()); controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); @@ -290,6 +307,36 @@ public void test4_genf_bern() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "4_genf_bern")); } + @Test + public void test4_1_genf_bern() { + + // Remove vehicles except the first one + Consumer filter = scenario -> { + + Set> remove = scenario.getTransitVehicles().getVehicles().values().stream().filter( + v -> !v.getId().toString().equals("Bummelzug_GE_BE_train_0") + ).map(Vehicle::getId).collect(Collectors.toSet()); + + for (TransitLine line : scenario.getTransitSchedule().getTransitLines().values()) { + + for (TransitRoute route : line.getRoutes().values()) { + + Collection values = new ArrayList<>(route.getDepartures().values()); + for (Departure departure : values) { + + if (remove.contains(departure.getVehicleId())) + route.removeDeparture(departure); + } + } + } + + remove.forEach(v -> scenario.getTransitVehicles().removeVehicle(v)); + }; + + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "4_genf_bern"), filter); + + } + @Test public void test5_complexTwoSources() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "5_complexTwoSources")); From 1093ef53f942fa86bd6efd2879f553f8b6d76220 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 28 Jun 2023 12:39:58 +0200 Subject: [PATCH 071/258] use more precise time for train states, don't reserve links when currently driving on pt stop --- .../matsim/contrib/railsim/RailsimUtils.java | 10 +++++ .../railsim/analysis/RailsimCsvWriter.java | 11 ++--- .../RailsimTrainStateEventMapper.java | 1 + .../events/RailsimTrainStateEvent.java | 16 ++++++- .../railsim/qsimengine/RailsimCalc.java | 8 ++++ .../railsim/qsimengine/RailsimEngine.java | 2 +- .../railsim/qsimengine/TrainState.java | 2 +- .../integration/RailsimIntegrationTest.java | 43 +++++++++++-------- 8 files changed, 68 insertions(+), 25 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index aa831601f4d..ff5984ba141 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -7,6 +7,9 @@ import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.vehicles.VehicleType; +import java.math.BigDecimal; +import java.math.RoundingMode; + /** * Utility class for working with Railsim and its specific attributes. * @@ -30,6 +33,13 @@ private RailsimUtils() { // TODO: Setter methods + /** + * Round number to precision commonly used in Railsim. + */ + public static double round(double d) { + return BigDecimal.valueOf(d).setScale(3, RoundingMode.HALF_EVEN).doubleValue(); + } + /** * @return the train capacity for this link, if no link attribute is provided the default is 1. */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java index b5353c60ed7..8ec5450d074 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java @@ -1,5 +1,6 @@ package ch.sbb.matsim.contrib.railsim.analysis; +import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.apache.commons.csv.CSVFormat; @@ -39,13 +40,13 @@ public static void writeTrainStatesCsv(List events, Netw try (CSVPrinter csv = new CSVPrinter(IOUtils.getBufferedWriter(filename), CSVFormat.DEFAULT.builder().setHeader(header).build())) { for (RailsimTrainStateEvent event : events) { csv.print(event.getVehicleId().toString()); - csv.print(event.getTime()); + csv.print(event.getExactTime()); csv.print(event.getAcceleration()); - csv.print(event.getSpeed()); - csv.print(event.getTargetSpeed()); + csv.print(RailsimUtils.round(event.getSpeed())); + csv.print(RailsimUtils.round(event.getTargetSpeed())); csv.print(event.getHeadLink().toString()); - csv.print(event.getHeadPosition()); + csv.print(RailsimUtils.round(event.getHeadPosition())); if (network != null) { Link link = network.getLinks().get(event.getHeadLink()); if (link != null) { @@ -61,7 +62,7 @@ public static void writeTrainStatesCsv(List events, Netw } csv.print(event.getTailLink().toString()); - csv.print(event.getTailPosition()); + csv.print(RailsimUtils.round(event.getTailPosition())); if (network != null) { Link link = network.getLinks().get(event.getTailLink()); if (link != null) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java index 586c9283aa3..4ca57bea622 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java @@ -32,6 +32,7 @@ public RailsimTrainStateEvent apply(GenericEvent event) { var attributes = event.getAttributes(); return new RailsimTrainStateEvent( event.getTime(), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_EXACT_TIME)), asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_VEHICLE), Vehicle.class), asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEADLINK), Link.class), Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEADPOSITION)), diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java index 6b773cab57d..a373a45f7a5 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -1,11 +1,14 @@ package ch.sbb.matsim.contrib.railsim.events; +import ch.sbb.matsim.contrib.railsim.RailsimUtils; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.events.Event; import org.matsim.api.core.v01.events.HasVehicleId; import org.matsim.api.core.v01.network.Link; import org.matsim.vehicles.Vehicle; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Map; /** @@ -15,6 +18,7 @@ public class RailsimTrainStateEvent extends Event implements HasVehicleId { public static final String EVENT_TYPE = "railsimTrainStateEvent"; + public static final String ATTRIBUTE_EXACT_TIME = "exactTime"; public static final String ATTRIBUTE_HEADLINK = "headLink"; public static final String ATTRIBUTE_HEADPOSITION = "headPosition"; public static final String ATTRIBUTE_TAILLINK = "tailLink"; @@ -23,6 +27,10 @@ public class RailsimTrainStateEvent extends Event implements HasVehicleId { public static final String ATTRIBUTE_ACCELERATION = "acceleration"; public static final String ATTRIBUTE_TARGETSPEED = "targetSpeed"; + /** + * Exact time with resolution of 0.001s. + */ + private final double exactTime; private final Id vehicleId; private final Id headLink; private final double headPosition; @@ -32,11 +40,12 @@ public class RailsimTrainStateEvent extends Event implements HasVehicleId { private final double acceleration; private final double targetSpeed; - public RailsimTrainStateEvent(double time, Id vehicleId, + public RailsimTrainStateEvent(double time, double exactTime, Id vehicleId, Id headLink, double headPosition, Id tailLink, double tailPosition, double speed, double acceleration, double targetSpeed) { super(time); + this.exactTime = RailsimUtils.round(exactTime); this.vehicleId = vehicleId; this.headLink = headLink; this.headPosition = headPosition; @@ -57,6 +66,10 @@ public Id getVehicleId() { return vehicleId; } + public double getExactTime() { + return exactTime; + } + public double getHeadPosition() { return headPosition; } @@ -88,6 +101,7 @@ public Id getTailLink() { @Override public Map getAttributes() { Map attr = super.getAttributes(); + attr.put(ATTRIBUTE_EXACT_TIME, String.valueOf(exactTime)); attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); attr.put(ATTRIBUTE_HEADLINK, String.valueOf(headLink)); attr.put(ATTRIBUTE_HEADPOSITION, Double.toString(headPosition)); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 46c1cded16a..36a9806fa60 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -152,6 +152,10 @@ static double calcTargetSpeedForStop(double dist, double acceleration, double de */ public static double nextLinkReservation(TrainState state, RailLink currentLink) { + // on way to pt stop, no need to reserve anymore + if (state.isStop(currentLink.getLinkId()) && FuzzyUtils.lessThan(state.headPosition, currentLink.length)) + return Double.POSITIVE_INFINITY; + double assumedSpeed = calcPossibleMaxSpeed(state); // time needed for full stop @@ -194,6 +198,10 @@ public static List calcLinksToBlock(TrainState state, RailLink current List result = new ArrayList<>(); + // Currently driving to pt stop + if (state.isStop(currentLink.getLinkId()) && FuzzyUtils.lessThan(state.headPosition, currentLink.length)) + return result; + double assumedSpeed = calcPossibleMaxSpeed(state); double stopTime = assumedSpeed / state.train.deceleration(); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index eb4b23967fa..1ff27b9bdfa 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -577,7 +577,7 @@ private void decideNextUpdate(UpdateEvent event) { List tmp = RailsimCalc.calcLinksToBlock(state, currentLink); double r = RailsimCalc.nextLinkReservation(state, currentLink); - throw new AssertionError("Reserve distance must be positive, but was" + r); + throw new AssertionError("Reserve distance must be positive, but was " + r); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 6ccdb5f3df4..22665e23428 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -141,7 +141,7 @@ boolean isStop(Id link) { } RailsimTrainStateEvent asEvent(double time) { - return new RailsimTrainStateEvent(time, driver.getVehicle().getId(), + return new RailsimTrainStateEvent(time, time, driver.getVehicle().getId(), headLink, headPosition, tailLink, tailPosition, speed, acceleration, targetSpeed); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index bb42e8dfbde..3863bca99d3 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -21,6 +21,7 @@ import org.matsim.pt.transitSchedule.api.Departure; import org.matsim.pt.transitSchedule.api.TransitLine; import org.matsim.pt.transitSchedule.api.TransitRoute; +import org.matsim.pt.transitSchedule.api.TransitScheduleFactory; import org.matsim.testcases.MatsimTestUtils; import org.matsim.testcases.utils.EventsCollector; import org.matsim.vehicles.Vehicle; @@ -273,23 +274,6 @@ public void test0_varyingCapacities() { @Test public void test1_oppositeTraffic() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "1_oppositeTraffic")); - -// List arrivalEvents = collector.getEvents() -// .stream() -// .filter(event -> event instanceof VehicleArrivesAtFacilityEvent) -// .map(event -> (VehicleArrivesAtFacilityEvent) event) -// .filter(event -> event.getFacilityId().toString().equals("t3_A-B")) -// .toList(); -// VehicleArrivesAtFacilityEvent train0Arrival = arrivalEvents.stream() -// .filter(event -> event.getFacilityId().toString().equals("t3_A-B")) -// .filter(event -> event.getVehicleId().toString().equals("train1")) -// .findFirst().orElseThrow(); -// Assert.assertEquals("train1 should arrive at 10:00:00", 36000.0, train0Arrival.getTime(), 1e-7); // TODO fix times -// VehicleArrivesAtFacilityEvent train10Arrival = arrivalEvents.stream() -// .filter(event -> event.getFacilityId().toString().equals("t1_B-A")) -// .filter(event -> event.getVehicleId().toString().equals("train2")) -// .findFirst().orElseThrow(); -// Assert.assertEquals("train2 should arrive at 10:00:00", 36000.0, train0Arrival.getTime(), 1e-7); // TODO fix times } @Test @@ -403,4 +387,29 @@ public void test_station_rerouting() { assertTrainState(30982, 0, 0, 0, 400, filterTrainEvents(collector, "train4")); } + @Test + public void test_station_rerouting_concurrent() { + Consumer filter = scenario -> { + + TransitScheduleFactory f = scenario.getTransitSchedule().getFactory(); + + for (TransitLine line : scenario.getTransitSchedule().getTransitLines().values()) { + + for (TransitRoute route : line.getRoutes().values()) { + + Collection values = new ArrayList<>(route.getDepartures().values()); + values.forEach(route::removeDeparture); + + // Re-create departure for same time + for (Departure departure : values) { + Departure d = f.createDeparture(departure.getId(), 8 * 3600); + d.setVehicleId(departure.getVehicleId()); + route.addDeparture(d); + } + } + } + }; + + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "station_rerouting"), filter); + } } From e26a7870995fe43f7501b24bfb4930b2117b9dd3 Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Fri, 30 Jun 2023 10:23:52 +0200 Subject: [PATCH 072/258] Removed duplicate streamex dependency --- contribs/ev/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contribs/ev/pom.xml b/contribs/ev/pom.xml index ba150a34a5b..91a218be8b1 100644 --- a/contribs/ev/pom.xml +++ b/contribs/ev/pom.xml @@ -36,11 +36,5 @@ 0.8.1 compile - - one.util - streamex - 0.8.1 - compile - From 235fe65b5bce13bafe963af4126cb553c8296917 Mon Sep 17 00:00:00 2001 From: nixlaos Date: Wed, 12 Jul 2023 14:33:33 +0200 Subject: [PATCH 073/258] added converters for missing freight events --- .../freight/events/CarrierEventsReaders.java | 7 ++++++- .../freight/events/CarrierServiceEndEvent.java | 15 +++++++++++++++ .../freight/events/CarrierServiceStartEvent.java | 16 ++++++++++++++++ .../events/CarrierShipmentDeliveryEndEvent.java | 16 ++++++++++++++++ .../CarrierShipmentDeliveryStartEvent.java | 16 ++++++++++++++++ .../events/CarrierShipmentPickupEndEvent.java | 16 ++++++++++++++++ .../events/CarrierShipmentPickupStartEvent.java | 16 ++++++++++++++++ 7 files changed, 101 insertions(+), 1 deletion(-) diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java index c268d7d1184..20eb9dad5a8 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java @@ -36,9 +36,14 @@ public class CarrierEventsReaders { public static Map createCustomEventMappers() { return Map.of( + CarrierServiceStartEvent.EVENT_TYPE, CarrierServiceStartEvent::convert, + CarrierServiceEndEvent.EVENT_TYPE, CarrierServiceEndEvent::convert, + CarrierShipmentDeliveryStartEvent.EVENT_TYPE, CarrierShipmentDeliveryStartEvent::convert, + CarrierShipmentDeliveryEndEvent.EVENT_TYPE, CarrierShipmentDeliveryEndEvent::convert, + CarrierShipmentPickupStartEvent.EVENT_TYPE, CarrierShipmentPickupStartEvent::convert, + CarrierShipmentPickupEndEvent.EVENT_TYPE, CarrierShipmentPickupEndEvent::convert, CarrierTourStartEvent.EVENT_TYPE, CarrierTourStartEvent::convert, // CarrierTourEndEvent.EVENT_TYPE, CarrierTourEndEvent::convert - // more will follow later, KMT feb'23 ); } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java index e1527edc9b0..0c4585932a0 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java @@ -24,6 +24,8 @@ import java.util.Map; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierService; import org.matsim.vehicles.Vehicle; @@ -67,4 +69,17 @@ public Map getAttributes() { attr.put(ATTRIBUTE_SERVICE_DURATION, String.valueOf(serviceDuration)); return attr; } + + public static CarrierServiceEndEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id carrierServiceId = Id.create(attributes.get(ATTRIBUTE_SERVICE_ID), CarrierService.class); + Id locationLinkId = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + CarrierService service = CarrierService.Builder.newInstance(carrierServiceId, locationLinkId) + .setServiceDuration(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .build(); + Id vehicleId = Id.create(attributes.get(ATTRIBUTE_VEHICLE), Vehicle.class); + return new CarrierServiceEndEvent(time, carrierId, service, vehicleId); + } } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java index 719bfc96978..6c8550df63f 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierService; import org.matsim.vehicles.Vehicle; @@ -77,4 +79,18 @@ public Map getAttributes() { attr.put(ATTRIBUTE_CAPACITYDEMAND, String.valueOf(capacityDemand)); return attr; } + + public static CarrierServiceStartEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id carrierServiceId = Id.create(attributes.get(ATTRIBUTE_SERVICE_ID), CarrierService.class); + Id locationLinkId = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + CarrierService service = CarrierService.Builder.newInstance(carrierServiceId, locationLinkId) + .setServiceDuration(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .setCapacityDemand(Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND))) + .build(); + Id vehicleId = Id.create(attributes.get(ATTRIBUTE_VEHICLE), Vehicle.class); + return new CarrierServiceStartEvent(time, carrierId, service, vehicleId); + } } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java index 9e702f3359d..eb462b77a88 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -76,4 +78,18 @@ public Map getAttributes() { return attr; } + public static CarrierShipmentDeliveryEndEvent convert(GenericEvent event) { + var attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentTo = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int size = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, null, shipmentTo, size) + .setDeliveryServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentDeliveryEndEvent(time, carrierId, shipment, vehicleId); + } + } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java index 6e097bbe64d..08b77671842 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -75,4 +77,18 @@ public Map getAttributes() { return attr; } + public static CarrierShipmentDeliveryStartEvent convert(GenericEvent event) { + var attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentTo = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int size = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, null, shipmentTo, size) + .setDeliveryServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentDeliveryStartEvent(time, carrierId, shipment, vehicleId); + } + } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java index 664dbfa45c7..e88ead0cb37 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -69,4 +71,18 @@ public Map getAttributes() { attr.put(ATTRIBUTE_CAPACITYDEMAND, String.valueOf(capacityDemand)); return attr; } + + public static CarrierShipmentPickupEndEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentFrom = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int shipmentSize = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, shipmentFrom, null, shipmentSize) + .setPickupServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_PICKUP_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentPickupEndEvent(time, carrierId, shipment, vehicleId); + } } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java index 7285434d134..4f7725e6ece 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -69,4 +71,18 @@ public Map getAttributes() { attr.put(ATTRIBUTE_CAPACITYDEMAND, String.valueOf(capacityDemand)); return attr; } + + public static CarrierShipmentPickupStartEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentFrom = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int shipmentSize = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, shipmentFrom, null, shipmentSize) + .setPickupServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_PICKUP_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentPickupStartEvent(time, carrierId, shipment, vehicleId); + } } From 2ef28130513920e0c6a673c810d2a09f3f12764b Mon Sep 17 00:00:00 2001 From: nixlaos Date: Tue, 8 Aug 2023 16:14:51 +0200 Subject: [PATCH 074/258] added test for reading in carrier events --- .../carrier/CarrierEventsReadersTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java diff --git a/contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java b/contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java new file mode 100644 index 00000000000..f19068177c2 --- /dev/null +++ b/contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java @@ -0,0 +1,52 @@ +package org.matsim.contrib.freight.carrier; + +import org.junit.Rule; +import org.junit.Test; +import org.matsim.contrib.freight.events.CarrierEventsReaders; +import org.matsim.core.api.experimental.events.EventsManager; +import org.matsim.core.events.EventsUtils; +import org.matsim.core.events.MatsimEventsReader; +import org.matsim.testcases.MatsimTestUtils; +import org.matsim.testcases.utils.EventsCollector; + +import static org.junit.Assert.assertEquals; + +public class CarrierEventsReadersTest { + + @Rule + public final MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public void testWriteReadServiceBasedEvents() { + EventsManager eventsManager = EventsUtils.createEventsManager(); + EventsCollector collector = new EventsCollector(); + MatsimEventsReader carrierEventsReaders = CarrierEventsReaders.createEventsReader(eventsManager); + + eventsManager.addHandler(collector); + eventsManager.initProcessing(); + carrierEventsReaders.readFile(utils.getClassInputDirectory() + "serviceBasedEventsFile.xml"); + eventsManager.finishProcessing(); + + assertEquals("number of events should be same", collector.getEvents().size(), collector.getEvents().size()); + MatsimTestUtils.assertEqualEventsFiles(utils.getClassInputDirectory() + "serviceBasedEventsFile.xml", utils.getClassInputDirectory() + "serviceBasedEventsFileWritten.xml"); + MatsimTestUtils.assertEqualFilesLineByLine(utils.getClassInputDirectory() + "serviceBasedEventsFile.xml", utils.getClassInputDirectory() + "serviceBasedEventsFileWritten.xml"); + } + + @Test + public void testWriteReadShipmentBasedEvents() { + EventsManager eventsManager = EventsUtils.createEventsManager(); + EventsCollector collector = new EventsCollector(); + MatsimEventsReader carrierEventsReaders = CarrierEventsReaders.createEventsReader(eventsManager); + + eventsManager.addHandler(collector); + eventsManager.initProcessing(); + carrierEventsReaders.readFile(utils.getClassInputDirectory() + "shipmentBasedEventsFile.xml"); + eventsManager.finishProcessing(); + + assertEquals("number of events should be same", collector.getEvents().size(), collector.getEvents().size()); + MatsimTestUtils.assertEqualEventsFiles(utils.getClassInputDirectory() + "shipmentBasedEventsFile.xml", utils.getClassInputDirectory() + "shipmentBasedEventsFileWritten.xml"); + MatsimTestUtils.assertEqualFilesLineByLine(utils.getClassInputDirectory() + "shipmentBasedEventsFile.xml", utils.getClassInputDirectory() + "shipmentBasedEventsFileWritten.xml"); + } + + +} From 1eb3f9cb181014861a06d0ae0a8707f75f691f21 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Thu, 17 Aug 2023 14:46:42 +0200 Subject: [PATCH 075/258] small changes from meeting --- contribs/railsim/pom.xml | 6 ++++++ .../contrib/railsim/qsimengine/RailLink.java | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/contribs/railsim/pom.xml b/contribs/railsim/pom.xml index 24bad934ca2..37cc5674a20 100644 --- a/contribs/railsim/pom.xml +++ b/contribs/railsim/pom.xml @@ -30,6 +30,12 @@ vsp 16.0-SNAPSHOT test + + + * + * + + diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index a8a033e451e..db762656fd5 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -26,9 +26,9 @@ public final class RailLink implements HasLinkId { private final boolean isExitLink; /** - * Reservations held for each track. + * Drivers of each blocked track. */ - private final MobsimDriverAgent[] reservations; + private final MobsimDriverAgent[] blocked; final double length; final double freeSpeed; @@ -44,7 +44,7 @@ public RailLink(Link link) { id = link.getId(); state = new TrackState[RailsimUtils.getTrainCapacity(link)]; Arrays.fill(state, TrackState.FREE); - reservations = new MobsimDriverAgent[state.length]; + blocked = new MobsimDriverAgent[state.length]; length = link.getLength(); freeSpeed = link.getFreespeed(); minimumHeadwayTime = RailsimUtils.getMinimumTrainHeadwayTime(link); @@ -86,7 +86,7 @@ public double getAllowedFreespeed(MobsimDriverAgent driver) { * Check if driver has already reserved this link. */ public boolean isBlockedBy(MobsimDriverAgent driver) { - for (MobsimDriverAgent reservation : reservations) { + for (MobsimDriverAgent reservation : blocked) { if (reservation == driver) return true; } @@ -110,7 +110,7 @@ boolean hasFreeTrack() { int blockTrack(MobsimDriverAgent driver) { for (int i = 0; i < state.length; i++) { if (state[i] == TrackState.FREE) { - reservations[i] = driver; + blocked[i] = driver; state[i] = TrackState.BLOCKED; return i; } @@ -123,9 +123,9 @@ int blockTrack(MobsimDriverAgent driver) { */ int releaseTrack(MobsimDriverAgent driver) { for (int i = 0; i < state.length; i++) { - if (reservations[i] == driver) { + if (blocked[i] == driver) { state[i] = TrackState.FREE; - reservations[i] = null; + blocked[i] = null; return i; } } From 59eb4b3f9c72d2d8c2f9a6a32182456f48dd22b5 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 18 Aug 2023 09:18:23 +0200 Subject: [PATCH 076/258] Avoid dependency on vsp contrib - Enterprise security restrictions prohibit the dependency org.springframework:spring-core:5.1.0.RELEASE. This version is a transitive dependency of vsp contrib (via osmosis). - As solution, the SnzActivities class needed for the integration tests is copied to the railsim package. --- contribs/railsim/pom.xml | 14 --- .../integration/RailsimIntegrationTest.java | 1 - .../railsim/integration/SnzActivities.java | 91 +++++++++++++++++++ 3 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java diff --git a/contribs/railsim/pom.xml b/contribs/railsim/pom.xml index 37cc5674a20..3f184d39bb0 100644 --- a/contribs/railsim/pom.xml +++ b/contribs/railsim/pom.xml @@ -24,20 +24,6 @@ compile - - - org.matsim.contrib - vsp - 16.0-SNAPSHOT - test - - - * - * - - - - org.assertj assertj-core diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 3863bca99d3..c910d5e08a8 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -10,7 +10,6 @@ import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; -import org.matsim.contrib.vsp.scenario.SnzActivities; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.AbstractModule; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java new file mode 100644 index 00000000000..99fa9ad6fc0 --- /dev/null +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java @@ -0,0 +1,91 @@ +/** + * Avoid dependency on vsp contrib, copy from: + * https://github.com/matsim-org/matsim-libs/blob/b2305e5e0f744b357486c8bbab253bb7c38aaad4/contribs/vsp/src/main/java/org/matsim/contrib/vsp/scenario/SnzActivities.java + */ +package ch.sbb.matsim.contrib.railsim.integration; + +import org.matsim.core.config.Config; +import org.matsim.core.config.groups.PlanCalcScoreConfigGroup; + +/** + * Defines available activities and open- and closing times in Snz scenarios at vsp. + */ +public enum SnzActivities { + + home, + other, + visit, + accomp_children, + accomp_other, + + educ_kiga(7, 17), + educ_primary(7, 16), + educ_secondary(7, 17), + educ_tertiary(7, 22), + educ_higher(7, 19), + educ_other(7, 22), + + work(6, 20), + business(8, 20), + errands(8, 20), + + leisure(9, 27), + restaurant(8, 27), + shop_daily(8, 20), + shop_other(8, 20); + + /** + * Start time of an activity in hours, can be -1 if not defined. + */ + private final double start; + + /** + * End time of an activity in hours, can be -1 if not defined. + */ + private final double end; + + SnzActivities(double start, double end) { + this.start = start; + this.end = end; + } + + SnzActivities() { + this.start = -1; + this.end = -1; + } + + + /** + * Apply start and end time to params. + */ + public PlanCalcScoreConfigGroup.ActivityParams apply(PlanCalcScoreConfigGroup.ActivityParams params) { + if (start >= 0) + params = params.setOpeningTime(start * 3600.); + if (end >= 0) + params = params.setClosingTime(end * 3600.); + + return params; + } + + /** + * Add activity params for the scenario config. + */ + public static void addScoringParams(Config config) { + + for (SnzActivities value : SnzActivities.values()) { + for (long ii = 600; ii <= 97200; ii += 600) { + config.planCalcScore().addActivityParams(value.apply(new PlanCalcScoreConfigGroup.ActivityParams(value.name() + "_" + ii).setTypicalDuration(ii))); + } + } + + config.planCalcScore().addActivityParams(new PlanCalcScoreConfigGroup.ActivityParams("car interaction").setTypicalDuration(60)); + config.planCalcScore().addActivityParams(new PlanCalcScoreConfigGroup.ActivityParams("ride interaction").setTypicalDuration(60)); + config.planCalcScore().addActivityParams(new PlanCalcScoreConfigGroup.ActivityParams("bike interaction").setTypicalDuration(60)); + + config.planCalcScore().addActivityParams(new PlanCalcScoreConfigGroup.ActivityParams("other").setTypicalDuration(600 * 3)); + + config.planCalcScore().addActivityParams(new PlanCalcScoreConfigGroup.ActivityParams("freight_start").setTypicalDuration(60 * 15)); + config.planCalcScore().addActivityParams(new PlanCalcScoreConfigGroup.ActivityParams("freight_end").setTypicalDuration(60 * 15)); + + } +} From e8ae168c1312764fdb54bdb8912b6d5eca5cc746 Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Fri, 18 Aug 2023 09:51:16 +0200 Subject: [PATCH 077/258] Fixed failing taxi extension --- .../contrib/ev/stats/ChargerPowerTimeProfileCalculator.java | 2 +- .../contrib/ev/stats/ChargerPowerTimeProfileView.java | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java index e471c772fdb..3ae9a239aa7 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java @@ -53,7 +53,7 @@ public void handleEvent(ChargingEndEvent event) { increment(averagePowerIn_kW, event.getChargerId(), chargingStartTime.get(event.getVehicleId()), event.getTime()); } private void increment(double averagePower, Id chargerId, double beginTime, double endTime) { - if (beginTime == endTime && beginTime >= qsimEndTime) { + if (beginTime == endTime && beginTime >= qsimEndTime || qsimEndTime == 0) { return; } endTime = Math.min(endTime, qsimEndTime); diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java index 27dbb7559c9..d0941954540 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileView.java @@ -4,14 +4,10 @@ import org.apache.commons.lang3.tuple.Pair; import org.matsim.api.core.v01.Id; import org.matsim.contrib.common.timeprofile.ProfileWriter; -import org.matsim.contrib.ev.infrastructure.Charger; import java.awt.*; -import java.util.Comparator; import java.util.Map; -import one.util.streamex.EntryStream; - public class ChargerPowerTimeProfileView implements ProfileWriter.ProfileView { private final ChargerPowerTimeProfileCalculator calculator; @@ -20,7 +16,7 @@ public ChargerPowerTimeProfileView(ChargerPowerTimeProfileCalculator calculator) } @Override - public int[] times() { + public double[] times() { return calculator.getTimeDiscretizer().getTimes(); } From 1a3c4b9c1379109260f9d33f13d2a75ae761317b Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 18 Aug 2023 12:40:19 +0200 Subject: [PATCH 078/258] Remove signal based railsim prototype --- .../AdaptiveTrainSignalsControler.java | 385 ------ .../prototype/LinkUsageVisualizer.java | 153 --- .../railsim/prototype/RailsimConfigGroup.java | 192 --- .../prototype/RailsimLinkSpeedCalculator.java | 12 - .../RailsimLinkSpeedCalculatorImpl.java | 203 ---- .../prototype/RailsimSignalsQSimModule.java | 35 - .../railsim/prototype/RailsimUtils.java | 138 --- .../contrib/railsim/prototype/RunRailsim.java | 158 --- .../contrib/railsim/prototype/SignalInfo.java | 134 --- .../prototype/SpatialTrainDimension.java | 257 ---- .../railsim/prototype/TrainEntersLink.java | 56 - .../TrainEntersLinkEventHandler.java | 11 - .../railsim/prototype/TrainLeavesLink.java | 55 - .../TrainLeavesLinkEventHandler.java | 10 - .../prototype/TrainPathEntersLink.java | 56 - .../TrainPathEntersLinkEventHandler.java | 10 - .../railsim/prototype/TrainStatistics.java | 195 --- .../ConvertTrainEventsToDefaultEvents.java | 47 - .../analysis/TrainEventsHandler.java | 81 -- .../prototype/analysis/TrainEventsReader.java | 112 -- .../analysis/TransitEventHandler.java | 100 -- .../railsim/prototype/demo/RunDemo.java | 72 -- .../prepare/AdjustNetworkToSchedule.java | 286 ----- .../prepare/DistributeCapacities.java | 99 -- .../prototype/prepare/SplitTransitLinks.java | 250 ---- .../railsim/prototype/supply/DepotInfo.java | 132 -- .../supply/InfrastructureRepository.java | 47 - .../railsim/prototype/supply/LinkType.java | 31 - .../supply/RailsimSupplyBuilder.java | 417 ------- .../supply/RailsimSupplyConfigGroup.java | 333 ----- .../supply/RollingStockRepository.java | 20 - .../prototype/supply/RouteDirection.java | 30 - .../prototype/supply/RouteStopInfo.java | 92 -- .../railsim/prototype/supply/RouteType.java | 35 - .../supply/RunRailsimSupplyBuilder.java | 154 --- .../prototype/supply/SectionPartInfo.java | 57 - .../prototype/supply/SectionSegmentInfo.java | 69 -- .../railsim/prototype/supply/StopInfo.java | 102 -- .../prototype/supply/SupplyFactory.java | 224 ---- .../prototype/supply/TransitLineInfo.java | 231 ---- .../supply/VehicleAllocationInfo.java | 18 - .../supply/VehicleCircuitsPlanner.java | 15 - .../prototype/supply/VehicleTypeInfo.java | 80 -- .../DefaultVehicleCircuitsPlanner.java | 256 ---- .../circuits/NoVehicleCircuitsPlanner.java | 65 - .../supply/circuits/RouteDepartureEvent.java | 140 --- .../TransitLineVehicleAllocation.java | 84 -- .../prototype/supply/circuits/Vehicle.java | 14 - .../supply/circuits/VehicleDepot.java | 116 -- .../supply/circuits/VehicleFleet.java | 190 --- .../supply/circuits/VehicleQueue.java | 93 -- .../supply/circuits/VehicleReadyEvent.java | 14 - .../DefaultInfrastructureRepository.java | 74 -- .../DefaultRollingStockRepository.java | 39 - .../QRailsimSignalsNetworkFactory.java | 64 - .../RunRailsimAdvancedCorridorTest.java | 106 -- .../railsim/prototype/RunRailsimTest.java | 1066 ----------------- .../prepare/AdjustNetworkToScheduleTest.java | 91 -- .../prepare/DistributeCapacitiesTest.java | 58 - .../prepare/SplitTransitLinksTest.java | 89 -- .../supply/RailsimSupplyBuilderTest.java | 157 --- .../DefaultVehicleCircuitsPlannerTest.java | 96 -- .../NoVehicleCircuitsPlannerTest.java | 96 -- .../test0/config.xml | 40 - .../test0/network_trainCorridor.xml | 329 ----- .../test0/network_trainCorridor.xml.gz | Bin 4838 -> 0 bytes .../test0/transitSchedule.xml | 153 --- .../test0/transitVehicles.xml | 42 - .../prototype/RunRailsimTest/test0/config.xml | 39 - .../RunRailsimTest/test0/trainNetwork.xml | 57 - .../RunRailsimTest/test0/transitSchedule.xml | 45 - .../RunRailsimTest/test0/transitVehicles.xml | 35 - .../prototype/RunRailsimTest/test1/config.xml | 39 - .../RunRailsimTest/test1/trainNetwork.xml | 73 -- .../RunRailsimTest/test1/transitSchedule.xml | 58 - .../RunRailsimTest/test1/transitVehicles.xml | 35 - .../RunRailsimTest/test10/config.xml | 38 - .../RunRailsimTest/test10/trainNetwork.xml | 84 -- .../RunRailsimTest/test10/transitSchedule.xml | 52 - .../RunRailsimTest/test10/transitVehicles.xml | 29 - .../RunRailsimTest/test11/config.xml | 39 - .../RunRailsimTest/test11/trainNetwork.xml | 79 -- .../RunRailsimTest/test11/transitSchedule.xml | 76 -- .../RunRailsimTest/test11/transitVehicles.xml | 33 - .../RunRailsimTest/test12/config.xml | 39 - .../RunRailsimTest/test12/trainNetwork.xml | 79 -- .../RunRailsimTest/test12/transitSchedule.xml | 76 -- .../RunRailsimTest/test12/transitVehicles.xml | 33 - .../RunRailsimTest/test13/config.xml | 39 - .../RunRailsimTest/test13/trainNetwork.xml | 104 -- .../RunRailsimTest/test13/transitSchedule.xml | 56 - .../RunRailsimTest/test13/transitVehicles.xml | 48 - .../RunRailsimTest/test14/config.xml | 39 - .../RunRailsimTest/test14/trainNetwork.xml | 85 -- .../RunRailsimTest/test14/transitSchedule.xml | 46 - .../RunRailsimTest/test14/transitVehicles.xml | 28 - .../prototype/RunRailsimTest/test2/config.xml | 39 - .../RunRailsimTest/test2/trainNetwork.xml | 85 -- .../RunRailsimTest/test2/transitSchedule.xml | 64 - .../RunRailsimTest/test2/transitVehicles.xml | 54 - .../prototype/RunRailsimTest/test3/config.xml | 39 - .../RunRailsimTest/test3/trainNetwork.xml | 123 -- .../RunRailsimTest/test3/transitSchedule.xml | 67 -- .../RunRailsimTest/test3/transitVehicles.xml | 35 - .../prototype/RunRailsimTest/test4/config.xml | 38 - .../RunRailsimTest/test4/trainNetwork.xml.gz | Bin 159748 -> 0 bytes .../test4/transitSchedule.xml.gz | Bin 70575 -> 0 bytes .../test4/transitVehicles.xml.gz | Bin 1366 -> 0 bytes .../prototype/RunRailsimTest/test5/config.xml | 39 - .../RunRailsimTest/test5/trainNetwork.xml | 96 -- .../RunRailsimTest/test5/transitSchedule.xml | 62 - .../RunRailsimTest/test5/transitVehicles.xml | 36 - .../prototype/RunRailsimTest/test6/config.xml | 39 - .../RunRailsimTest/test6/trainNetwork.xml | 196 --- .../RunRailsimTest/test6/transitSchedule.xml | 65 - .../RunRailsimTest/test6/transitVehicles.xml | 28 - .../prototype/RunRailsimTest/test7/config.xml | 39 - .../RunRailsimTest/test7/trainNetwork.xml | 178 --- .../RunRailsimTest/test7/transitSchedule.xml | 47 - .../RunRailsimTest/test7/transitVehicles.xml | 50 - .../prototype/RunRailsimTest/test8/config.xml | 39 - .../RunRailsimTest/test8/trainNetwork.xml | 423 ------- .../RunRailsimTest/test8/transitSchedule.xml | 105 -- .../RunRailsimTest/test8/transitVehicles.xml | 29 - .../prototype/RunRailsimTest/test9/config.xml | 39 - .../RunRailsimTest/test9/trainNetwork.xml | 423 ------- .../RunRailsimTest/test9/transitSchedule.xml | 104 -- .../RunRailsimTest/test9/transitVehicles.xml | 29 - .../test0/config.xml | 44 - .../test0/transitNetwork.xml.gz | Bin 97116 -> 0 bytes .../test0/transitSchedule.xml.gz | Bin 72093 -> 0 bytes .../test0/transitVehicles.xml.gz | Bin 640 -> 0 bytes .../DistributeCapacitiesTest/test0/config.xml | 44 - .../test0/transitNetwork.xml.gz | Bin 97116 -> 0 bytes .../test0/transitSchedule.xml.gz | Bin 72093 -> 0 bytes .../test0/transitVehicles.xml.gz | Bin 640 -> 0 bytes .../SplitTransitLinksTest/test0/config.xml | 44 - .../test0/trainNetwork.xml | 57 - .../test0/transitSchedule.xml | 34 - .../test0/transitVehicles.xml | 35 - .../testRunRailsim/config.xml | 44 - 141 files changed, 13061 deletions(-) delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java delete mode 100644 contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java delete mode 100644 contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java delete mode 100644 contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/trainNetwork.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitSchedule.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitVehicles.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitNetwork.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitSchedule.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitVehicles.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitNetwork.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitSchedule.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitVehicles.xml.gz delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java deleted file mode 100644 index 72ae8a3751e..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/AdaptiveTrainSignalsControler.java +++ /dev/null @@ -1,385 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineSegment; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Node; -import org.matsim.core.controler.events.IterationEndsEvent; -import org.matsim.core.controler.listener.IterationEndsListener; -import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent; -import org.matsim.core.mobsim.framework.events.MobsimInitializedEvent; -import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener; -import org.matsim.core.mobsim.framework.listeners.MobsimInitializedListener; -import org.matsim.core.mobsim.qsim.interfaces.Netsim; -import org.matsim.core.mobsim.qsim.interfaces.SignalGroupState; -import org.matsim.core.mobsim.qsim.interfaces.SignalizeableItem; -import org.matsim.core.utils.io.IOUtils; -import org.matsim.vehicles.Vehicle; - -import javax.inject.Inject; -import java.io.BufferedWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * This class places a simple adaptive signal on each link and controls them. - *

- * A signal state for a link-to-link direction is changed depending on the available capacities on the next link. - * The signal control also makes sure that trains are not able to enter the same link in the same time step. - * - * @author Ihab Kaddoura - */ -public class AdaptiveTrainSignalsControler implements IterationEndsListener, MobsimInitializedListener, MobsimBeforeSimStepListener, TrainPathEntersLinkEventHandler, TrainLeavesLinkEventHandler { - - private final boolean printOutputs = true; - - private static final Logger log = LogManager.getLogger(AdaptiveTrainSignalsControler.class); - - @Inject - private Scenario scenario; - - private final Map, HashSet>> linkId2touchingVehicles = new HashMap<>(); - private final HashMap, SignalInfo> toLink2signalinfo = new HashMap<>(); - private final HashMap, SignalizeableItem> linkId2signal = new HashMap<>(); - - private final List signalInfosToVisualize = new ArrayList<>(); - private Map> nextCrossingTime2nodes = new HashMap<>(); - - private boolean atLeastOneLinkWithOppositeDirectionLink = false; - private boolean atLeastOneLinkWithMinimumTrainHeadway = false; - - @Override - public void notifyMobsimInitialized(MobsimInitializedEvent e) { - - Netsim mobsim = (Netsim) e.getQueueSimulation(); - - for (Link link : this.scenario.getNetwork().getLinks().values()) { - - // place a signal on all links - SignalizeableItem signal = (SignalizeableItem) mobsim.getNetsimNetwork().getNetsimLink(link.getId()); - signal.setSignalized(true); - this.linkId2signal.put(link.getId(), signal); - - // initialize ingoing links for each link - SignalInfo signalInfo = new SignalInfo(link, this.scenario.getNetwork()); - this.toLink2signalinfo.put(link.getId(), signalInfo); - - // initialize link utilization - this.linkId2touchingVehicles.put(link.getId(), new HashSet<>()); - - if (RailsimUtils.getOppositeDirectionLink(link, this.scenario.getNetwork()) != null) { - atLeastOneLinkWithOppositeDirectionLink = true; - } - - if (RailsimUtils.getMinimumTrainHeadwayTime(link) > 0.) { - atLeastOneLinkWithMinimumTrainHeadway = true; - } - - } - } - - @Override - public void reset(int iteration) { - if (iteration > 0) throw new RuntimeException("Running more than 1 iteration. Aborting..."); - } - - @Override - public void handleEvent(TrainLeavesLink event) { - - Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); - Id vehicleId = event.getVehicleId(); - -// log.warn("-"); -// log.warn("Train " + vehicleId + " leaves link " + link.getId() + " (time: " + event.getTime() + ")"); - - // bookkeeping: this train is no longer 'on' the link - this.linkId2touchingVehicles.get(link.getId()).remove(vehicleId); - - // 1) capacity condition - - int trainCapacity = RailsimUtils.getTrainCapacity(link); - - int vehCount = 0; - if (this.linkId2touchingVehicles.get(link.getId()) != null) { - vehCount = this.linkId2touchingVehicles.get(link.getId()).size(); - } - - if (vehCount == trainCapacity - 1) { - // There is capacity available on the link, update the signal condition for all inLinks... - this.toLink2signalinfo.get(link.getId()).changeCondition(SignalInfo.SignalCondition.linkCapacity, SignalGroupState.GREEN); - - // also make sure the from node is green for all other links - for (Link linkWithSameFromNode : link.getFromNode().getOutLinks().values()) { - if (linkWithSameFromNode != link) { - // not the same link - if (!isBlocked(linkWithSameFromNode)) { - this.toLink2signalinfo.get(linkWithSameFromNode.getId()).changeCondition(SignalInfo.SignalCondition.nodeCapacity, SignalGroupState.GREEN); - } - } - } - - } else { - // do nothing - } - - // 2) opposite direction - - if (vehCount == 0 && RailsimUtils.getOppositeDirectionLink(link, this.scenario.getNetwork()) != null) { - Id oppositeDirectionLink = this.toLink2signalinfo.get(link.getId()).getOppositeLink().getId(); - this.toLink2signalinfo.get(oppositeDirectionLink).changeCondition(SignalInfo.SignalCondition.oppositeDirection, SignalGroupState.GREEN); - } - - // 3) minimal train headway (for the toNode of the link) - - double minimumTime = RailsimUtils.getMinimumTrainHeadwayTime(link); - double earliestLeaveTime = event.getTime() + minimumTime; - - if (earliestLeaveTime > event.getTime()) { - for (Link toLink : link.getToNode().getOutLinks().values()) { - this.toLink2signalinfo.get(toLink.getId()).changeCondition(SignalInfo.SignalCondition.nodeMinimumHeadway, SignalGroupState.RED); - } - - if (this.nextCrossingTime2nodes.get(earliestLeaveTime) == null) { - Set nodes = new HashSet<>(); - nodes.add(link.getToNode()); - this.nextCrossingTime2nodes.put(earliestLeaveTime, nodes); - } else { - this.nextCrossingTime2nodes.get(earliestLeaveTime).add(link.getToNode()); - } - -// log.warn("Node " + link.getToNode().getId() + " blocked until " + earliestLeaveTime); - } - - } - - @Override - public void handleEvent(TrainPathEntersLink event) { - - Id vehicleId = event.getVehicleId(); - Id linkId = event.getLinkId(); - Link link = this.scenario.getNetwork().getLinks().get(linkId); - -// log.warn("-------"); -// log.warn("Fahrweg of " + vehicleId + " enters link " + linkId); - - // bookkeeping: register the train 'on' the link - this.linkId2touchingVehicles.get(linkId).add(vehicleId); - - // 1) capacity condition - - if (isBlocked(link)) { - this.toLink2signalinfo.get(link.getId()).changeCondition(SignalInfo.SignalCondition.linkCapacity, SignalGroupState.RED); - - // also make sure the from node is blocked for all other links - for (Link linkWithSameFromNode : link.getFromNode().getOutLinks().values()) { - if (linkWithSameFromNode != link) { - // not the same link - this.toLink2signalinfo.get(linkWithSameFromNode.getId()).changeCondition(SignalInfo.SignalCondition.nodeCapacity, SignalGroupState.RED); - - } - } - } - - // 2) opposite direction - - if (RailsimUtils.getOppositeDirectionLink(link, this.scenario.getNetwork()) != null) { - Id oppositeDirectionLink = this.toLink2signalinfo.get(link.getId()).getOppositeLink().getId(); - this.toLink2signalinfo.get(oppositeDirectionLink).changeCondition(SignalInfo.SignalCondition.oppositeDirection, SignalGroupState.RED); - } - } - - /** - * @param link - * @return - */ - private boolean isBlocked(Link link) { - int trainCapacity = RailsimUtils.getTrainCapacity(link); - - int vehCount = 0; - if (this.linkId2touchingVehicles.get(link.getId()) != null) { - vehCount = this.linkId2touchingVehicles.get(link.getId()).size(); - } - if (vehCount == trainCapacity) { - return true; - } else if (vehCount < trainCapacity) { - return false; - } else { - log.warn("+++++++++++++++++++++++++++++++++++++++++++++++++++++"); - log.warn("Link: " + link.getId()); - log.warn("Vehicles touching this link: " + this.linkId2touchingVehicles.get(link.getId())); - log.warn("trainCapacity: " + trainCapacity); - log.warn("attributes: " + link.getAttributes().toString()); - log.warn("+++++++++++++++++++++++++++++++++++++++++++++++++++++"); - throw new RuntimeException("More vehicles than allowed on link " + link.getId()); - } - } - - private void storeSignalStateInfo(double simulationTime, Link fromLink, Link toLink, SignalGroupState state) { - - // I want the signals to be visualized right before the end of the from link... - LineSegment ls = new LineSegment(fromLink.getFromNode().getCoord().getX(), fromLink.getFromNode().getCoord().getY(), fromLink.getToNode().getCoord().getX(), fromLink.getToNode().getCoord().getY()); - Coordinate point = ls.pointAlong(0.8); - - this.signalInfosToVisualize.add(simulationTime + ";" + point.getX() + ";" + point.getY() + ";" + toLink.getCoord().getX() + ";" + toLink.getCoord().getY() + ";" + state.toString()); - } - - @Override - public void notifyIterationEnds(IterationEndsEvent event) { - if (printOutputs) { - printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visSignalStates.csv", this.signalInfosToVisualize); - } - } - - private void printSignalInfos(String outputFile, List strings) { - - BufferedWriter writer = IOUtils.getBufferedWriter(outputFile); - - try { - writer.write("time;X;Y;toLinkX;toLinkY;state"); - writer.newLine(); - for (String line : strings) { - writer.write(line); - writer.newLine(); - } - writer.close(); - - log.info("Text info written to file."); - } catch (Exception e) { - log.warn("Text info not written to file."); - } - } - - @Override - public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) { - - double time = e.getSimulationTime(); - - // see if enough time has passed and we can update the headway condition - if (atLeastOneLinkWithMinimumTrainHeadway) updateHeadwaySignalCondition(time); - - // update conflicts between toLinks - if (atLeastOneLinkWithOppositeDirectionLink) updateConflicts(); - - // update all signal states based on the current conditions - updateSignalStates(time); - } - - /** - * if there is a conflicting toLink, set the conflictingOppositeLink condition to Red for one of the conflicting toLinks - */ - private void updateConflicts() { - // update conflicting opposite links - this.toLink2signalinfo.values().stream().forEach(signalInfo -> { - if (signalInfo.getOppositeLink() != null) { - if (signalInfo.isConsiderInNextTimeStep()) { - signalInfo.changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.GREEN); - this.toLink2signalinfo.get(signalInfo.getOppositeLink().getId()).changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.RED); - // change for next time step - signalInfo.setConsiderInNextTimeStep(false); - - } else { - signalInfo.changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.RED); - this.toLink2signalinfo.get(signalInfo.getOppositeLink().getId()).changeCondition(SignalInfo.SignalCondition.conflictingOppositeLink, SignalGroupState.GREEN); - - // change for next time step - signalInfo.setConsiderInNextTimeStep(true); - } - } - }); - } - - /** - * Iterate through all signals and see where all conditions are green... - *

- * - if there is only one inLink, directly switch to green (there are no potential conflicts between any inLinks) - * - if there is more than one inLink, make sure there is only from link green (otherwise vehicles may enter a link in the same time step...) - * - * @param time - */ - private void updateSignalStates(double time) { - - // TODO: improve performance - // with parallel() I get some weird test failures... - // parallel().forEachOrdered() runs without any problems but does not improve performance - - this.toLink2signalinfo.values().stream().forEach(signalInfo -> { - - boolean demandOnAFromLink = false; - for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { - if (this.linkId2touchingVehicles.get(fromLinkInfo.getId()).size() > 0) { - demandOnAFromLink = true; - break; - } - } - - if (demandOnAFromLink) { - if (signalInfo.allConditionsGreen()) { - if (signalInfo.getFromLink2fromLinkInfo().size() <= 1) { - // no potential conflict, set for all ingoing links to green - for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { - switchSignalState(time, fromLinkInfo, signalInfo.getToLink(), SignalGroupState.GREEN); - } - } else { - // potential conflict, only set to green for one inlink - Link fromLink = signalInfo.getNextFromLink(); - switchSignalState(time, fromLink, signalInfo.getToLink(), SignalGroupState.GREEN); - - // and switch all other fromLinks to red. - for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { - if (fromLinkInfo != fromLink) { - switchSignalState(time, fromLinkInfo, signalInfo.getToLink(), SignalGroupState.RED); - } - } - } - } else { -// log.info("At least one Red condition. Setting to red: from link: " + fromLinkInfo.getFromLink().getId() + " --- to link: " + signalInfo.getToLink().getId()); - for (Link fromLinkInfo : signalInfo.getFromLink2fromLinkInfo()) { - switchSignalState(time, fromLinkInfo, signalInfo.getToLink(), SignalGroupState.RED); - } - } - } - - }); - } - - /** - * @param time - */ - private void updateHeadwaySignalCondition(double time) { - if (this.nextCrossingTime2nodes.get(time) != null) { - for (Node node : this.nextCrossingTime2nodes.get(time)) { - for (Link toLink : node.getOutLinks().values()) { - this.toLink2signalinfo.get(toLink.getId()).changeCondition(SignalInfo.SignalCondition.nodeMinimumHeadway, SignalGroupState.GREEN); - } - } - this.nextCrossingTime2nodes.remove(time); - } - } - - private void switchSignalState(double time, Link fromLink, Link toLink, SignalGroupState state) { - boolean previousSignalStateIsGreen = this.linkId2signal.get(fromLink.getId()).hasGreenForToLink(toLink.getId()); - - if (previousSignalStateIsGreen && state == SignalGroupState.GREEN) { - // was already green, nothing to do! -// log.warn("Signal was already green and was then requested to be changed to green again..." + fromLink.getId() + " -> " + toLink.getId()); - } else if (!previousSignalStateIsGreen && state == SignalGroupState.RED) { - // was already red, nothing to do! -// log.warn("Signal was already red and was then requested to be changed to red again..." + fromLink.getId() + " -> " + toLink.getId()); - } else { - // the signal state changes from green to red or from red to green -// log.info("change signal state: " + fromLink.getId() + " --> " + toLink.getId() + ":" + state.toString()); - this.linkId2signal.get(fromLink.getId()).setSignalStateForTurningMove(state, toLink.getId()); - if (printOutputs) this.storeSignalStateInfo(time, fromLink, toLink, state); - } - - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java deleted file mode 100644 index 39451ba305a..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/LinkUsageVisualizer.java +++ /dev/null @@ -1,153 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.controler.events.IterationEndsEvent; -import org.matsim.core.controler.listener.IterationEndsListener; -import org.matsim.core.mobsim.qsim.interfaces.SignalGroupState; -import org.matsim.core.utils.io.IOUtils; -import org.matsim.vehicles.Vehicle; - -import javax.inject.Inject; -import java.io.BufferedWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -/** - * This class writes out data to visualize which link is touched or even blocked by trains. - * - * @author Ihab Kaddoura - */ -public class LinkUsageVisualizer implements IterationEndsListener, TrainPathEntersLinkEventHandler, TrainLeavesLinkEventHandler, TrainEntersLinkEventHandler { - - private static final Logger log = LogManager.getLogger(LinkUsageVisualizer.class); - - @Inject - private Scenario scenario; - - private final Map, HashSet>> linkId2touchingTrainPaths = new HashMap<>(); - private final Map, HashSet>> linkId2touchingTrains = new HashMap<>(); - - private final List blockedLinksInfosToVisualize = new ArrayList<>(); - private final List touchedLinksByTrainInfosToVisualize = new ArrayList<>(); - private final List touchedLinksCountToVisualize = new ArrayList<>(); - private final List touchedLinksToVisualize = new ArrayList<>(); - - @Override - public void handleEvent(TrainPathEntersLink event) { - - HashSet> touchingTrainPaths = this.linkId2touchingTrainPaths.getOrDefault(event.getLinkId(), new HashSet<>()); - touchingTrainPaths.add(event.getVehicleId()); - this.linkId2touchingTrainPaths.put(event.getLinkId(), touchingTrainPaths); - - Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); - - this.touchedLinksCountToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + touchingTrainPaths.size()); - - if (touchingTrainPaths.size() == 1) { - this.touchedLinksToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.RED.toString()); - } - - if (isBlocked(link)) { - // the link is blocked - this.blockedLinksInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.RED.toString()); - - } - } - - @Override - public void handleEvent(TrainEntersLink event) { - - HashSet> touchingTrains = this.linkId2touchingTrains.getOrDefault(event.getLinkId(), new HashSet<>()); - touchingTrains.add(event.getVehicleId()); - this.linkId2touchingTrains.put(event.getLinkId(), touchingTrains); - - Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); - - if (touchingTrains.size() == 1) { - this.touchedLinksByTrainInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.RED.toString()); - } - } - - @Override - public void handleEvent(TrainLeavesLink event) { - - Link link = this.scenario.getNetwork().getLinks().get(event.getLinkId()); - - HashSet> touchingTrainPaths = this.linkId2touchingTrainPaths.getOrDefault(event.getLinkId(), new HashSet<>()); - touchingTrainPaths.remove(event.getVehicleId()); - this.linkId2touchingTrainPaths.put(event.getLinkId(), touchingTrainPaths); - - this.touchedLinksCountToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + touchingTrainPaths.size()); - - int trainCapacity = RailsimUtils.getTrainCapacity(link); - - if (touchingTrainPaths.size() == trainCapacity - 1) { - // the link is no longer blocked - this.blockedLinksInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.GREEN.toString()); - } - - if (touchingTrainPaths.size() == 0) { - this.touchedLinksToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.GREEN.toString()); - } - - HashSet> touchingTrains = this.linkId2touchingTrains.getOrDefault(event.getLinkId(), new HashSet<>()); - touchingTrains.remove(event.getVehicleId()); - this.linkId2touchingTrains.put(event.getLinkId(), touchingTrains); - - if (touchingTrains.size() == 0) { - this.touchedLinksByTrainInfosToVisualize.add(event.getTime() + ";" + link.getFromNode().getCoord().getX() + ";" + link.getFromNode().getCoord().getY() + ";" + link.getToNode().getCoord().getX() + ";" + link.getToNode().getCoord().getY() + ";" + SignalGroupState.GREEN.toString()); - } - } - - private boolean isBlocked(Link link) { - int trainCapacity = RailsimUtils.getTrainCapacity(link); - - int vehCount = 0; - if (this.linkId2touchingTrainPaths.get(link.getId()) != null) { - vehCount = this.linkId2touchingTrainPaths.get(link.getId()).size(); - } - if (vehCount == trainCapacity) { - return true; - } else if (vehCount < trainCapacity) { - return false; - } else { - throw new RuntimeException("More vehicles than allowed on link " + link.getId()); - } - } - - @Override - public void notifyIterationEnds(IterationEndsEvent event) { - printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visTouchedLinks.csv", this.touchedLinksToVisualize); - printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visBlockedLinks.csv", this.blockedLinksInfosToVisualize); - printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visTouchedLinksByTrain.csv", this.touchedLinksByTrainInfosToVisualize); - printSignalInfos(this.scenario.getConfig().controler().getOutputDirectory() + "/" + this.scenario.getConfig().controler().getRunId() + ".visTouchedLinksCount.csv", this.touchedLinksCountToVisualize); - - } - - private void printSignalInfos(String outputFile, List strings) { - - BufferedWriter writer = IOUtils.getBufferedWriter(outputFile); - - try { - writer.write("time;fromX;fromY;toX;toY;state"); - writer.newLine(); - for (String line : strings) { - writer.write(line); - writer.newLine(); - } - writer.close(); - - log.info("Text info written to file."); - } catch (Exception e) { - log.warn("Text info not written to file."); - } - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java deleted file mode 100644 index 90e3acb175f..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimConfigGroup.java +++ /dev/null @@ -1,192 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.core.config.Config; -import org.matsim.core.config.ReflectiveConfigGroup; - -import java.util.Arrays; -import java.util.Map; - - -/** - * Provides the parameters for railsim. - * - * @author Ihab Kaddoura - */ -public final class RailsimConfigGroup extends ReflectiveConfigGroup { - public static final String GROUP_NAME = "railsim"; - private static final String REACTION_TIME = "reactionTime"; - private static final String GRAVITY = "gravity"; - private static final String DECELERATION = "deceleration"; - private static final String ACCELERATION = "acceleration"; - private static final String GRADE = "grade"; - private static final String ADJUST_NETWORK_TO_SCHEDULE = "adjustNetworkToSchedule"; - private static final String ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT = "abortIfVehicleMaxVelocityIsViolated"; - private static final String SPLIT_LINKS = "splitLinks"; - private static final String SPLIT_LINKS_LENGTH = "splitLinksLength"; - private static final String LINK_FREESPEED_APPROACH = "linkFreespeedApproach"; - private static final String TRAIN_ACCELERATION_APPROACH = "trainAccelerationApproach"; - - public enum TrainSpeedApproach {constantValue, fromLinkAttributesForEachVehicleType, fromLinkAttributesForEachLine, fromLinkAttributesForEachLineAndRoute} - - public enum TrainAccelerationApproach {without, euclideanDistanceBetweenStops, speedOnPreviousLink} - - public RailsimConfigGroup() { - super(GROUP_NAME); - } - - private static final Logger log = LogManager.getLogger(RailsimConfigGroup.class); - - private double reactionTime = 1.5; // seconds - private double gravity = 9.81; // meters per second squared - private double decelerationGlobalDefault = 0.5; // meters per second squared - private double accelerationGlobalDefault = 0.5; // meters per second squared - private double gradeGlobalDefault = 0.0; // percent - - private boolean adjustNetworkToSchedule = false; - private boolean abortIfVehicleMaxVelocityIsViolated = true; - - private boolean splitLinks = false; - private double splitLinksLength = 100.; - private TrainSpeedApproach trainSpeedApproach = TrainSpeedApproach.constantValue; - private TrainAccelerationApproach trainAccelerationApproach = TrainAccelerationApproach.without; - - @Override - public Map getComments() { - Map comments = super.getComments(); - comments.put(REACTION_TIME, "Reaction time in seconds which is used to compute the reserved train path."); - comments.put(GRAVITY, "Gravity in meters per second^2 which is used to compute the reserved train path."); - comments.put(DECELERATION, "Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link."); - comments.put(ACCELERATION, "Global acceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_ACCELERATION + ");" + " used to compute the train velocity per link."); - comments.put(GRADE, "Global grade in percentage which is used if there is no value provided in the link attributes (" + RailsimUtils.LINK_ATTRIBUTE_GRADE + ");" + " used to compute the reserved train path."); - comments.put(ADJUST_NETWORK_TO_SCHEDULE, "Set to 'true' to adjust the network to the travel times provided in the transit schedule. " + "There is no guarantee that all delays are eliminiated."); - comments.put(ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT, "Set to 'true' to throw a runtime exception if the schedule requires a speed which exceeds the vehicle maximum velocity. " + "Set to 'false' to continue anyway and only print a warning. For 'false', there is no guarantee that all delays are eliminiated."); - comments.put(SPLIT_LINKS, "Set to 'true' to split links into smaller link segments."); - comments.put(SPLIT_LINKS_LENGTH, "The (approximate) maximum length of link segments. Links with a higher distance will be split into smaller links."); - comments.put(LINK_FREESPEED_APPROACH, "The freespeed calculation approach: " + Arrays.toString(TrainSpeedApproach.values()) + ". " + TrainSpeedApproach.constantValue + " is the matsim default approach: a constant freespeed value for all vehicles. " + TrainSpeedApproach.fromLinkAttributesForEachVehicleType + " uses vehicle type-specific freespeed values provided in the link attributes. " + TrainSpeedApproach.fromLinkAttributesForEachLine + " uses transit line-specific freespeed values provided in the link attributes. " + TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute + " uses transit line- and route-specific freespeed values provided in the link attributes. "); - comments.put(TRAIN_ACCELERATION_APPROACH, "The acceleration calculation approach: " + Arrays.toString(TrainAccelerationApproach.values()) + ". " + TrainAccelerationApproach.without + " is the matsim default (The acceleration and deceleration is infinite). " + TrainAccelerationApproach.euclideanDistanceBetweenStops + " accounts for the euclidean distance between the train and the previous stop (acceleration) and the train and the next stop (decelearion). " + TrainAccelerationApproach.speedOnPreviousLink + " EXPERIMENTAL: accounts for the speed on the previous link and computes the acceleration based on the distance of the current link."); - return comments; - } - - // ######################################################################################################## - - @Override - protected void checkConsistency(Config config) { - - log.info("Checking consistency in train config group..."); - // TODO: check parameter consistency. - } - - @StringGetter(REACTION_TIME) - public double getReactionTime() { - return reactionTime; - } - - @StringSetter(REACTION_TIME) - public void setReactionTime(double reactionTime) { - this.reactionTime = reactionTime; - } - - @StringGetter(GRAVITY) - public double getGravity() { - return gravity; - } - - @StringSetter(GRAVITY) - public void setGravity(double gravity) { - this.gravity = gravity; - } - - @StringGetter(DECELERATION) - public double getDecelerationGlobalDefault() { - return decelerationGlobalDefault; - } - - @StringSetter(DECELERATION) - public void setDecelerationGlobalDefault(double deceleration) { - this.decelerationGlobalDefault = deceleration; - } - - @StringGetter(GRADE) - public double getGradeGlobalDefault() { - return gradeGlobalDefault; - } - - @StringSetter(GRADE) - public void setGradeGlobalDefault(double grade) { - this.gradeGlobalDefault = grade; - } - - @StringGetter(ADJUST_NETWORK_TO_SCHEDULE) - public boolean isAdjustNetworkToSchedule() { - return adjustNetworkToSchedule; - } - - @StringSetter(ADJUST_NETWORK_TO_SCHEDULE) - public void setAdjustNetworkToSchedule(boolean adjustNetworkToSchedule) { - this.adjustNetworkToSchedule = adjustNetworkToSchedule; - } - - @StringGetter(SPLIT_LINKS) - public boolean isSplitLinks() { - return splitLinks; - } - - @StringSetter(SPLIT_LINKS) - public void setSplitLinks(boolean splitLinks) { - this.splitLinks = splitLinks; - } - - @StringGetter(SPLIT_LINKS_LENGTH) - public double getSplitLinksLength() { - return splitLinksLength; - } - - @StringSetter(SPLIT_LINKS_LENGTH) - public void setSplitLinksLength(double splitLinksLength) { - this.splitLinksLength = splitLinksLength; - } - - @StringGetter(ACCELERATION) - public double getAccelerationGlobalDefault() { - return accelerationGlobalDefault; - } - - @StringSetter(ACCELERATION) - public void setAccelerationGlobalDefault(double accelerationGlobalDefault) { - this.accelerationGlobalDefault = accelerationGlobalDefault; - } - - @StringGetter(LINK_FREESPEED_APPROACH) - public TrainSpeedApproach getTrainSpeedApproach() { - return trainSpeedApproach; - } - - @StringSetter(LINK_FREESPEED_APPROACH) - public void setTrainSpeedApproach(TrainSpeedApproach trainSpeedApproach) { - this.trainSpeedApproach = trainSpeedApproach; - } - - @StringGetter(ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT) - public boolean isAbortIfVehicleMaxVelocityIsViolated() { - return abortIfVehicleMaxVelocityIsViolated; - } - - @StringSetter(ADJUST_NETWORK_TO_SCHEDULE_VELOCITY_ABORT) - public void setAbortIfVehicleMaxVelocityIsViolated(boolean abortIfVehicleMaxVelocityIsViolated) { - this.abortIfVehicleMaxVelocityIsViolated = abortIfVehicleMaxVelocityIsViolated; - } - - @StringGetter(TRAIN_ACCELERATION_APPROACH) - public TrainAccelerationApproach getTrainAccelerationApproach() { - return trainAccelerationApproach; - } - - @StringSetter(TRAIN_ACCELERATION_APPROACH) - public void setTrainAccelerationApproach(TrainAccelerationApproach trainAccelerationApproach) { - this.trainAccelerationApproach = trainAccelerationApproach; - } - - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java deleted file mode 100644 index 42ee94d92e0..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculator.java +++ /dev/null @@ -1,12 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.mobsim.qsim.qnetsimengine.linkspeedcalculator.LinkSpeedCalculator; -import org.matsim.vehicles.Vehicle; - -/** - * interface so that this can be injected - */ -public interface RailsimLinkSpeedCalculator extends LinkSpeedCalculator { - double getRailsimMaximumVelocity(Vehicle vehicle, Link link, double time); -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java deleted file mode 100644 index f1c38f0deb8..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimLinkSpeedCalculatorImpl.java +++ /dev/null @@ -1,203 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import com.google.inject.Inject; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Coord; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.events.TransitDriverStartsEvent; -import org.matsim.api.core.v01.events.handler.TransitDriverStartsEventHandler; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.mobsim.qsim.qnetsimengine.DefaultLinkSpeedCalculator; -import org.matsim.core.mobsim.qsim.qnetsimengine.QVehicle; -import org.matsim.core.network.NetworkUtils; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; -import org.matsim.vehicles.Vehicle; - -import java.util.HashSet; -import java.util.Set; - -/** - * A simple link speed calculator which accounts for the acceleration and deceleration of vehicles. - * - * @author Ihab Kaddoura - */ -public class RailsimLinkSpeedCalculatorImpl implements TransitDriverStartsEventHandler, RailsimLinkSpeedCalculator { - private static final Logger log = LogManager.getLogger(RailsimLinkSpeedCalculatorImpl.class); - - Set> transitVehicles = new HashSet<>(); - - @Inject - Scenario scenario; - - @Inject - TrainStatistics statistics; - - /** - * TODO: This was changed during merge and is now untested. - */ - @Inject - DefaultLinkSpeedCalculator defaultLinkSpeedCalculator; - - @Override - public double getMaximumVelocity(QVehicle vehicle, Link link, double time) { - if (isTrain(vehicle)) { - return getRailsimMaximumVelocity(vehicle.getVehicle(), link, time); - } else { - return defaultLinkSpeedCalculator.getMaximumVelocity(vehicle, link, time); - } - } - - @Override - public double getRailsimMaximumVelocity(Vehicle vehicle, Link link, double time) { - - RailsimConfigGroup railSimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); - - double freespeed = Double.MIN_VALUE; - - { - final double defaultFreespeed = Math.min(vehicle.getType().getMaximumVelocity(), link.getFreespeed(time)); - if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.constantValue) { - freespeed = defaultFreespeed; - - } else if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachVehicleType) { - freespeed = RailsimUtils.getLinkFreespeedForVehicleType(vehicle.getType().getId(), link); - - } else if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine) { - Id line = statistics.getVehicleId2currentTransitLine().get(vehicle.getId()); - freespeed = RailsimUtils.getLinkFreespeedForTransitLine(line, link); - - } else if (railSimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute) { - Id line = statistics.getVehicleId2currentTransitLine().get(vehicle.getId()); - Id route = statistics.getVehicleId2currentTransitRoute().get(vehicle.getId()); - - freespeed = RailsimUtils.getLinkFreespeedForTransitLineAndTransitRoute(line, route, link); - - } else { - throw new RuntimeException("Unknown train speed approach. Aborting..."); - } - - if (freespeed <= 0) { - // use the default if no information is provided in the link attributes... - freespeed = defaultFreespeed; - } - } - - // now, account for acceleration and so on - - if (railSimConfigGroup.getTrainAccelerationApproach() == RailsimConfigGroup.TrainAccelerationApproach.without) { - return freespeed; - - } else if (railSimConfigGroup.getTrainAccelerationApproach() == RailsimConfigGroup.TrainAccelerationApproach.euclideanDistanceBetweenStops) { - - final double beelineDistanceFactor = 1.3; - - Id lastStopId = statistics.getVehicle2lastStop().get(vehicle.getId()); - TransitStopFacility lastStop = scenario.getTransitSchedule().getFacilities().get(lastStopId); - Coord lastStopCoord = scenario.getNetwork().getLinks().get(lastStop.getLinkId()).getCoord(); - - final double minimumDistanceNonZero = 1.; - double distanceFromLastStop = Math.max(minimumDistanceNonZero, NetworkUtils.getEuclideanDistance(lastStopCoord, link.getCoord()) * beelineDistanceFactor); - double speedWithConsiderationOfAcceleration = getSpeedAfterAccelerating(distanceFromLastStop, vehicle, railSimConfigGroup); - - Id nextStopId = statistics.getVehicle2nextStop().get(vehicle.getId()); - double speedWithConsiderationOfDeceleration = Double.MAX_VALUE; - if (nextStopId == null) { - // train at final stop of transit line, the vehicle may be required for the next cycle in a different transit route... - } else { - TransitStopFacility nextStop = scenario.getTransitSchedule().getFacilities().get(nextStopId); - Coord nextStopCoord = scenario.getNetwork().getLinks().get(nextStop.getLinkId()).getCoord(); - double distanceToNextStop = Math.max(minimumDistanceNonZero, NetworkUtils.getEuclideanDistance(link.getCoord(), nextStopCoord) * beelineDistanceFactor); - speedWithConsiderationOfDeceleration = getSpeedBeforeDecelerating(distanceToNextStop, vehicle, railSimConfigGroup); - } - double maxVehicleVelocityWithAccelerationAndDeceleration = Math.min(speedWithConsiderationOfAcceleration, speedWithConsiderationOfDeceleration); - - if (freespeed == 0. || maxVehicleVelocityWithAccelerationAndDeceleration == 0.) { - throw new RuntimeException("Velocity is 0. Aborting..."); - } - // make sure the infrastructure limitations are taken into consideration - double velocity = Math.min(freespeed, maxVehicleVelocityWithAccelerationAndDeceleration); -// log.info("vehicle: " + vehicle.getId() + " / link: " + link.getId() + "/ velocity: " + velocity); - return velocity; - - } else if (railSimConfigGroup.getTrainAccelerationApproach() == RailsimConfigGroup.TrainAccelerationApproach.speedOnPreviousLink) { - - // TODO: very experimental, needs tests etc. - // The idea is to start with the speed on the previous link (or: when entering the current link). - // The previous speed is then increased based on the acceleration and length of the current link. - // Unclear: What do we do about the deceleration? - // Unclear: The speed calculation is done for the front of the train path... and the front of the train is computed based on the - double speedOnPreviousLink = statistics.getSpeedOnPreviousLink(vehicle.getId()); - double speedWithConsiderationOfAcceleration = getSpeedAfterAcceleratingFromPreviousSpeed(speedOnPreviousLink, link.getLength(), vehicle, railSimConfigGroup); - - if (freespeed == 0. || speedWithConsiderationOfAcceleration == 0.) { - throw new RuntimeException("Velocity is 0. Aborting..."); - } - - // make sure the infrastructure limitations are taken into consideration - double velocity = Math.min(freespeed, speedWithConsiderationOfAcceleration); -// log.info("vehicle: " + vehicle.getId() + " / link: " + link.getId() + "/ velocity: " + velocity); - return velocity; - - } else { - throw new RuntimeException("Unknown train acceleration approach. Aborting..."); - } - - } - - /** - * @param distanceToNextStop - * @param vehicle - * @param railSimConfigGroup - * @return - */ - private double getSpeedBeforeDecelerating(double distanceToNextStop, Vehicle vehicle, RailsimConfigGroup railSimConfigGroup) { - double deceleration = RailsimUtils.getTrainDeceleration(vehicle, railSimConfigGroup); - double v = Math.sqrt(distanceToNextStop * 2 * deceleration); - double maxVelocityVehicle = vehicle.getType().getMaximumVelocity(); - return Math.min(v, maxVelocityVehicle); - } - - /** - * @param distanceFromLastStop - * @param vehicle - * @param railSimConfigGroup s = a/2 * t^2 - * a = v/t - * --> v = sqrt(s * 2 * a) - * @return - */ - private double getSpeedAfterAccelerating(double distanceFromLastStop, Vehicle vehicle, RailsimConfigGroup railSimConfigGroup) { - double acceleration = RailsimUtils.getTrainAcceleration(vehicle, railSimConfigGroup); - double v = Math.sqrt(distanceFromLastStop * 2 * acceleration); - double maxVelocityVehicle = vehicle.getType().getMaximumVelocity(); - return Math.min(v, maxVelocityVehicle); - } - - /** - * s = a/2 * t^2 - * a = (v1 - v0) / (t1 - t0) - * --> v1 = v0 + sqrt(s * 2 * a) - * - * @return - */ - private double getSpeedAfterAcceleratingFromPreviousSpeed(double previousSpeed, double distance, Vehicle vehicle, RailsimConfigGroup railSimConfigGroup) { - double acceleration = RailsimUtils.getTrainAcceleration(vehicle, railSimConfigGroup); - double v1 = previousSpeed + Math.sqrt(distance * 2 * acceleration); - double maxVelocityVehicle = vehicle.getType().getMaximumVelocity(); - return Math.min(v1, maxVelocityVehicle); - } - - private boolean isTrain(QVehicle vehicle) { - return this.transitVehicles.contains(vehicle.getVehicle().getId()); - } - - @Override - public void handleEvent(TransitDriverStartsEvent event) { - this.transitVehicles.add(event.getVehicleId()); - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java deleted file mode 100644 index 5f65dceb358..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimSignalsQSimModule.java +++ /dev/null @@ -1,35 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import com.google.inject.Inject; -import com.google.inject.Provider; -import org.matsim.api.core.v01.Scenario; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.mobsim.qsim.AbstractQSimModule; -import org.matsim.core.mobsim.qsim.qnetsimengine.QNetworkFactory; -import org.matsim.core.mobsim.qsim.qnetsimengine.QRailsimSignalsNetworkFactory; - -class RailsimSignalsQSimModule extends AbstractQSimModule { - @Override - protected void configureQSim() { - this.bind(QNetworkFactory.class).toProvider(QNetworkFactoryProvider.class); - } - - static class QNetworkFactoryProvider implements Provider { - - @Inject - private Scenario scenario; - - @Inject - private EventsManager events; - - @Inject - private RailsimLinkSpeedCalculator calculator; - - @Override - public QNetworkFactory get() { - final QRailsimSignalsNetworkFactory factory = new QRailsimSignalsNetworkFactory(scenario, events); - factory.setLinkSpeedCalculator(calculator); - return factory; - } - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java deleted file mode 100644 index 511c2b80cb1..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RailsimUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Network; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.vehicles.Vehicle; -import org.matsim.vehicles.VehicleType; - -/** - * @author Ihab Kaddoura - */ -public final class RailsimUtils { - - // link - public static final String LINK_ATTRIBUTE_GRADE = "grade"; - public static final String LINK_ATTRIBUTE_OPPOSITE_DIRECTION = "trainOppositeDirectionLink"; - public static final String LINK_ATTRIBUTE_CAPACITY = "trainCapacity"; - public static final String LINK_ATTRIBUTE_MAX_SPEED = "maxSpeed"; - public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "minimumTime"; - // vehicle - public static final String VEHICLE_ATTRIBUTE_MAX_DECELERATION = "maxDeceleration"; - public static final String VEHICLE_ATTRIBUTE_MAX_ACCELERATION = "maxAcceleration"; - - private RailsimUtils() { - } - - /** - * @param link - * @return the train capacity for this link, if no link attribute is provided the default is 1. - */ - public static int getTrainCapacity(Link link) { - int trainCapacity = 1; - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY) != null) { - trainCapacity = (Integer) link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY); - } - return trainCapacity; - } - - /** - * @param link - * @return the minimum time for the switch at the end of the link (toNode); if no link attribute is provided the default is 0. - */ - public static double getMinimumTrainHeadwayTime(Link link) { - double minimumTime = 0.; - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME) != null) { - minimumTime = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME); - } - return minimumTime; - } - - /** - * @return the default deceleration time or the vehicle-specific value - */ - public static double getTrainDeceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { - double deceleration = railsimConfigGroup.getDecelerationGlobalDefault(); - if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION) != null) { - deceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION); - } - return deceleration; - } - - /** - * @return the default acceleration time or the vehicle-specific value - */ - public static double getTrainAcceleration(Vehicle vehicle, RailsimConfigGroup railsimConfigGroup) { - double acceleration = railsimConfigGroup.getAccelerationGlobalDefault(); - if (vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION) != null) { - acceleration = (Double) vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION); - } - return acceleration; - } - - /** - * @return the default acceleration time or the vehicle-specific value - */ - public static double getGrade(Link link, RailsimConfigGroup railsimConfigGroup) { - double grade = railsimConfigGroup.getGradeGlobalDefault(); - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE) != null) { - grade = (Double) link.getAttributes().getAttribute(LINK_ATTRIBUTE_GRADE); - } - return grade; - } - - /** - * @param type - * @param link - * @return the vehicle-specific freespeed or 0 if there is no vehicle-specific freespeed provided in the link attributes - */ - public static double getLinkFreespeedForVehicleType(Id type, Link link) { - Object attribute = link.getAttributes().getAttribute(type.toString()); - if (attribute == null) { - return 0.; - } else { - return (double) attribute; - } - } - - /** - * @param line - * @param link - * @return the line-specific freespeed or 0 if there is no line-specific freespeed provided in the link attributes - */ - public static double getLinkFreespeedForTransitLine(Id line, Link link) { - Object attribute = link.getAttributes().getAttribute(line.toString()); - if (attribute == null) { - return 0.; - } else { - return (double) attribute; - } - } - - /** - * @param line - * @param route - * @param link - * @return the line- and route-specific freespeed or 0 if there is no line- and route-specific freespeed provided in the link attributes - */ - public static double getLinkFreespeedForTransitLineAndTransitRoute(Id line, Id route, Link link) { - Object attribute = link.getAttributes().getAttribute(line.toString() + "+++" + route.toString()); - if (attribute == null) { - return 0.; - } else { - return (double) attribute; - } - } - - public static Id getOppositeDirectionLink(Link link, Network network) { - if (link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION) == null) { - return null; - } else { - String oppositeLink = (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_OPPOSITE_DIRECTION); - return Id.createLinkId(oppositeLink); - } - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java deleted file mode 100644 index 0d1c191a787..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsim.java +++ /dev/null @@ -1,158 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import ch.sbb.matsim.contrib.railsim.prototype.prepare.AdjustNetworkToSchedule; -import ch.sbb.matsim.contrib.railsim.prototype.prepare.SplitTransitLinks; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.config.Config; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.config.groups.QSimConfigGroup.LinkDynamics; -import org.matsim.core.controler.AbstractModule; -import org.matsim.core.controler.Controler; -import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; -import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.vehicles.VehicleType; - -/** - * @author Ihab Kaddoura - */ -public final class RunRailsim { - private static final Logger log = LogManager.getLogger(RunRailsim.class); - - private RunRailsim() { - } - - public static void main(String[] args) { - log.info("Arguments:"); - for (String arg : args) { - log.info(arg); - } - log.info("---"); - - Config config = prepareConfig(args); - Scenario scenario = prepareScenario(config); - Controler controler = prepareControler(scenario); - - controler.run(); - } - - public static Config prepareConfig(String[] args) { - Config config = ConfigUtils.loadConfig(args); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - config.qsim().setUsingFastCapacityUpdate(false); - config.qsim().setTimeStepSize(1.); - config.qsim().setLinkDynamics(LinkDynamics.PassingQ); - config.qsim().setNumberOfThreads(1); - - // Storage capacities are handled via signals. - // To avoid any additional restrictions, the storage capacity is set to a very large value. - if (config.qsim().getStorageCapFactor() < 9999999.) { - log.warn("Storage capacities are handled via signals. To avoid any additional restrictions, the storage capacity factor is set to 9999999."); - config.qsim().setStorageCapFactor(9999999.); - } - - if (config.qsim().getFlowCapFactor() != 1.0) { - log.warn("The signals need at least one time step to react to the movements of transit vehicles. To enforce this, the flow capacity factor needs to be set to 1.0."); - config.qsim().setFlowCapFactor(1.0); - } - - return config; - } - - public static Scenario prepareScenario(Config config) { - Scenario scenario = ScenarioUtils.loadScenario(config); - double timeStepSize = config.qsim().getTimeStepSize(); - - // check links - for (Link link : scenario.getNetwork().getLinks().values()) { - - if (link.getCapacity() > 3600 / timeStepSize) { - log.warn("The signals need at least one time step to react to the movements of transit vehicles. Link capacities should be set to a maximum of 3600 vehicles per hour."); - link.setCapacity(3600. / timeStepSize); - } - - if (RailsimUtils.getOppositeDirectionLink(link, scenario.getNetwork()) != null && RailsimUtils.getTrainCapacity(link) > 1) { - - throw new RuntimeException("In the current version, one direction tracks only work for capacities = 1. " + "For capacities larger than 1, we have to think about the train disposition strategies to avoid deadlocks."); - } - - for (Link outLink : link.getFromNode().getOutLinks().values()) { - if (outLink == link) { - // same link - } else { - // another link - if (outLink.getFromNode() == link.getFromNode() && outLink.getToNode() == link.getToNode()) { - // overlaying link - throw new RuntimeException("Overlaying links with identical from and to node: " + outLink.getId() + " and " + link.getId() + ". " + "Please use separate nodes if you want to run trains on separate links, otherwise we have many potentially conflicting links " + "which increases the computation time. Aborting..."); - } - } - } - - } - - for (VehicleType vehicleType : scenario.getTransitVehicles().getVehicleTypes().values()) { - if (vehicleType.getPcuEquivalents() != 1.0) { - log.warn("The transit vehicle types are required to have a pcu equivalent of 1.0."); - vehicleType.setPcuEquivalents(1.0); - } - } - - return scenario; - } - - public static Controler prepareControler(Scenario scenario) { - - RailsimConfigGroup railsimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); - - if (railsimConfigGroup.isAdjustNetworkToSchedule()) { - AdjustNetworkToSchedule adjust = new AdjustNetworkToSchedule(scenario); - adjust.run(); - } - - if (railsimConfigGroup.isSplitLinks()) { - SplitTransitLinks splitTransitLinks = new SplitTransitLinks(scenario); - splitTransitLinks.run(railsimConfigGroup.getSplitLinksLength()); - } - - Controler controler = new Controler(scenario); - - controler.addOverridingModule(new AbstractModule() { - @Override - public void install() { - - // train statistics used in different classes - bind(TrainStatistics.class).asEagerSingleton(); - addEventHandlerBinding().to(TrainStatistics.class); - - // train expansion along several links - bind(SpatialTrainDimension.class).asEagerSingleton(); - addEventHandlerBinding().to(SpatialTrainDimension.class); - - // adaptive signal controler - bind(AdaptiveTrainSignalsControler.class).asEagerSingleton(); - addMobsimListenerBinding().to(AdaptiveTrainSignalsControler.class); - addEventHandlerBinding().to(AdaptiveTrainSignalsControler.class); - addControlerListenerBinding().to(AdaptiveTrainSignalsControler.class); - - // visualization output - bind(LinkUsageVisualizer.class).asEagerSingleton(); - addEventHandlerBinding().to(LinkUsageVisualizer.class); - addControlerListenerBinding().to(LinkUsageVisualizer.class); - - // for train acceleration / deceleration dynamics - bind(RailsimLinkSpeedCalculatorImpl.class).asEagerSingleton(); - bind(RailsimLinkSpeedCalculator.class).to(RailsimLinkSpeedCalculatorImpl.class); - addEventHandlerBinding().to(RailsimLinkSpeedCalculatorImpl.class); - - } - }); - - controler.addOverridingQSimModule(new RailsimSignalsQSimModule()); - - return controler; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java deleted file mode 100644 index 4c34be32f0b..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SignalInfo.java +++ /dev/null @@ -1,134 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - - -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Network; -import org.matsim.core.mobsim.qsim.interfaces.SignalGroupState; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * @author Ihab Kaddoura - */ -public class SignalInfo { - - private final Link toLink; - private final Link oppositeLink; - private final List fromLink2fromLinkInfo; - private final Map signalCondition2state; - - public enum SignalCondition {linkCapacity, nodeCapacity, nodeMinimumHeadway, oppositeDirection, conflictingOppositeLink} - - private Iterator iterator; - private boolean considerInNextTimeStep = true; - - /** - * @param considerInNextTimeStep the considerInNextTimeStep to set - */ - public void setConsiderInNextTimeStep(boolean considerInNextTimeStep) { - this.considerInNextTimeStep = considerInNextTimeStep; - } - - public SignalInfo(Link toLink, Network network) { - this.toLink = toLink; - - this.fromLink2fromLinkInfo = new ArrayList<>(); - - for (Link inLink : toLink.getFromNode().getInLinks().values()) { - if (inLink.getFromNode() == toLink.getToNode() && inLink.getToNode() == toLink.getFromNode()) { - // skip the inverse link (assuming that turning around at nodes is not possible) - } else { - fromLink2fromLinkInfo.add(inLink); - } - } - - if (RailsimUtils.getOppositeDirectionLink(toLink, network) != null) { - oppositeLink = network.getLinks().get(RailsimUtils.getOppositeDirectionLink(toLink, network)); - } else { - oppositeLink = null; - } - - iterator = this.fromLink2fromLinkInfo.iterator(); - - this.signalCondition2state = new HashMap<>(); - - // set initial conditions to green - this.signalCondition2state.put(SignalCondition.linkCapacity, SignalGroupState.GREEN); - this.signalCondition2state.put(SignalCondition.nodeCapacity, SignalGroupState.GREEN); - this.signalCondition2state.put(SignalCondition.nodeMinimumHeadway, SignalGroupState.GREEN); - this.signalCondition2state.put(SignalCondition.oppositeDirection, SignalGroupState.GREEN); - } - - /** - * Changes the state (GREEN/RED) of a signal condition (e.g. capacity, minimum time) - * - * @param condition - * @param state - */ - public void changeCondition(SignalCondition condition, SignalGroupState state) { - this.signalCondition2state.put(condition, state); - } - - /** - * Returns true if all signal conditions are green, otherwise false. - */ - public boolean allConditionsGreen() { - for (SignalGroupState state : this.signalCondition2state.values()) { - if (state == SignalGroupState.RED) { - return false; - } - } - return true; - } - - /** - * @return the signalCondition2state - */ - public Map getSignalCondition2state() { - return signalCondition2state; - } - - /** - * @return the oppositeLink - */ - public Link getOppositeLink() { - return oppositeLink; - } - - /** - * @return the toLink - */ - public Link getToLink() { - return toLink; - } - - /** - * @return the fromLink2fromLinkInfo - */ - public List getFromLink2fromLinkInfo() { - return fromLink2fromLinkInfo; - } - - /** - * @return the next fromLink using an iterator which always starts from the beginning - */ - public Link getNextFromLink() { - if (!iterator.hasNext()) { - // set the iterator to the beginning - iterator = this.fromLink2fromLinkInfo.iterator(); - } - return iterator.next(); - } - - /** - * @return the considerInNextTimeStep - */ - public boolean isConsiderInNextTimeStep() { - return considerInNextTimeStep; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java deleted file mode 100644 index 83d99eaaa0b..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/SpatialTrainDimension.java +++ /dev/null @@ -1,257 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -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.VehicleEntersTrafficEvent; -import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; -import org.matsim.api.core.v01.events.handler.VehicleEntersTrafficEventHandler; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.config.ConfigUtils; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitRouteStop; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; -import org.matsim.vehicles.Vehicle; -import org.matsim.vehicles.VehicleType; - -import javax.inject.Inject; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; - -/** - * This class computes the spatial dimension of trains along several links. - *

- * Processes default MATSim events (e.g. link enter events), accounts for the length of the train plus the reserved train path - * and then throws train enters and train leaves events. - * - * @author Ihab Kaddoura - */ -public class SpatialTrainDimension implements LinkEnterEventHandler, VehicleEntersTrafficEventHandler { - private static final Logger log = LogManager.getLogger(SpatialTrainDimension.class); - - @Inject - private Scenario scenario; - - @Inject - private EventsManager events; - - @Inject - private TrainStatistics statistics; - - @Inject - private RailsimLinkSpeedCalculator linkSpeedCalculator; - - private final HashMap, LinkedList>> vehicleId2currentlyTouchedLinksTotalSorted = new HashMap<>(); - - // in the following data container we don't need the actual queue or order of vehicles - private final HashMap, Set>> vehicleId2currentlyTouchedLinksByTrain = new HashMap<>(); - - private int warnCnt = 0; - - @Override - public void reset(int iteration) { - if (iteration > 0) throw new RuntimeException("Running more than 1 iteration. Aborting..."); - } - - @Override - public void handleEvent(LinkEnterEvent event) { - - // matsim vehicle is the tip of the train path (=fahrweg) - this.events.processEvent(new TrainPathEntersLink(event.getTime(), event.getLinkId(), event.getVehicleId())); - - // update the information about which links are touched and blocked (by the train and/or by the fahrweg) - updateBlockLinks(event.getLinkId(), event.getVehicleId(), event.getTime()); - } - - /** - * @param linkId - * @param vehicleId - * @param time - */ - private void updateBlockLinks(Id linkId, Id vehicleId, double time) { - - final LinkedList> touchedLinksSorted = this.vehicleId2currentlyTouchedLinksTotalSorted.computeIfAbsent(vehicleId, k -> new LinkedList<>()); - // add the just entered linkId to the 'queue' - touchedLinksSorted.addFirst(linkId); - - final Set> touchedLinksByTrain = this.vehicleId2currentlyTouchedLinksByTrain.computeIfAbsent(vehicleId, k -> new HashSet<>()); - - VehicleType vehicleType = this.scenario.getTransitVehicles().getVehicles().get(vehicleId).getType(); - final double vehicleLength = vehicleType.getLength(); - final double fahrwegLength = getFahrwegLength(vehicleId, vehicleType, linkId, time); - final double trainAndFahrwegLength = vehicleLength + fahrwegLength; - - // iterate through the list of touched links (start from where the transit vehicle currently is) - - // IIIIIIIIIIIIIIIIIIIII---------------------------- >>> - // | train | fahrweg 'vehicle' - - LinkedList> touchedLinksSortedUpdated = new LinkedList<>(); - - LinkedList linksNoLongerTouched = new LinkedList<>(); - - Link latestTouchedLink = this.scenario.getNetwork().getLinks().get(linkId); - if (latestTouchedLink.getLength() > trainAndFahrwegLength) { - // This link is long enough --> This link is touched by both the path and the train itself - - // update the queue - touchedLinksSortedUpdated.add(linkId); - - // all other links are no longer touched, update the list of no longer touched links - for (Id touchedLinkId : touchedLinksSorted) { - if (touchedLinkId.toString().equals(linkId.toString())) { - // this link is still touched - } else { - Link touchedLink = this.scenario.getNetwork().getLinks().get(touchedLinkId); - linksNoLongerTouched.addFirst(touchedLink); - } - } - - // The link is also touched by the train -> throw train enters link event - this.events.processEvent(new TrainEntersLink(time, linkId, vehicleId)); - touchedLinksByTrain.add(linkId); - - } else { - double lengthCumulated = 0.; - for (Id touchedLinkId : touchedLinksSorted) { - Link touchedLink = this.scenario.getNetwork().getLinks().get(touchedLinkId); - - if (lengthCumulated <= trainAndFahrwegLength) { - // The link is touched by either the fahrweg or the train itself. - - touchedLinksSortedUpdated.add(touchedLinkId); - - // compute the train position (for visualization purposes only) - if (lengthCumulated < fahrwegLength) { - // this link is touched by the reserved fahrweg (not by the train itself) - } else { - // this link is touched by the train itself - - if (touchedLinksByTrain.contains(touchedLinkId)) { - // This link has already been touched by the train. - } else { - // This link is touched by the train for the first time. - this.events.processEvent(new TrainEntersLink(time, touchedLinkId, vehicleId)); - touchedLinksByTrain.add(touchedLinkId); - } - } - - // increase the cumulated length - lengthCumulated += touchedLink.getLength(); - - } else { - // collect the links which are no longer touched by the train or fahrweg - linksNoLongerTouched.addFirst(touchedLink); - } - } - } - - // throw 'train enters link' events for all links no longer touched by the train path - // otherwise these events are missing and event-based processing may not work correctly - for (Link linkNoLongerTouched : linksNoLongerTouched) { - if (touchedLinksByTrain.contains(linkNoLongerTouched.getId())) { - // a 'train enters link' event has already been thrown for this link - } else { - if (warnCnt < 5) { - log.warn("'train entered link' event is thrown in the same time step as the 'train left link' event. " + "This may be prevented by reducing the link length or reducing the physical extension of the train (train length or train path length)."); - log.warn("link: " + linkId + " / vehicle: " + vehicleId); - warnCnt++; - } else if (warnCnt == 5) { - log.warn("Further warnings of this type will not be printed out."); - warnCnt++; - } - this.events.processEvent(new TrainEntersLink(time, linkNoLongerTouched.getId(), vehicleId)); - touchedLinksByTrain.add(linkNoLongerTouched.getId()); - } - } - - // throw 'train leaves link' events for all links no longer touched by the train (or train path) - for (Link linkNoLongerTouched : linksNoLongerTouched) { - this.events.processEvent(new TrainLeavesLink(time, linkNoLongerTouched.getId(), vehicleId)); - - // link is also no longer touched by train - touchedLinksByTrain.remove(linkNoLongerTouched.getId()); - } - - // add the updated lists of blocked link IDs - this.vehicleId2currentlyTouchedLinksTotalSorted.put(vehicleId, touchedLinksSortedUpdated); - } - - /** - * @param vehicleId - * @param vehicleType - * @param linkId - * @return - */ - private double getFahrwegLength(Id vehicleId, VehicleType vehicleType, Id linkId, double time) { - - RailsimConfigGroup railsimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); - - // Set the reserved train path to 0 if the train is on a link at which the train stops. - TransitStopFacility stopFacility = this.statistics.getLink2stop().get(linkId); - if (stopFacility != null) { - // this link is a stop - Id currentLineId = this.statistics.getVehicleId2currentTransitLine().get(vehicleId); - Id currentRouteId = this.statistics.getVehicleId2currentTransitRoute().get(vehicleId); - TransitRouteStop routeStop = this.scenario.getTransitSchedule().getTransitLines().get(currentLineId).getRoutes().get(currentRouteId).getStop(stopFacility); - if (routeStop != null) { - // The train is about to stop at this station and the speed will be reduced to 0. - // Setting the length of the reserved train path to 0. - return 0.; - } else { - // The train will not stop at this station and the speed will not be reduced. - // The reserved train path will not be reduced to 0. - } - } - - // start with the theoretical maximum speed - // the following does not account for the acceleration or deceleration... -// final double maxSpeedOnTheCurrentLink = Math.min(vehicleType.getMaximumVelocity(), -// this.scenario.getNetwork().getLinks().get(linkId).getFreespeed()); - - Vehicle vehicle = scenario.getTransitVehicles().getVehicles().get(vehicleId); - Link link = scenario.getNetwork().getLinks().get(linkId); - - final double maxSpeedOnTheCurrentLink = linkSpeedCalculator.getRailsimMaximumVelocity(vehicle, this.scenario.getNetwork().getLinks().get(linkId), time); - - // account for congestion effects, e.g. a fast train behind a slower train - double speedWhenEnteringTheLink = this.statistics.getSpeedOnPreviousLink(vehicleId); - final double currentSpeed = Math.min(maxSpeedOnTheCurrentLink, speedWhenEnteringTheLink); - - final double reactionTime = railsimConfigGroup.getReactionTime(); - final double gravity = railsimConfigGroup.getGravity(); - - double deceleration = RailsimUtils.getTrainDeceleration(vehicle, railsimConfigGroup); - double grade = RailsimUtils.getGrade(link, railsimConfigGroup); - - // calculate the fahrwegLength with given formula - double fahrwegLength = (currentSpeed * reactionTime) + (Math.pow(currentSpeed, 2) / (2 * (deceleration + gravity * grade))) + ((currentSpeed / 2) + 20); - - return fahrwegLength; - } - - @Override - public void handleEvent(VehicleEntersTrafficEvent event) { - - // manually process the relevant events on the initial link - this.events.processEvent(new TrainPathEntersLink(event.getTime(), event.getLinkId(), event.getVehicleId())); - this.events.processEvent(new TrainEntersLink(event.getTime(), event.getLinkId(), event.getVehicleId())); - - // also add the initial link to the relevant data containers - - Set> touchedLinksByTrainUpdated = new HashSet<>(); - touchedLinksByTrainUpdated.add(event.getLinkId()); - this.vehicleId2currentlyTouchedLinksByTrain.put(event.getVehicleId(), touchedLinksByTrainUpdated); - - LinkedList> touchedLinksSorted = new LinkedList<>(); - touchedLinksSorted.add(event.getLinkId()); - this.vehicleId2currentlyTouchedLinksTotalSorted.put(event.getVehicleId(), touchedLinksSorted); - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java deleted file mode 100644 index 9e63006e9d6..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLink.java +++ /dev/null @@ -1,56 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.events.Event; -import org.matsim.api.core.v01.events.HasLinkId; -import org.matsim.api.core.v01.events.HasVehicleId; -import org.matsim.api.core.v01.network.Link; -import org.matsim.vehicles.Vehicle; - -import java.util.Map; - -/** - * @author Ihab Kaddoura - */ -public class TrainEntersLink extends Event implements HasLinkId, HasVehicleId { - - public static final String EVENT_TYPE = "train entered link"; - public static final String ATTRIBUTE_VEHICLE = "vehicle"; - public static final String ATTRIBUTE_LINK = "link"; - - private final Id linkId; - private final Id vehicleId; - - public TrainEntersLink(double time, Id linkId, Id vehicleId) { - super(time); - this.linkId = linkId; - this.vehicleId = vehicleId; - } - - @Override - public String getEventType() { - return EVENT_TYPE; - } - - @Override - public Id getLinkId() { - return linkId; - } - - /** - * @return the vehicleId - */ - @Override - public Id getVehicleId() { - return vehicleId; - } - - @Override - public Map getAttributes() { - Map attr = super.getAttributes(); - attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); - attr.put(ATTRIBUTE_LINK, this.linkId.toString()); - return attr; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java deleted file mode 100644 index 2e60fb2349e..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainEntersLinkEventHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - - -import org.matsim.core.events.handler.EventHandler; - -/** - * @author Ihab Kaddoura - */ -public interface TrainEntersLinkEventHandler extends EventHandler { - void handleEvent(TrainEntersLink event); -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java deleted file mode 100644 index 761e7139ef3..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLink.java +++ /dev/null @@ -1,55 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.events.Event; -import org.matsim.api.core.v01.events.HasLinkId; -import org.matsim.api.core.v01.events.HasVehicleId; -import org.matsim.api.core.v01.network.Link; -import org.matsim.vehicles.Vehicle; - -import java.util.Map; - -/** - * @author Ihab Kaddoura - */ -public class TrainLeavesLink extends Event implements HasLinkId, HasVehicleId { - - public static final String EVENT_TYPE = "train left link"; - public static final String ATTRIBUTE_VEHICLE = "vehicle"; - public static final String ATTRIBUTE_LINK = "link"; - - private final Id linkId; - private final Id vehicleId; - - public TrainLeavesLink(double time, Id linkId, Id vehicleId) { - super(time); - this.linkId = linkId; - this.vehicleId = vehicleId; - } - - @Override - public String getEventType() { - return EVENT_TYPE; - } - - @Override - public Id getLinkId() { - return linkId; - } - - /** - * @return the vehicleId - */ - @Override - public Id getVehicleId() { - return vehicleId; - } - - @Override - public Map getAttributes() { - Map attr = super.getAttributes(); - attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); - attr.put(ATTRIBUTE_LINK, this.linkId.toString()); - return attr; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java deleted file mode 100644 index 5578a42b4f4..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainLeavesLinkEventHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.matsim.core.events.handler.EventHandler; - -/** - * @author Ihab Kaddoura - */ -public interface TrainLeavesLinkEventHandler extends EventHandler { - void handleEvent(TrainLeavesLink event); -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java deleted file mode 100644 index 1ae6c05fbaf..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLink.java +++ /dev/null @@ -1,56 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.events.Event; -import org.matsim.api.core.v01.events.HasLinkId; -import org.matsim.api.core.v01.events.HasVehicleId; -import org.matsim.api.core.v01.network.Link; -import org.matsim.vehicles.Vehicle; - -import java.util.Map; - -/** - * @author Ihab Kaddoura - */ -public class TrainPathEntersLink extends Event implements HasLinkId, HasVehicleId { - - public static final String EVENT_TYPE = "train path entered link"; - public static final String ATTRIBUTE_VEHICLE = "vehicle"; - public static final String ATTRIBUTE_LINK = "link"; - - private final Id linkId; - private final Id vehicleId; - - public TrainPathEntersLink(double time, Id linkId, Id vehicleId) { - super(time); - this.linkId = linkId; - this.vehicleId = vehicleId; - } - - @Override - public String getEventType() { - return EVENT_TYPE; - } - - @Override - public Id getLinkId() { - return this.linkId; - } - - /** - * @return the vehicleId - */ - @Override - public Id getVehicleId() { - return vehicleId; - } - - @Override - public Map getAttributes() { - Map attr = super.getAttributes(); - attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); - attr.put(ATTRIBUTE_LINK, this.linkId.toString()); - return attr; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java deleted file mode 100644 index 52fb7ee4a1b..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainPathEntersLinkEventHandler.java +++ /dev/null @@ -1,10 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.matsim.core.events.handler.EventHandler; - -/** - * @author Ihab Kaddoura - */ -public interface TrainPathEntersLinkEventHandler extends EventHandler { - public void handleEvent(TrainPathEntersLink event); -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java deleted file mode 100644 index a5f1e3a860d..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/TrainStatistics.java +++ /dev/null @@ -1,195 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -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.LinkLeaveEvent; -import org.matsim.api.core.v01.events.TransitDriverStartsEvent; -import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; -import org.matsim.api.core.v01.events.handler.LinkLeaveEventHandler; -import org.matsim.api.core.v01.events.handler.TransitDriverStartsEventHandler; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.api.experimental.events.VehicleArrivesAtFacilityEvent; -import org.matsim.core.api.experimental.events.handler.VehicleArrivesAtFacilityEventHandler; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitRouteStop; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; -import org.matsim.vehicles.Vehicle; - -import javax.inject.Inject; -import java.util.HashMap; -import java.util.List; - -/** - * Note: These statistics refer to the MATSim transit vehicles, which in our current implementation is interpreted as the front of the train path. - * - * @author Ihab Kaddoura - */ -public class TrainStatistics implements LinkEnterEventHandler, LinkLeaveEventHandler, TransitDriverStartsEventHandler, VehicleArrivesAtFacilityEventHandler { - private static final Logger log = LogManager.getLogger(TrainStatistics.class); - - @Inject - private Scenario scenario; - - // the following maps are required to compute the length of the reserved train path - private final HashMap, Double> vehicleId2speedOnPreviousLink = new HashMap<>(); - private final HashMap, Double> vehicleId2lastLinkEnterTime = new HashMap<>(); - private final HashMap, Id> vehicleId2previousLink = new HashMap<>(); - - private final HashMap, Id> vehicleId2currentTransitLine = new HashMap<>(); - private final HashMap, Id> vehicleId2currentTransitRoute = new HashMap<>(); - private final HashMap, TransitStopFacility> link2stop = new HashMap<>(); - private final HashMap, Id> stop2link = new HashMap<>(); - - private final HashMap, Id> vehicle2lastStop = new HashMap<>(); - private final HashMap, Id> vehicle2nextStop = new HashMap<>(); - - private final HashMap, Id> vehicle2lastStopLink = new HashMap<>(); - - @Override - public void reset(int iteration) { - - for (TransitStopFacility facility : this.scenario.getTransitSchedule().getFacilities().values()) { - if (this.link2stop.containsKey(facility.getLinkId())) { - log.warn("transitStopFacility: " + facility.getId() + " 1st link: " + link2stop.get(facility.getLinkId())); - log.warn("transitStopFacility: " + facility.getId() + " 2nd link: " + facility.getLinkId()); - throw new RuntimeException("A transit stop facility is located on more than one link. Aborting..."); - } - this.link2stop.put(facility.getLinkId(), facility); - this.stop2link.put(facility.getId(), facility.getLinkId()); - } - - if (iteration > 0) throw new RuntimeException("Running more than 1 iteration. Aborting..."); - } - - @Override - public void handleEvent(LinkEnterEvent event) { - this.vehicleId2lastLinkEnterTime.put(event.getVehicleId(), event.getTime()); - } - - @Override - public void handleEvent(LinkLeaveEvent event) { - if (vehicleId2lastLinkEnterTime.get(event.getVehicleId()) == null) { - // At the very beginning vehicles are 'put' on the link without entering... - this.vehicleId2speedOnPreviousLink.put(event.getVehicleId(), null); - - } else { - double t = event.getTime() - this.vehicleId2lastLinkEnterTime.get(event.getVehicleId()); - double s = this.scenario.getNetwork().getLinks().get(event.getLinkId()).getLength(); - double v = s / t; - this.vehicleId2speedOnPreviousLink.put(event.getVehicleId(), v); - } - - this.vehicleId2previousLink.put(event.getVehicleId(), event.getLinkId()); - } - - @Override - public void handleEvent(TransitDriverStartsEvent event) { - this.vehicleId2currentTransitLine.put(event.getVehicleId(), event.getTransitLineId()); - this.vehicleId2currentTransitRoute.put(event.getVehicleId(), event.getTransitRouteId()); - } - - /** - * @return the speed on the previous link. - * If there is no information about the previous link or if the vehicle was stopping at the previous link, the speed on the previous link is 0. - */ - public double getSpeedOnPreviousLink(Id vehicleId) { - - if (vehicleId2speedOnPreviousLink.get(vehicleId) == null) { - // There is no speed stored for the vehicle, probably the beginning of the transit route. - return 0.; - } else { - - if (this.vehicle2lastStop.get(vehicleId) == null) { - throw new RuntimeException("A vehicle should always have a last stop. Aborting..."); - } - - if (this.vehicle2lastStopLink.get(vehicleId).toString().equals(this.vehicleId2previousLink.get(vehicleId).toString())) { - // the vehicle was stopping at the previous link - return 0.; - } - - return vehicleId2speedOnPreviousLink.get(vehicleId); - - } - } - - /** - * @return the vehicleId2lastLinkEnterTime - */ - public HashMap, Double> getVehicleId2lastLinkEnterTime() { - return vehicleId2lastLinkEnterTime; - } - - /** - * @return the vehicleId2currentTransitLine - */ - public HashMap, Id> getVehicleId2currentTransitLine() { - return vehicleId2currentTransitLine; - } - - /** - * @return the vehicleId2currentTransitRoute - */ - public HashMap, Id> getVehicleId2currentTransitRoute() { - return vehicleId2currentTransitRoute; - } - - /** - * @return the link2stop - */ - public HashMap, TransitStopFacility> getLink2stop() { - return link2stop; - } - - @Override - public void handleEvent(VehicleArrivesAtFacilityEvent event) { - this.vehicle2lastStop.put(event.getVehicleId(), event.getFacilityId()); - this.vehicle2lastStopLink.put(event.getVehicleId(), this.stop2link.get(event.getFacilityId())); - - // find next stop - Id lineId = this.vehicleId2currentTransitLine.get(event.getVehicleId()); - Id routeId = this.vehicleId2currentTransitRoute.get(event.getVehicleId()); - - List stops = this.scenario.getTransitSchedule().getTransitLines().get(lineId).getRoutes().get(routeId).getStops(); - int index = 0; - for (TransitRouteStop stop : stops) { - if (stop.getStopFacility().getId().toString().equals(event.getFacilityId().toString())) { - break; - } - index++; - } - - int indexNextStop = index + 1; - TransitRouteStop nextStop = null; - if (stops.size() == indexNextStop) { - // final stop - } else { - nextStop = stops.get(indexNextStop); - } - if (nextStop == null) { - this.vehicle2nextStop.put(event.getVehicleId(), null); - } else { - this.vehicle2nextStop.put(event.getVehicleId(), nextStop.getStopFacility().getId()); - } - } - - /** - * @return the vehicle2lastStop - */ - public HashMap, Id> getVehicle2lastStop() { - return vehicle2lastStop; - } - - /** - * @return the vehicle2nextStop - */ - public HashMap, Id> getVehicle2nextStop() { - return vehicle2nextStop; - } - - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java deleted file mode 100644 index 3ac0c959727..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/ConvertTrainEventsToDefaultEvents.java +++ /dev/null @@ -1,47 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.analysis; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.events.EventsUtils; -import org.matsim.core.events.algorithms.EventWriterXML; - -/** - * @author Ihab Kaddoura - */ -public class ConvertTrainEventsToDefaultEvents { - private static final Logger log = LogManager.getLogger(ConvertTrainEventsToDefaultEvents.class); - - public static void main(String[] args) { - - String outputDirectory = "test/output/ch/sbb/railsim/RunRailsimTest/test4/"; - String runId = "test"; - - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(runId, outputDirectory); - } - - public void run(String runId, String outputDirectory) { - - if (!outputDirectory.endsWith("/")) outputDirectory = outputDirectory.concat("/"); - - String eventsFile = outputDirectory + runId + ".output_events.xml.gz"; - - // read events - EventsManager events = EventsUtils.createEventsManager(); - EventWriterXML eventWriter = new EventWriterXML(outputDirectory + runId + ".output_events_trainVisualization.xml.gz"); - - TrainEventsHandler trainEventHandler = new TrainEventsHandler(events); - events.addHandler(eventWriter); - events.addHandler(trainEventHandler); - events.initProcessing(); - - log.info("Reading the events..."); - new TrainEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - log.info("Reading the events... Done."); - - eventWriter.closeFile(); - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java deleted file mode 100644 index d94aff44826..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsHandler.java +++ /dev/null @@ -1,81 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.analysis; - -import ch.sbb.matsim.contrib.railsim.prototype.TrainEntersLink; -import ch.sbb.matsim.contrib.railsim.prototype.TrainEntersLinkEventHandler; -import ch.sbb.matsim.contrib.railsim.prototype.TrainLeavesLink; -import ch.sbb.matsim.contrib.railsim.prototype.TrainLeavesLinkEventHandler; -import ch.sbb.matsim.contrib.railsim.prototype.TrainPathEntersLink; -import ch.sbb.matsim.contrib.railsim.prototype.TrainPathEntersLinkEventHandler; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.events.LinkEnterEvent; -import org.matsim.api.core.v01.events.LinkLeaveEvent; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.vehicles.Vehicle; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Ihab Kaddoura - */ -public class TrainEventsHandler implements TrainEntersLinkEventHandler, TrainLeavesLinkEventHandler, TrainPathEntersLinkEventHandler { - - private EventsManager events; - - Map, Double> vehId2lastTimeStep = new HashMap<>(); - - public TrainEventsHandler(EventsManager events) { - this.events = events; - } - - @Override - public void handleEvent(TrainPathEntersLink event) { - Id pathId = Id.createVehicleId(event.getVehicleId() + "_viz_path"); - - if (isNewTimeStep(event.getTime(), pathId)) { - this.events.processEvent(new LinkEnterEvent(event.getTime(), pathId, event.getLinkId())); - this.vehId2lastTimeStep.put(pathId, event.getTime()); - } - - } - - @Override - public void handleEvent(TrainLeavesLink event) { - - Id trainId = Id.createVehicleId(event.getVehicleId() + "_viz_train"); - Id pathId = Id.createVehicleId(event.getVehicleId() + "_viz_path"); - - if (isNewTimeStep(event.getTime(), trainId)) { - this.events.processEvent(new LinkLeaveEvent(event.getTime(), trainId, event.getLinkId())); - this.vehId2lastTimeStep.put(trainId, event.getTime()); - } - - if (isNewTimeStep(event.getTime(), pathId)) { - this.events.processEvent(new LinkLeaveEvent(event.getTime(), pathId, event.getLinkId())); - this.vehId2lastTimeStep.put(pathId, event.getTime()); - - } - - } - - private boolean isNewTimeStep(double time, Id vehId) { - if (this.vehId2lastTimeStep.get(vehId) == null) { - return true; - } else if (this.vehId2lastTimeStep.get(vehId) == time) { - return false; - } else { - return true; - } - } - - @Override - public void handleEvent(TrainEntersLink event) { - Id trainId = Id.createVehicleId(event.getVehicleId() + "_viz_train"); - - if (isNewTimeStep(event.getTime(), trainId)) { - this.events.processEvent(new LinkEnterEvent(event.getTime(), trainId, event.getLinkId())); - this.vehId2lastTimeStep.put(trainId, event.getTime()); - } - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java deleted file mode 100644 index 21bbfa72893..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TrainEventsReader.java +++ /dev/null @@ -1,112 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.analysis; - -import ch.sbb.matsim.contrib.railsim.prototype.TrainEntersLink; -import ch.sbb.matsim.contrib.railsim.prototype.TrainLeavesLink; -import ch.sbb.matsim.contrib.railsim.prototype.TrainPathEntersLink; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.utils.io.MatsimXmlParser; -import org.matsim.vehicles.Vehicle; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; - -import java.util.Stack; - -/** - * @author ihab - */ -public class TrainEventsReader extends MatsimXmlParser { - - private static final String EVENT = "event"; - - private final EventsManager eventsManager; - - public TrainEventsReader(EventsManager events) { - super(); - this.eventsManager = events; - setValidating(false); // events-files have no DTD, thus they cannot validate - } - - @Override - public void startTag(String name, Attributes atts, Stack context) { - if (EVENT.equals(name)) { - startEvent(atts); - } - } - - @Override - public void endTag(String name, String content, Stack context) { - } - - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - // ignore characters to prevent OutOfMemoryExceptions - /* the events-file only contains empty tags with attributes, - * but without the dtd or schema, all whitespace between tags is handled - * by characters and added up by super.characters, consuming huge - * amount of memory when large events-files are read in. - */ - } - - private void startEvent(final Attributes attributes) { - - String eventType = attributes.getValue("type"); - - Double time = 0.0; - Id linkId = null; - Id vehicleId = null; - - if (TrainEntersLink.EVENT_TYPE.equals(eventType)) { - for (int i = 0; i < attributes.getLength(); i++) { - if (attributes.getQName(i).equals("time")) { - time = Double.parseDouble(attributes.getValue(i)); - } else if (attributes.getQName(i).equals("type")) { - eventType = attributes.getValue(i); - } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_LINK)) { - linkId = Id.create((attributes.getValue(i)), Link.class); - } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_VEHICLE)) { - vehicleId = Id.create((attributes.getValue(i)), Vehicle.class); - } else { - throw new RuntimeException("Unknown event attribute. Aborting..."); - } - } - this.eventsManager.processEvent(new TrainEntersLink(time, linkId, vehicleId)); - - } else if (TrainLeavesLink.EVENT_TYPE.equals(eventType)) { - for (int i = 0; i < attributes.getLength(); i++) { - if (attributes.getQName(i).equals("time")) { - time = Double.parseDouble(attributes.getValue(i)); - } else if (attributes.getQName(i).equals("type")) { - eventType = attributes.getValue(i); - } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_LINK)) { - linkId = Id.create((attributes.getValue(i)), Link.class); - } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_VEHICLE)) { - vehicleId = Id.create((attributes.getValue(i)), Vehicle.class); - } else { - throw new RuntimeException("Unknown event attribute. Aborting..."); - } - } - this.eventsManager.processEvent(new TrainLeavesLink(time, linkId, vehicleId)); - - } else if (TrainPathEntersLink.EVENT_TYPE.equals(eventType)) { - for (int i = 0; i < attributes.getLength(); i++) { - if (attributes.getQName(i).equals("time")) { - time = Double.parseDouble(attributes.getValue(i)); - } else if (attributes.getQName(i).equals("type")) { - eventType = attributes.getValue(i); - } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_LINK)) { - linkId = Id.create((attributes.getValue(i)), Link.class); - } else if (attributes.getQName(i).equals(TrainEntersLink.ATTRIBUTE_VEHICLE)) { - vehicleId = Id.create((attributes.getValue(i)), Vehicle.class); - } else { - throw new RuntimeException("Unknown event attribute. Aborting..."); - } - } - this.eventsManager.processEvent(new TrainPathEntersLink(time, linkId, vehicleId)); - - } else { - // do not process other event types - } - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java deleted file mode 100644 index b27e56e98ce..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/analysis/TransitEventHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.analysis; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Id; -import org.matsim.core.api.experimental.events.VehicleArrivesAtFacilityEvent; -import org.matsim.core.api.experimental.events.VehicleDepartsAtFacilityEvent; -import org.matsim.core.api.experimental.events.handler.VehicleArrivesAtFacilityEventHandler; -import org.matsim.core.api.experimental.events.handler.VehicleDepartsAtFacilityEventHandler; -import org.matsim.core.utils.collections.Tuple; -import org.matsim.core.utils.io.IOUtils; -import org.matsim.vehicles.Vehicle; - -import java.io.BufferedWriter; -import java.util.HashMap; -import java.util.Map; - -/** - * @author Ihab Kaddoura - */ -public class TransitEventHandler implements VehicleArrivesAtFacilityEventHandler, VehicleDepartsAtFacilityEventHandler { - private static final Logger log = LogManager.getLogger(TransitEventHandler.class); - - private final Map> vehicleFacilityDeparture2time2delay = new HashMap<>(); - private final Map> vehicleFacilityArrival2time2delay = new HashMap<>(); - private final Map, Double> vehicle2totalTraveltime = new HashMap<>(); - private final Map, Double> vehicle2previousDepartureTime = new HashMap<>(); - - @Override - public void handleEvent(VehicleDepartsAtFacilityEvent event) { - String vehicleFacility = event.getVehicleId().toString() + "---" + event.getFacilityId().toString(); - this.vehicleFacilityDeparture2time2delay.put(vehicleFacility, new Tuple<>(event.getTime(), event.getDelay())); - this.vehicle2previousDepartureTime.put(event.getVehicleId(), event.getTime()); - } - - @Override - public void handleEvent(VehicleArrivesAtFacilityEvent event) { - String vehicleFacility = event.getVehicleId().toString() + "---" + event.getFacilityId().toString(); - this.vehicleFacilityArrival2time2delay.put(vehicleFacility, new Tuple<>(event.getTime(), event.getDelay())); - - double tt = event.getTime() - this.vehicle2previousDepartureTime.getOrDefault(event.getVehicleId(), 0.); - double ttSoFar = this.vehicle2totalTraveltime.getOrDefault(event.getVehicleId(), 0.); - this.vehicle2totalTraveltime.put(event.getVehicleId(), ttSoFar + tt); - } - - /** - * @return the vehicleFacilityDeparture2time2delay - */ - public Map> getVehicleFacilityDeparture2time2delay() { - return vehicleFacilityDeparture2time2delay; - } - - /** - * @return the vehicleFacilityArrival2time2delay - */ - public Map> getVehicleFacilityArrival2time2delay() { - return vehicleFacilityArrival2time2delay; - } - - /** - * @param outputDirectory - * @param runId - */ - public void printResults(String outputDirectory, String runId) { - final String name = "transitAnalysis"; - - if (!outputDirectory.endsWith("/")) outputDirectory = outputDirectory + "/"; - - String outputFile = outputDirectory + runId + "." + name + ".csv"; - - BufferedWriter writer = IOUtils.getBufferedWriter(outputFile); - - try { - writer.write("vehicle;facility;type;time;delay"); - writer.newLine(); - - for (String vehicleFacility : vehicleFacilityDeparture2time2delay.keySet()) { - writer.write(vehicleFacility.split("---")[0] + ";" + vehicleFacility.split("---")[1] + ";" + "departure;" + vehicleFacilityDeparture2time2delay.get(vehicleFacility).getFirst() + ";" + vehicleFacilityDeparture2time2delay.get(vehicleFacility).getSecond()); - writer.newLine(); - } - for (String vehicleFacility : vehicleFacilityArrival2time2delay.keySet()) { - writer.write(vehicleFacility.split("---")[0] + ";" + vehicleFacility.split("---")[1] + ";" + "arrival;" + vehicleFacilityArrival2time2delay.get(vehicleFacility).getFirst() + ";" + vehicleFacilityArrival2time2delay.get(vehicleFacility).getSecond()); - writer.newLine(); - } - writer.close(); - - log.info("Text info written to file."); - } catch (Exception e) { - log.warn("Text info not written to file."); - } - } - - /** - * @return the vehicle2totalTraveltime - */ - public Map, Double> getVehicle2totalTraveltime() { - return vehicle2totalTraveltime; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java deleted file mode 100644 index 651b7d8b5f8..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/demo/RunDemo.java +++ /dev/null @@ -1,72 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.demo; - -import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.TransitEventHandler; -import org.matsim.api.core.v01.Scenario; -import org.matsim.contrib.otfvis.OTFVisLiveModule; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.config.Config; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; -import org.matsim.core.controler.Controler; -import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; -import org.matsim.core.events.EventsUtils; -import org.matsim.core.events.MatsimEventsReader; -import org.matsim.vis.otfvis.OTFVisConfigGroup; - -public class RunDemo { - - public static void main(String[] args) { - - System.setProperty("matsim.preferLocalDtds", "true"); -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml"}; // one direction -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml"}; // two directions -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml"}; // two directions, several trains - String[] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml"}; // T shaped network -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml"}; // Genf-Bern -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml"}; // very short links -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml"}; // crossing -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml"}; // one corridor, very short links -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml"}; // advanced crossing, same links -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml"}; // advanced crossing, crossing... -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml"}; // simple crossing, different links -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml"}; // simplified GE-BN situation, capacity = 2 -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml"}; // simplified GE-BN situation, capacity = 1 -// String [] args0 = {"contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml"}; // one direction, passing queue - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory("contribs/railsim/test/output/ch/sbb/matsim/contrib/railsim/RunRailsimTest/demo/output"); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - OTFVisConfigGroup otfvisCfg = ConfigUtils.addOrGetModule(config, OTFVisConfigGroup.class); - otfvisCfg.setDrawNonMovingItems(false); - otfvisCfg.setDrawTime(true); - otfvisCfg.setLinkWidth(20); - otfvisCfg.setLinkWidthIsProportionalTo("capacity"); - otfvisCfg.setColoringScheme(OTFVisConfigGroup.ColoringScheme.standard); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.addOverridingModule(new OTFVisLiveModule()); - controler.run(); - - // read events - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), config.controler().getOutputDirectory()); - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java deleted file mode 100644 index dff6c450f06..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToSchedule.java +++ /dev/null @@ -1,286 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.prepare; - -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Network; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.network.io.NetworkWriter; -import org.matsim.pt.transitSchedule.api.Departure; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitRouteStop; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; -import org.matsim.vehicles.Vehicle; -import org.matsim.vehicles.VehicleType; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * @author Ihab Kaddoura - */ -public class AdjustNetworkToSchedule { - private static final Logger log = LogManager.getLogger(AdjustNetworkToSchedule.class); - - private final Scenario scenario; - private final Set> stopLinkIds = new HashSet<>(); - private final RailsimConfigGroup railsimConfigGroup; - - private boolean throwExceptionIfMaximumVehicleVelocityIsViolated = false; - - /** - * @param scenario - */ - public AdjustNetworkToSchedule(Scenario scenario) { - this.scenario = scenario; - this.railsimConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimConfigGroup.class); - } - - public void run() { - - log.info("Adjust network speed levels to the transit schedule..."); - - // first store the information which link is a stop link - for (TransitStopFacility facility : scenario.getTransitSchedule().getFacilities().values()) { - stopLinkIds.add(facility.getLinkId()); - } - - // now adjust the travel times for each transit line, transit route, stop to stop segment... - for (TransitLine line : scenario.getTransitSchedule().getTransitLines().values()) { - for (TransitRoute route : line.getRoutes().values()) { - - log.debug("+++++++ Transit route: " + route.getId().toString() + " ++++++++"); - - // identify vehicle types - VehicleType vehicleType = null; - for (Departure departure : route.getDepartures().values()) { - Vehicle vehicle = this.scenario.getTransitVehicles().getVehicles().get(departure.getVehicleId()); - if (vehicleType == null) { - vehicleType = vehicle.getType(); - } else { - if (vehicleType == vehicle.getType()) { - // same vehicle type - } else { - throw new RuntimeException("Different vehicle types detected within a single transit route: " + route.getId()); - } - } - } - - Double previousDeparture = null; - Id previousStopLink = null; - for (TransitRouteStop stop : route.getStops()) { - if (previousDeparture == null) { - previousDeparture = stop.getDepartureOffset().seconds(); - previousStopLink = stop.getStopFacility().getLinkId(); - - } else { - double ttSchedule = stop.getArrivalOffset().seconds() - previousDeparture; - - if (ttSchedule <= 0) { - log.warn("Implausible travel time in schedule: " + ttSchedule + " / arrival: " + stop.getArrivalOffset().seconds() + " / previous departure: " + previousDeparture); - log.warn("Line: " + line.getId() + " Route: " + route.getId() + " Stop: " + stop.getStopFacility().getId()); - - if (ttSchedule < 0) throw new RuntimeException("Invalid travel time. Aborting..."); - // TODO: do not throw a runtime exception in the case of ttSchedule == 0; otherwise a test fails... - } - - List> routeLinks = getRouteLinks(route, previousStopLink, stop.getStopFacility().getLinkId()); - adjustLinks(route.getId(), line.getId(), routeLinks, ttSchedule, vehicleType); - previousStopLink = stop.getStopFacility().getLinkId(); - - if (stop.getDepartureOffset().isDefined()) { - previousDeparture = stop.getDepartureOffset().seconds(); - } else { - previousDeparture = Double.MAX_VALUE; - } - } - } - } - } - - new NetworkWriter(scenario.getNetwork()).write(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTrainNetwork.xml"); - } - - private void adjustLinks(Id routeId, Id lineId, List> routeLinks, double ttSchedule, VehicleType vehicleType) { - log.info("++++++++++++++++++"); - log.info("Line: " + lineId); - log.info("Route: " + routeId); - log.info("Vehicle type: " + vehicleType.getId()); - log.info("Route links: " + routeLinks.toString()); - - final double ttNetwork = computeRouteTravelTime(routeLinks, lineId, routeId, vehicleType.getId()); - - if (isSame(ttNetwork, ttSchedule)) { - // no delays and no early arrivals, nothing to do. - - } else { - - // Start with the easiest implementation: set the speed equally for each link - // In a later step and depending on the available data, we may come up with - // a more elaborated approach. - // If we already have several smaller links, we may account for the acceleration and deceleration - // and apply a function, e.g. lower speeds behind/before stops and so on... - double requiredSpeed = this.computeRouteTravelDistance(routeLinks) / ttSchedule; - - if (requiredSpeed > vehicleType.getMaximumVelocity()) { - log.warn("The required speed is larger than the maximum velocity of the vehicle."); - log.warn("ttNetwork: " + ttNetwork + " ttSchedule: " + ttSchedule); - log.warn("Required speed is above vehicle maximum velocity."); - log.warn("required speed: " + requiredSpeed); - log.warn("vehicle maximum velocity: " + vehicleType.getMaximumVelocity()); - - if (this.throwExceptionIfMaximumVehicleVelocityIsViolated) { - throw new RuntimeException("Aborting..."); - } else { - log.warn("Continuing anyway..."); - } - } - - Network network = this.scenario.getNetwork(); - - for (Id linkId : routeLinks) { - Link link = network.getLinks().get(linkId); - - if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine) { - if (link.getAttributes().getAttribute(lineId.toString()) == null) { - link.getAttributes().putAttribute(lineId.toString(), requiredSpeed); - } else { - double previousValue = (double) link.getAttributes().getAttribute(lineId.toString()); - if (previousValue == requiredSpeed) { - // ok - } else { - log.warn("link: " + linkId); - log.warn("previous value: " + previousValue); - log.warn("required speed: " + requiredSpeed); - throw new RuntimeException("Link attribute already set for line " + lineId + ". Maybe try " + RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute + " instead! Aborting..."); - } - } - } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute) { - if (link.getAttributes().getAttribute(lineId.toString() + "+++" + routeId.toString()) == null) { - link.getAttributes().putAttribute(lineId.toString() + "+++" + routeId.toString(), requiredSpeed); - } else { - double previousValue = (double) link.getAttributes().getAttribute(lineId.toString() + "+++" + routeId.toString()); - if (previousValue == requiredSpeed) { - // ok - } else { - log.warn("previous value: " + previousValue); - log.warn("required speed: " + requiredSpeed); - throw new RuntimeException("Link attribute already set for line " + lineId + " and route " + routeId + ". Aborting..."); - } - } - } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachVehicleType) { - if (link.getAttributes().getAttribute(vehicleType.getId().toString()) == null) { - link.getAttributes().putAttribute(vehicleType.getId().toString(), requiredSpeed); - } else { - double previousValue = (double) link.getAttributes().getAttribute(vehicleType.getId().toString()); - if (previousValue == requiredSpeed) { - // ok - } else { - log.warn("previous value: " + previousValue); - log.warn("required speed: " + requiredSpeed); - throw new RuntimeException("Link attribute already set for vehicleType " + vehicleType.getId() + ". Maybe try " + RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine + " instead! Aborting..."); - } - } - } else { - throw new RuntimeException("AdjustNetworkToSchedule does not work for trainSpeedApproach " + railsimConfigGroup.getTrainSpeedApproach().toString() + " Aborting..."); - } - } - - // re-compute the network travel time... - double ttNetworkAfterNetworkAdjustment = computeRouteTravelTime(routeLinks, lineId, routeId, vehicleType.getId()); - if (isSame(ttNetworkAfterNetworkAdjustment, ttSchedule)) { - // network adjustment successful - - } else { - log.warn("ttNetwork (before): " + ttNetwork + " / ttNetwork (after): " + ttNetworkAfterNetworkAdjustment + " / ttSchedule: " + ttSchedule); - throw new RuntimeException("Network travel time is still different from schedule travel time. Aborting..."); - } - } - } - - /** - * @param ttNetwork - * @param ttSchedule - * @return - */ - private boolean isSame(double ttNetwork, double ttSchedule) { - return Math.abs(ttNetwork - ttSchedule) <= 1.; - } - - /** - * @param route - * @param fromStopLinkId - * @param toStopLinkId - * @return a snippet of the transit route which contains all links between the provided from and to link (excluding the from stop and including the to stop) - */ - private List> getRouteLinks(TransitRoute route, Id fromStopLinkId, Id toStopLinkId) { - List> routeSnippet = new ArrayList<>(); - - List> transitRouteInclFirstAndLastLink = new ArrayList<>(); - transitRouteInclFirstAndLastLink.add(route.getRoute().getStartLinkId()); - transitRouteInclFirstAndLastLink.addAll(route.getRoute().getLinkIds()); - transitRouteInclFirstAndLastLink.add(route.getRoute().getEndLinkId()); - - boolean putInList = false; - for (Id linkId : transitRouteInclFirstAndLastLink) { - - if (putInList) { - routeSnippet.add(linkId); - } - - if (linkId.toString().equals(fromStopLinkId.toString())) { - putInList = true; - } - - if (linkId.toString().equals(toStopLinkId.toString())) { - break; - } - } - - if (routeSnippet.size() < 2) { - log.warn("fromLink: " + fromStopLinkId + " --> toLink: " + toStopLinkId); - log.warn("Route: " + route.getRoute().toString()); - throw new RuntimeException("Route snippet should at least have two links. Aborting... " + routeSnippet.toString()); - } - - return routeSnippet; - } - - private double computeRouteTravelTime(List> routeLinks, Id lineId, Id routeId, Id vehicleType) { - double ttNetwork = 0.; - for (Id linkId : routeLinks) { - Link link = this.scenario.getNetwork().getLinks().get(linkId); - double linkFreespeed = Double.NEGATIVE_INFINITY; - if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLine) { - linkFreespeed = RailsimUtils.getLinkFreespeedForTransitLine(lineId, link); - } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachVehicleType) { - linkFreespeed = RailsimUtils.getLinkFreespeedForVehicleType(vehicleType, link); - } else if (railsimConfigGroup.getTrainSpeedApproach() == RailsimConfigGroup.TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute) { - linkFreespeed = RailsimUtils.getLinkFreespeedForTransitLineAndTransitRoute(lineId, routeId, link); - } else { - linkFreespeed = link.getFreespeed(); - } - double ttLink = link.getLength() / linkFreespeed; - ttNetwork += ttLink; - } - return ttNetwork; - } - - private double computeRouteTravelDistance(List> routeLinks) { - double distance = 0.; - for (Id linkId : routeLinks) { - Link link = this.scenario.getNetwork().getLinks().get(linkId); - double distanceLink = link.getLength(); - distance += distanceLink; - } - return distance; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java deleted file mode 100644 index a2521cc8ed3..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacities.java +++ /dev/null @@ -1,99 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.prepare; - -import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.network.io.NetworkWriter; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; - -import java.util.HashSet; -import java.util.Set; - -/** - * @author Ihab Kaddoura - */ -public class DistributeCapacities { - private static final Logger log = LogManager.getLogger(DistributeCapacities.class); - - private final Scenario scenario; - private final Set> stopLinkIds = new HashSet<>(); - - /** - * @param scenario - */ - public DistributeCapacities(Scenario scenario) { - this.scenario = scenario; - } - - public void run() { - - log.info("Adjust network speed levels to the transit schedule..."); - - // first store the information which link is a stop link - for (TransitStopFacility facility : scenario.getTransitSchedule().getFacilities().values()) { - stopLinkIds.add(facility.getLinkId()); - } - - for (Link link : scenario.getNetwork().getLinks().values()) { - if (stopLinkIds.contains(link.getId())) { - // bhf - - // inLinks - int linkCapacity = RailsimUtils.getTrainCapacity(link); - - reduceCapacityOnAllInLinks(link, linkCapacity); - reduceCapacityOnAllOutLinks(link, linkCapacity); - - } - } - - new NetworkWriter(scenario.getNetwork()).write(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTrainNetwork_capacities.xml.gz"); - } - - /** - * @param link - * @param capacity - */ - private void reduceCapacityOnAllOutLinks(Link link, int capacity) { - if (capacity == 1) { - // can't further decrease the capacity - } else { - for (Link outLink : link.getToNode().getOutLinks().values()) { - int outLinkCapacity = RailsimUtils.getTrainCapacity(outLink); - - if (outLinkCapacity > capacity || stopLinkIds.contains(outLink.getId())) { - // stop - } else { - // in all other cases: reduce capacity by one. - int updatedCapacity = capacity - 1; - outLink.getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_CAPACITY, updatedCapacity); - reduceCapacityOnAllOutLinks(outLink, updatedCapacity); - } - } - } - - } - - private void reduceCapacityOnAllInLinks(Link link, int capacity) { - if (capacity == 1) { - // can't further decrease the capacity - } else { - for (Link inLink : link.getFromNode().getInLinks().values()) { - int inLinkCapacity = RailsimUtils.getTrainCapacity(inLink); - - if (inLinkCapacity > capacity || stopLinkIds.contains(inLink.getId())) { - // stop - } else { - // in all other cases: reduce capacity by one. - int updatedCapacity = capacity - 1; - inLink.getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_CAPACITY, updatedCapacity); - reduceCapacityOnAllInLinks(inLink, updatedCapacity); - } - } - } - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java deleted file mode 100644 index f3b9ad1d1f1..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinks.java +++ /dev/null @@ -1,250 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.prepare; - -import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.LineSegment; -import org.matsim.api.core.v01.Coord; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Node; -import org.matsim.core.network.NetworkUtils; -import org.matsim.core.population.routes.NetworkRoute; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; -import org.matsim.utils.objectattributes.attributable.Attributes; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * @author Ihab Kaddoura - */ -public class SplitTransitLinks { - private static final Logger log = LogManager.getLogger(SplitTransitLinks.class); - - private final HashMap>> connectedLinks = new HashMap<>(); - private final Scenario scenario; - - /** - * @param scenario - */ - public SplitTransitLinks(Scenario scenario) { - this.scenario = scenario; - } - - /** - * Splits all links in the network into smaller link segments and adjusts the network routes in the transit schedule. - * Network links which have a transit stop facility are skipped. - * - * @param maximumLinkLength - */ - public void run(double maximumLinkLength) { - Set> stopLinkIds = new HashSet<>(); - for (TransitStopFacility facility : scenario.getTransitSchedule().getFacilities().values()) { - stopLinkIds.add(facility.getLinkId()); - } - - Map, List>> link2splitLinks = new HashMap<>(); - - List linksFromNetwork = new ArrayList<>(); - for (Link link : scenario.getNetwork().getLinks().values()) { - linksFromNetwork.add(link); - } - - for (Link link : linksFromNetwork) { - if (stopLinkIds.contains(link.getId())) { - log.info("Skipping link " + link.getId() + " (transit stop)"); - - } else if (RailsimUtils.getOppositeDirectionLink(link, scenario.getNetwork()) != null) { - log.info("Skipping link " + link.getId() + " (one direction track)"); - // TODO: Once we have the use case, allow for splitting these links (and make sure the one direction logic is correctly transferred to all link segments...) - - } else { - if (link.getLength() > maximumLinkLength) { - log.info("Splitting link " + link.getId()); - List> links = connectNodes(link.getId().toString(), link.getFromNode(), link.getToNode(), link.getFreespeed(), link.getLength(), maximumLinkLength, link.getAttributes()); - link2splitLinks.put(link.getId(), links); - } else { - log.info("Skipping link " + link.getId() + " (below maximumLength)"); - } - } - } - - for (Id linkId : link2splitLinks.keySet()) { - // remove old link from the scenario - this.scenario.getNetwork().removeLink(linkId); - - // replace old link in all transit routes - for (TransitLine transitLine : this.scenario.getTransitSchedule().getTransitLines().values()) { - for (TransitRoute transitRoute : transitLine.getRoutes().values()) { - - - NetworkRoute networkRoute = transitRoute.getRoute(); - - List> replacedLinkIds = new ArrayList<>(); - - for (Id linkIdInNetworkRoute : networkRoute.getLinkIds()) { - if (linkIdInNetworkRoute.toString().equals(linkId.toString())) { - replacedLinkIds.addAll(link2splitLinks.get(linkIdInNetworkRoute)); - } else { - replacedLinkIds.add(linkIdInNetworkRoute); - } - } - - networkRoute.setLinkIds(networkRoute.getStartLinkId(), replacedLinkIds, networkRoute.getEndLinkId()); - } - } - } - -// new NetworkWriter(scenario.getNetwork()).write(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTrainNetwork.xml"); -// new TransitScheduleWriter(scenario.getTransitSchedule()).writeFile(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTransitSchedule.xml"); -// new MatsimVehicleWriter(scenario.getTransitVehicles()).writeFile(scenario.getConfig().controler().getOutputDirectory() + "../modified_inputTransitVehicles.xml"); - } - - /** - * Connects two nodes with link(s) - *

- * Creates segmented links between nodes. Each segment is itself a link with maximum length (minimumLinkLength / euclideanDistance). - * - * @param name the name of the link. - * @param fromNode the source node. - * @param toNode the target node. - * @param speedLevel the maximum speed level allowed on the link. - * @param originalLinkLength the original link length (set to <= 0 to use the euclidean distance) - * @param linkSegmentLength the length of the link segments. - * @param attributes - * @return A list holding the link ids between the connected nodes. - */ - public List> connectNodes(String name, Node fromNode, Node toNode, double speedLevel, double originalLinkLength, double linkSegmentLength, Attributes attributes) { - - // lookup if link already exists - String nodeIds = fromNode.getId().toString() + "_" + toNode.getId().toString(); - List> links = connectedLinks.get(nodeIds); - if (links != null) { - log.info("Link already exists, skipping " + nodeIds); - return links; - } - - // create links - log.info("Create link " + nodeIds); - - List nodes = new ArrayList<>(); - double distance = originalLinkLength; - if (originalLinkLength <= 0.) { - distance = NetworkUtils.getEuclideanDistance(fromNode.getCoord(), toNode.getCoord()); - } - if (distance <= linkSegmentLength) { - nodes.add(fromNode); - nodes.add(toNode); - } else { - // add first node - nodes.add(fromNode); - - // add additional nodes in between... - - int nodeCounter = 0; - for (double fraction = linkSegmentLength / distance; fraction < 1.; ) { - LineSegment ls = new LineSegment(fromNode.getCoord().getX(), fromNode.getCoord().getY(), toNode.getCoord().getX(), toNode.getCoord().getY()); - Coordinate point = ls.pointAlong(fraction); - Coord coord = new Coord(point.x, point.y); - String idBetweenNode = fromNode.getId().toString() + "-" + toNode.getId().toString() + "-" + nodeCounter; -// log.info("Adding 'between node' " + idBetweenNode); - Node betweenNode = addNode(idBetweenNode, coord); - nodes.add(betweenNode); - - fraction = fraction + linkSegmentLength / distance; - nodeCounter++; - } - - // add last node - nodes.add(toNode); - } - - links = connectStopsAndGetRailLinks(name, nodes, speedLevel, originalLinkLength, attributes); - connectedLinks.put(nodeIds, links); - return links; - } - - private List> connectStopsAndGetRailLinks(String name, List nodes, double speedLevel, double originalLinkLength, Attributes attributes) { - - if (nodes.size() < 2) throw new RuntimeException("At least two route stops required. Aborting..."); - - List> railLinks = new ArrayList<>(); - - log.info("Connecting nodes..."); - - double length = originalLinkLength / (nodes.size() - 1.); - length = Math.round(length); - if (length <= 0.) { - log.warn("An euclidean distance of " + length + " is not accepted."); - length = 5.; - log.warn("... the length will be set to " + length); - } - - int linkCounter = 0; - - Node previousNode = null; - for (Node node : nodes) { - - if (previousNode == null) { - // first node - } else { - // not the first terminal - // add connecting link - Link connectingLink = getOrCreateLink(name, linkCounter, previousNode, node, speedLevel, length, attributes); - linkCounter++; - - railLinks.add(connectingLink.getId()); - - } - previousNode = node; - } - return railLinks; - } - - public Node addNode(String name, Coord coord) { - - Id id = Id.createNodeId(name); - - if (this.scenario.getNetwork().getNodes().get(id) != null) { - return this.scenario.getNetwork().getNodes().get(id); - } - - Node node = this.scenario.getNetwork().getFactory().createNode(id, coord); - this.scenario.getNetwork().addNode(node); - return node; - } - - private Link getOrCreateLink(String name, int linkCounter, Node fromNode, Node toNode, double freespeed, double length, Attributes attributes) { - - if (length <= 0.) throw new RuntimeException("Length " + length + " not accepted. Aborting..."); - - Id linkId = Id.create(name + "_" + linkCounter, Link.class); - - if (this.scenario.getNetwork().getLinks().get(linkId) != null) { - return this.scenario.getNetwork().getLinks().get(linkId); - } - - Link link = this.scenario.getNetwork().getFactory().createLink(linkId, fromNode, toNode); - link.setAllowedModes(new HashSet<>(List.of("rail"))); - link.setLength(length); - link.setFreespeed(freespeed); - link.setCapacity(3600.); - link.setNumberOfLanes(1.); - for (String attribute : attributes.getAsMap().keySet()) { - link.getAttributes().putAttribute(attribute, attributes.getAttribute(attribute)); - } - this.scenario.getNetwork().addLink(link); - - return link; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java deleted file mode 100644 index 71b4e25195f..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/DepotInfo.java +++ /dev/null @@ -1,132 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import org.matsim.api.core.v01.Coord; -import org.matsim.api.core.v01.network.Link; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * Depot information - * - * @author Merlin Unterfinger - */ -public class DepotInfo { - private final String id; - private final Coord coord; - private final double length; - private final double inLength; - private final double outLength; - private final int capacity; - private final Map inLinkAttributes = new HashMap<>(); - private final Map depotLinkAttributes = new HashMap<>(); - private final Map outLinkAttributes = new HashMap<>(); - private TransitStopFacility depot; - private Link depotIn; - private Link depotLink; - private Link depotOut; - - public DepotInfo(String id, Coord coord, double length, double inLength, double outLength, int capacity) { - this.id = id; - this.coord = coord; - this.length = length; - this.capacity = capacity; - this.inLength = inLength; - this.outLength = outLength; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - var depotInfo = (DepotInfo) o; - if (Double.compare(depotInfo.length, length) != 0) return false; - if (Double.compare(depotInfo.inLength, inLength) != 0) return false; - if (Double.compare(depotInfo.outLength, outLength) != 0) return false; - if (capacity != depotInfo.capacity) return false; - if (!Objects.equals(id, depotInfo.id)) return false; - if (!Objects.equals(coord, depotInfo.coord)) return false; - if (!inLinkAttributes.equals(depotInfo.inLinkAttributes)) return false; - if (!depotLinkAttributes.equals(depotInfo.depotLinkAttributes)) return false; - if (!outLinkAttributes.equals(depotInfo.outLinkAttributes)) return false; - if (!Objects.equals(depot, depotInfo.depot)) return false; - if (!Objects.equals(depotIn, depotInfo.depotIn)) return false; - if (!Objects.equals(depotLink, depotInfo.depotLink)) return false; - return Objects.equals(depotOut, depotInfo.depotOut); - } - - @Override - public int hashCode() { - return id != null ? id.hashCode() : 0; - } - - public Map getInLinkAttributes() { - return inLinkAttributes; - } - - public Map getDepotLinkAttributes() { - return depotLinkAttributes; - } - - public Map getOutLinkAttributes() { - return outLinkAttributes; - } - - public String getId() { - return id; - } - - public Coord getCoord() { - return coord; - } - - public double getLength() { - return length; - } - - public int getCapacity() { - return capacity; - } - - public double getInLength() { - return inLength; - } - - public double getOutLength() { - return outLength; - } - - public TransitStopFacility getDepot() { - return depot; - } - - public Link getDepotIn() { - return depotIn; - } - - public Link getDepotLink() { - return depotLink; - } - - public Link getDepotOut() { - return depotOut; - } - - public void setDepot(TransitStopFacility depot) { - this.depot = depot; - } - - public void setDepotIn(Link depotIn) { - this.depotIn = depotIn; - } - - public void setDepotLink(Link depotLink) { - this.depotLink = depotLink; - } - - public void setDepotOut(Link depotOut) { - this.depotOut = depotOut; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java deleted file mode 100644 index 79b583a2238..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/InfrastructureRepository.java +++ /dev/null @@ -1,47 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; - -import java.util.Map; - -/** - * Infrastructure repository - *

- * Implement a repository to provide for capacities, speed limits and coordinates of depots, stops and links. - * - * @author Merlin Unterfinger - */ -public interface InfrastructureRepository { - StopInfo getStop(String stopId, double x, double y); - - DepotInfo getDepot(StopInfo stopInfo); - - SectionPartInfo getSectionPart(StopInfo fromStop, StopInfo toStop); - - static void addRailsimAttributes(StopInfo stopInfo, int capacity, double speedLimit, double grade) { - InfrastructureRepository.addRailsimLinkAttributes(stopInfo.getLinkAttributes(), capacity, speedLimit, grade); - } - - static void addRailsimAttributes(DepotInfo depotInfo, int capacity, int inOutCapacity, double speedLimit, double grade) { - // add in link attributes - addRailsimLinkAttributes(depotInfo.getInLinkAttributes(), inOutCapacity, speedLimit, grade); - // add depot link attributes - addRailsimLinkAttributes(depotInfo.getDepotLinkAttributes(), capacity, speedLimit, grade); - // add out link attributes - addRailsimLinkAttributes(depotInfo.getOutLinkAttributes(), inOutCapacity, speedLimit, grade); - } - - static void addRailsimAttributes(SectionSegmentInfo sectionSegmentInfo, int capacity, double speedLimit, double grade) { - InfrastructureRepository.addRailsimLinkAttributes(sectionSegmentInfo.getLinkAttributes(), capacity, speedLimit, grade); - } - - private static void addRailsimLinkAttributes(Map attributes, int capacity, double speedLimit, double grade) { - attributes.put(RailsimUtils.LINK_ATTRIBUTE_CAPACITY, capacity); - attributes.put(RailsimUtils.LINK_ATTRIBUTE_MAX_SPEED, speedLimit); - attributes.put(RailsimUtils.LINK_ATTRIBUTE_GRADE, grade); - } - - static void addRailsimLinkAttributes(Map attributes, String vehicleType, double speedLimit) { - attributes.put(String.format("%s_%s", RailsimUtils.LINK_ATTRIBUTE_MAX_SPEED, vehicleType), speedLimit); - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java deleted file mode 100644 index 4aa00894fc0..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/LinkType.java +++ /dev/null @@ -1,31 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -/** - * The possible link types in the network. - * - * @author Merlin Unterfinger - */ -enum LinkType { - /** - * Links inside the stations. - */ - STOP("stp"), - /** - * Links in the depot and connecting the depot. - */ - DEPOT("dpt"), - /** - * Links on the route between two stations. - */ - ROUTE("rte"); - - private final String abbreviation; - - LinkType(String abbreviation) { - this.abbreviation = abbreviation; - } - - public String getAbbreviation() { - return abbreviation; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java deleted file mode 100644 index 36342e31997..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilder.java +++ /dev/null @@ -1,417 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.circuits.DefaultVehicleCircuitsPlanner; -import ch.sbb.matsim.contrib.railsim.prototype.supply.circuits.NoVehicleCircuitsPlanner; -import ch.sbb.matsim.contrib.railsim.prototype.supply.infrastructure.DefaultInfrastructureRepository; -import ch.sbb.matsim.contrib.railsim.prototype.supply.rollingstock.DefaultRollingStockRepository; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Coord; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.config.ConfigUtils; -import org.matsim.pt.transitSchedule.api.Departure; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitRouteStop; -import org.matsim.vehicles.VehicleType; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * Generate the supply for MATSim railsim extension - *

- * Create transit schedules and networks for running simulations with the railsim extension, by adding individual transit lines. - * - * @author Merlin Unterfinger - * @author Ihab Kaddoura - */ -public class RailsimSupplyBuilder { - - private static final Logger log = LogManager.getLogger(RailsimSupplyBuilder.class); - private final Scenario scenario; - private final InfrastructureRepository infrastructureRepository; - private final RollingStockRepository rollingStockRepository; - private final VehicleCircuitsPlanner vehicleCircuitsPlanner; - private final RailsimSupplyConfigGroup railsimSupplyConfigGroup; - private final SupplyFactory supplyFactory; - private final Map stopInfos = new HashMap<>(); - private final Map transitLineInfos = new HashMap<>(); - private final Map vehicleTypeInfos = new HashMap<>(); - private final Map depotInfos = new HashMap<>(); - private final Map sectionPartInfos = new HashMap<>(); - private final Map>> sectionParts = new HashMap<>(); - - - /** - * @param scenario a scenario, set the CRS. - */ - public RailsimSupplyBuilder(Scenario scenario) { - this(scenario, new DefaultInfrastructureRepository(scenario), new DefaultRollingStockRepository(scenario), switch (ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class).getCircuitPlanningApproach()) { - case DEFAULT -> new DefaultVehicleCircuitsPlanner(scenario); - case NONE -> new NoVehicleCircuitsPlanner(); - }); - } - - /** - * @param scenario a scenario, set the CRS. - * @param infrastructureRepository an infrastructure provider for rail capacities, speed limits and coordinates of depots and stops. - * @param rollingStockRepository a rolling stock provider, to create the vehicle types with attributes (maximum velocity, passenger capacity, acceleration and deceleration). - * @param vehicleCircuitsPlanner a vehicle circuits planner for planning the transit line vehicle allocations. - */ - public RailsimSupplyBuilder(Scenario scenario, InfrastructureRepository infrastructureRepository, RollingStockRepository rollingStockRepository, VehicleCircuitsPlanner vehicleCircuitsPlanner) { - this.scenario = scenario; - this.infrastructureRepository = infrastructureRepository; - this.rollingStockRepository = rollingStockRepository; - this.vehicleCircuitsPlanner = vehicleCircuitsPlanner; - railsimSupplyConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); - supplyFactory = new SupplyFactory(scenario); - } - - /** - * Adds a stop info to the supply. - *

- * Note: StopInfos have only to be added once. Use the RouteStopInfo to define a route profile. - * - * @param id the unique id or name of the stop. - * @param x the x coordinates of the stop. - * @param y the y coordinates of the stop. - */ - public void addStop(String id, double x, double y) { - // ensure unique stop ids - if (stopInfos.containsKey(id)) { - throw new RuntimeException("Stop already existing for id " + id); - } - // get stop infos from infrastructure repository - var stopInfo = infrastructureRepository.getStop(id, x, y); - x = stopInfo.getCoord().getX(); - y = stopInfo.getCoord().getY(); - final double stopLinkLength = stopInfo.getStopLinkLength(); - // create the stop link - var tIn = supplyFactory.createNode(id + "_IN", new Coord(x - stopLinkLength, y)); - var tOut = supplyFactory.createNode(id + "_OUT", stopInfo.getCoord()); - var stopLink = supplyFactory.createLink(LinkType.STOP, tIn, tOut, stopLinkLength, stopInfo.getLinkAttributes()); - // put transit stop on the link - var stop = supplyFactory.createTransitStopFacility(id, stopLink); - // store stop information - stopInfo.setStopLink(stopLink); - stopInfo.setStop(stop); - this.stopInfos.put(stopInfo.getId(), stopInfo); - } - - /** - * Get a stop info from the supply builder. - * - * @param id the unique id or name of the stop. - * @return returns a StopInfo from the supply if it exists and null otherwise. - */ - StopInfo getStop(String id) { - StopInfo stopInfo = stopInfos.get(id); - if (stopInfo == null) { - throw new RuntimeException(String.format("Stop %s not existing, it has to be added first.", id)); - } - return stopInfo; - } - - /** - * Creates and adds a transit line information to the supply. - *

- * The TransitLineInfo has then to be completed with further stops (addStop), passes (addPass) and departures (addDepartures). - * - * @param id the unique name or id of the transit line. - * @param vehicleTypeid the unique name or id of the vehicle type used in the transit line. - * @param firstStopId the unique name or id of the first stop. - * @param waitingTime the waiting time in seconds at the first stop. - * @return A transit line information object. - */ - public TransitLineInfo addTransitLine(String id, String vehicleTypeid, String firstStopId, double waitingTime) { - // ensure unique transit line ids - if (transitLineInfos.containsKey(id)) { - throw new RuntimeException("Transit line already existing for id " + id); - } - var firstStopInfo = getStop(firstStopId); - // get vehicle type info from repository and ensure consistency - var vehicleTypeInfo = rollingStockRepository.getVehicleType(vehicleTypeid); - if (!isConsistent(vehicleTypeInfo)) { - throw new RuntimeException("Vehicle type from repository is not consistent for id " + vehicleTypeInfo.getId()); - } - // create transit line and matsim object - var transitLineInfo = new TransitLineInfo(id, new RouteStopInfo(firstStopInfo, waitingTime), vehicleTypeInfo, supplyFactory.createTransitLine(id), this); - // store transit line info - transitLineInfos.put(id, transitLineInfo); - return transitLineInfo; - } - - private boolean isConsistent(VehicleTypeInfo received) { - var existing = vehicleTypeInfos.get(received.getId()); - if (existing == null) { - vehicleTypeInfos.put(received.getId(), received); - return true; - } - return received.equals(existing); - } - - /** - * Build the transit schedule - *

- * Plans the vehicle circuits and creates the needed MATSim objects (transit routes, vehicle types and vehicles). - */ - public void build() { - // build transit lines and create route links - transitLineInfos.values().forEach(TransitLineInfo::build); - // plan vehicle circuits - var vehicleAllocations = vehicleCircuitsPlanner.plan(new ArrayList<>(transitLineInfos.values())); - // add transit lines with allocated vehicles to the schedule - for (var entry : vehicleAllocations.entrySet()) { - final var transitLineInfo = entry.getKey(); - final var vehicleTypeInfo = transitLineInfo.getVehicleTypeInfo(); - final var vehicleAllocationInfo = entry.getValue(); - // construct or get vehicle type - var vehicleType = supplyFactory.getOrCreateVehicleType(vehicleTypeInfo.getId(), vehicleTypeInfo.getLength(), vehicleTypeInfo.getMaxVelocity(), vehicleTypeInfo.getCapacity(), vehicleTypeInfo.getAttributes()); - // add routes for each type and direction - for (var routeType : RouteType.values()) { - // from origin - var departures = vehicleAllocationInfo.getDepartures(routeType, RouteDirection.FORWARD); - var vehicleIds = vehicleAllocationInfo.getVehicleIds(routeType, RouteDirection.FORWARD); - addTransitRouteToTransitLine(transitLineInfo, routeType, vehicleType, RouteDirection.FORWARD, departures, vehicleIds); - // from destination - departures = vehicleAllocationInfo.getDepartures(routeType, RouteDirection.REVERSE); - vehicleIds = vehicleAllocationInfo.getVehicleIds(routeType, RouteDirection.REVERSE); - addTransitRouteToTransitLine(transitLineInfo, routeType, vehicleType, RouteDirection.REVERSE, departures, vehicleIds); - } - } - } - - /** - * Add a depot to the stop - *

- * Creates a TransitStop facility and inLink, depotLink and outLink for the depot. Then constructs a DepotInfo and sets it on the stopInfo. - * - *

-	 * 		(t_IN)------------------>(t_OUT)
-	 * 		^          stationLink         |
-	 * 		| outLink               inLink |
-	 * 		|           depotLink          v
-	 * 		(t_DPT_OUT)<----------(t_DPT_IN)
-	 * 
- * - * @param stopInfo the stopInfo to add the depot to. - */ - private void addDepotToStop(StopInfo stopInfo) { - // request depot info from infrastructure repository and ensure consistency - var depotInfo = infrastructureRepository.getDepot(stopInfo); - if (!isConsistent(depotInfo)) { - throw new RuntimeException("Depot is from repository not consistent for id " + depotInfo.getId()); - } - // create nodes - log.info("Adding depot to stop " + stopInfo.getId()); - var dIn = supplyFactory.createNode(depotInfo.getId() + "_DPT_IN", depotInfo.getCoord()); - var dOut = supplyFactory.createNode(depotInfo.getId() + "_DPT_OUT", new Coord(depotInfo.getCoord().getX() - depotInfo.getLength(), depotInfo.getCoord().getY())); - // create depot links - var depotInLink = supplyFactory.createLink(LinkType.DEPOT, stopInfo.getStopLink().getToNode(), dIn, depotInfo.getInLength(), depotInfo.getInLinkAttributes()); - var depotLink = supplyFactory.createLink(LinkType.DEPOT, dIn, dOut, depotInfo.getLength(), depotInfo.getDepotLinkAttributes()); - var depotOutLink = supplyFactory.createLink(LinkType.DEPOT, dOut, stopInfo.getStopLink().getFromNode(), depotInfo.getOutLength(), depotInfo.getOutLinkAttributes()); - // put transit depot on the link - var depot = supplyFactory.createTransitStopFacility(stopInfo.getId() + "_DPT", depotLink); - // add links and stop facility to depot - depotInfo.setDepot(depot); - depotInfo.setDepotIn(depotInLink); - depotInfo.setDepotLink(depotLink); - depotInfo.setDepotOut(depotOutLink); - // add depot to transit stop - stopInfo.setDepotInfo(depotInfo); - } - - private boolean isConsistent(DepotInfo received) { - var existing = depotInfos.get(received.getId()); - if (existing == null) { - depotInfos.put(received.getId(), received); - return true; - } - return received.equals(existing); - } - - /** - * Creates and adds routes for a transit route type to the transit line - *

- * Note: The TransitLine object has to be created and added to the TransitLineInfo object beforehand. The HashMaps containing the departures and vehicles for each route type have to be created - * already. - * - * @param transitLineInfo transit line info container (which holds the transit line). - * @param routeType the type of the route to create (e.g. STATION_TO_DEPOT). - * @param vehicleType the type of the vehicle for the route. - * @param routeDirection the direction of the route. - * @param departures the HashMap which holds the departures for the route type. - * @param vehicleIds the HashMap which holds the vehicle ids for the route type. - */ - private void addTransitRouteToTransitLine(TransitLineInfo transitLineInfo, RouteType routeType, VehicleType vehicleType, RouteDirection routeDirection, LinkedList departures, LinkedList vehicleIds) { - if (departures == null || vehicleIds == null) { - log.info(String.format("Omitting route type %s for transit line %s (%s)...", routeType.toString(), transitLineInfo.getId(), routeDirection)); - return; - } - if (departures.size() != vehicleIds.size()) { - throw new RuntimeException(String.format("Failed adding transit route for %s (%s); departures and vehicles must have same size.", transitLineInfo.getId(), routeDirection)); - } - log.info(String.format("Adding routes (n=%d) for route type %s for transit line %s (%s)...", departures.size(), routeType.toString(), transitLineInfo.getId(), routeDirection)); - // get a copy of the transit line attribute lists (shallow, since only the order matters) - final LinkedList routeStopInfos = new LinkedList<>(transitLineInfo.getRouteStopInfos(routeDirection)); - final LinkedList travelTimes = new LinkedList<>(transitLineInfo.getTravelTimes(routeDirection)); - final LinkedList> routeLinks = new LinkedList<>(transitLineInfo.getRouteLinks(routeDirection)); - // build route type - final double depotTravelTime = railsimSupplyConfigGroup.getDepotTravelTime(); - if (routeType == RouteType.DEPOT_TO_STATION || routeType == RouteType.DEPOT_TO_DEPOT) { - addDepotAtOriginOfRoute(departures, routeStopInfos, travelTimes, routeLinks, depotTravelTime); - } - if (routeType == RouteType.STATION_TO_DEPOT || routeType == RouteType.DEPOT_TO_DEPOT) { - addDepotAtDestinationOfRoute(routeStopInfos, travelTimes, routeLinks, depotTravelTime); - } - // construct route - final List stops = new ArrayList<>(); - double cumulativeTravelTime = 0.; - // first stop - stops.add(supplyFactory.createTransitTerminalStop(routeStopInfos.get(0).getTransitStop(), cumulativeTravelTime, true)); - // intermediate stops - for (int stopCounter = 1; stopCounter <= routeStopInfos.size() - 2; stopCounter++) { - // increase cumulative travel time until arrival - cumulativeTravelTime += travelTimes.get(stopCounter - 1); - // increase cumulative travel time by waiting time until departure - double departureTime = cumulativeTravelTime + routeStopInfos.get(stopCounter).getWaitingTime(); - TransitRouteStop transitStop = supplyFactory.createTransitRouteStop(routeStopInfos.get(stopCounter).getTransitStop(), cumulativeTravelTime, departureTime, true); - stops.add(transitStop); - // set cumulative travel time to departure time - cumulativeTravelTime = departureTime; - } - // final stop - cumulativeTravelTime += travelTimes.getLast(); - stops.add(supplyFactory.createTransitTerminalStop(routeStopInfos.get(routeStopInfos.size() - 1).getTransitStop(), cumulativeTravelTime, false)); - // define route and add to transit line - final String routeId = String.format("%s_%s_%s", transitLineInfo.getId(), routeDirection.getAbbreviation(), routeType); - TransitRoute route = supplyFactory.createTransitRoute(transitLineInfo.getTransitLine(), routeId, routeLinks, stops); - // add departures and vehicles - Iterator iter = vehicleIds.iterator(); - int depCounter = 0; - for (Double departureTime : departures) { - Departure departure = supplyFactory.createDeparture(String.valueOf(depCounter), departureTime); - departure.setVehicleId(supplyFactory.getOrCreateVehicle(vehicleType, iter.next()).getId()); - route.addDeparture(departure); - depCounter++; - } - } - - /** - * Adds a depot at the origin of the route - *

- * The departure time is decreased by the depot travel time. - * - * @param departures the departures when the route is started. - * @param routeStopInfos the ordered stops of the route. - * @param travelTimes the ordered travel times between the stops. - * @param routeLinks the ordered links between the stops of the complete route. - * @param depotTravelTime the travel time to reach the depot. - */ - private void addDepotAtOriginOfRoute(LinkedList departures, LinkedList routeStopInfos, LinkedList travelTimes, LinkedList> routeLinks, double depotTravelTime) { - final var origStop = routeStopInfos.getFirst().getStopInfo(); - final double waitingTime = routeStopInfos.getFirst().getWaitingTime(); - if (origStop.hasNoDepot()) { - addDepotToStop(origStop); - } - // decrease departures, since the first stop is set to the depot - for (int i = 0; i < departures.size(); i++) { - double departure = departures.removeFirst(); - departures.addLast(departure - (depotTravelTime + waitingTime)); - } - // add origin depot stop and links - var origDepo = origStop.getDepotInfo(); - routeStopInfos.addFirst(new RouteStopInfo(origDepo.getDepotLink(), origDepo.getDepot(), 0.)); - routeLinks.addAll(0, List.of(origDepo.getDepotLink().getId(), origDepo.getDepotOut().getId())); - travelTimes.addFirst(depotTravelTime); - } - - /** - * Adds a depot at the destination of the route - * - * @param routeStopInfos the ordered stops of the route. - * @param travelTimes the ordered travel times between the stops. - * @param routeLinks the ordered links between the stops of the complete route. - * @param depotTravelTime the travel time to reach the depot. - */ - private void addDepotAtDestinationOfRoute(LinkedList routeStopInfos, LinkedList travelTimes, LinkedList> routeLinks, double depotTravelTime) { - var destStop = routeStopInfos.getLast().getStopInfo(); - if (destStop.hasNoDepot()) { - addDepotToStop(destStop); - } - // add destination depot stop and links - var destDepo = destStop.getDepotInfo(); - routeStopInfos.add(new RouteStopInfo(destDepo.getDepotLink(), destDepo.getDepot(), 0.)); - routeLinks.addAll(List.of(destDepo.getDepotIn().getId(), destDepo.getDepotLink().getId())); - travelTimes.add(depotTravelTime); - } - - /** - * Connect two stops - *

- * Connects two stops (or passes) of a transit line in the transit network . - * - * @param fromStop starting stop. - * @param toStop destination stop. - * @return A list of connecting links. - */ - List> connectStops(StopInfo fromStop, StopInfo toStop) { - String id = String.format("%s_%s", fromStop.getId(), toStop.getId()); - var sectionPart = sectionParts.get(id); - if (sectionPart != null) { - log.debug("Section part already existing, skipping {}", id); - return sectionPart; - } - // request section part information from infrastructure repository and ensure consistency - var sectionPartInfo = infrastructureRepository.getSectionPart(fromStop, toStop); - if (!isConsistent(sectionPartInfo)) { - throw new RuntimeException("Section part from repository is not consistent for id " + createSectionPartInfoId(sectionPartInfo)); - } - // create link for each section until last - var nodePrefix = String.format("%s_", id); - var links = new ArrayList>(); - var sectionSegmentInfos = sectionPartInfo.getSectionSegmentInfos(); - var currentNode = fromStop.getStopLink().getToNode(); - var currentSegmentInfo = sectionSegmentInfos.get(0); - int count = 0; - for (int i = 0; i < sectionSegmentInfos.size() - 1; i++) { - var nextNode = supplyFactory.createNode(nodePrefix + count, sectionSegmentInfos.get(i + 1).getToCoord()); - var nextSegmentInfo = sectionSegmentInfos.get(i + 1); - links.add(supplyFactory.createLink(LinkType.ROUTE, currentNode, nextNode, currentSegmentInfo.getLength(), currentSegmentInfo.getLinkAttributes()).getId()); - currentNode = nextNode; - currentSegmentInfo = nextSegmentInfo; - count++; - } - // create and add last link - var lastStop = toStop.getStopLink().getFromNode(); - var lastSegmentInfo = sectionSegmentInfos.get(sectionSegmentInfos.size() - 1); - links.add(supplyFactory.createLink(LinkType.ROUTE, currentNode, lastStop, lastSegmentInfo.getLength(), lastSegmentInfo.getLinkAttributes()).getId()); - // store links of section part - sectionParts.put(id, links); - return links; - } - - private boolean isConsistent(SectionPartInfo received) { - var id = createSectionPartInfoId(received); - var existing = sectionPartInfos.get(id); - if (existing == null) { - sectionPartInfos.put(id, received); - return true; - } - return received.equals(existing); - } - - private static String createSectionPartInfoId(SectionPartInfo sectionPartInfo) { - return String.format("%s_%s", sectionPartInfo.getFromStopId(), sectionPartInfo.getToStopId()); - } - - public Scenario getScenario() { - return scenario; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java deleted file mode 100644 index a27ed5e201b..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyConfigGroup.java +++ /dev/null @@ -1,333 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.core.config.Config; -import org.matsim.core.config.ReflectiveConfigGroup; - -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Map; - -/** - * Railsim supply config group - *

- * Config group to configure the supply generation for railsim. - * - * @author Merlin Unterfinger - */ -public class RailsimSupplyConfigGroup extends ReflectiveConfigGroup { - - private static final Logger log = LogManager.getLogger(RailsimConfigGroup.class); - public static final String GROUP_NAME = "railsimSupply"; - // stop - private static final String STOP_LINK_LENGTH = "stopLinkLength"; // 751. - private static final String STOP_TRAIN_CAPACITY = "stopTrainCapacity"; // 3 - private static final String STOP_SPEED_LIMIT = "stopSpeedLimit"; // 90. / 3.6; - // route - private static final String ROUTE_TRAIN_CAPACITY = "routeTrainCapacity"; // 1 - private static final String ROUTE_SPEED_LIMIT = "routeSpeedLimit"; // 100. / 3.6; - private static final String ROUTE_EUCLIDEAN_DISTANCE_FACTOR = "routeEuclideanDistanceFactor"; // 1 - // depot - private static final String DEPOT_TRAIN_CAPACITY = "depotTrainCapacity"; // 999 - private static final String DEPOT_IN_OUT_CAPACITY = "depotInOutCapacity"; // 1 - private static final String DEPOT_OFFSET = "depotOffset"; // 100. - private static final String DEPOT_TRAVEL_TIME = "depotTravelTime"; // 10 * 60 - private static final String DEPOT_SPEED_LIMIT = "depotSpeedLimit"; // 5./3.6 - private static final String DEPOT_LINK_LENGTH = "depotLinkLength"; // 751. - // vehicle - private static final String VEHICLE_PASSENGER_CAPACITY = "vehiclePassengerCapacity"; // 500 - private static final String VEHICLE_LENGTH = "vehicleLength"; // 200. - private static final String VEHICLE_MAX_VELOCITY = "vehicleMaxVelocity"; // 150 / 3.6 - private static final String VEHICLE_MAX_DECELERATION = "vehicleMaxDeceleration"; // 0.5 - private static final String VEHICLE_MAX_ACCELERATION = "vehicleMaxAcceleration"; // 0.5 - private static final String VEHICLE_TURNAROUND_TIME = "vehicleTurnaroundTime"; // 5. * 60 - // circuit - private static final String CIRCUIT_MAX_WAITING_TIME = "circuitMaxWaitingTime"; // 20 * 60 - private static final String CIRCUIT_PLANNING_APPROACH = "circuitPlanningApproach"; // DEFAULT - - public enum CircuitPlanningApproach {DEFAULT, NONE} - - /** - * Ctor - */ - public RailsimSupplyConfigGroup() { - super(GROUP_NAME); - } - - // stop - private double stopLinkLength = 751.; // meters - private int stopTrainCapacity = 3; // number of rails in station - private double stopSpeedLimit = 90. / 3.6; // meters per second - // route - private int routeTrainCapacity = 1; // number of rails - private double routeSpeedLimit = 120. / 3.6; // meters per second - private double routeEuclideanDistanceFactor = 1.; // factor - // depot - private int depotTrainCapacity = 999; // number of trains - private int depotInOutCapacity = 1; // number of rails - private double depotOffset = 100.; // meters - private double depotTravelTime = 10. * 60; // seconds - private double depotSpeedLimit = 5. / 3.6; // meters per second - private double depotLinkLength = 751.; // meters - // vehicle - private int vehiclePassengerCapacity = 500; - private double vehicleLength = 200.; - private double vehicleMaxVelocity = 150 / 3.6; // meters per second - private double vehicleMaxAcceleration = 0.5; // meters per seconds^2 - private double vehicleMaxDeceleration = 0.5; // meters per seconds^2 - private double vehicleTurnaroundTime = 5. * 60; // seconds - // circuit - private double circuitMaxWaitingTime = 20. * 60; // meters per second - private CircuitPlanningApproach circuitPlanningApproach = CircuitPlanningApproach.DEFAULT; // options - - @Override - public Map getComments() { - Map comments = super.getComments(); - // stop - comments.put(STOP_LINK_LENGTH, "Length of the stop links in the stations, should be longer than the longest stopping train."); - comments.put(STOP_TRAIN_CAPACITY, "The number of parallel rails in the station sections."); - comments.put(STOP_SPEED_LIMIT, "The maximum speed allowed in the station in meters per second."); - // route - comments.put(ROUTE_TRAIN_CAPACITY, "The number of parallel rails of the route sections. "); - comments.put(ROUTE_SPEED_LIMIT, "The maximum speed allowed on the route sections in meters per second."); - comments.put(ROUTE_EUCLIDEAN_DISTANCE_FACTOR, "Factor to scale the euclidean distance between to stops for the route length definition."); - // depot - comments.put(DEPOT_TRAIN_CAPACITY, "The number of trains allowed in the depot, should be high enough to prevent grid locks."); - comments.put(DEPOT_IN_OUT_CAPACITY, "The number of rails that are connecting a depot to its stop."); - comments.put(DEPOT_OFFSET, "The vertical offset in meters, to place the depot to the stop."); - comments.put(DEPOT_TRAVEL_TIME, "The travel time to enter a depot from a stop or to reach the stop from the depot."); - comments.put(DEPOT_SPEED_LIMIT, "The speed allowed in the depot and on the connecting links."); - comments.put(DEPOT_LINK_LENGTH, "The length of the depot link, should be larger than the maximum vehicle length."); - // vehicle - comments.put(VEHICLE_PASSENGER_CAPACITY, "The number of passengers per vehicle."); - comments.put(VEHICLE_LENGTH, "The total length of the vehicle in meters."); - comments.put(VEHICLE_MAX_VELOCITY, "The maximum speed the vehicle is capable of driving."); - comments.put(VEHICLE_MAX_ACCELERATION, "The maximum braking acceleration of the vehicle."); - comments.put(VEHICLE_MAX_DECELERATION, "The maximum braking declaration of the vehicle."); - comments.put(VEHICLE_TURNAROUND_TIME, "The time it takes the vehicle to turnaround in station (changing the driver's cab)"); - // circuit - comments.put(CIRCUIT_MAX_WAITING_TIME, "The maximum waiting time of a vehicle on a stop link for the next circuit. If the next departure is after this waiting time, the vehicle is sent to the " + "depot."); - comments.put(CIRCUIT_PLANNING_APPROACH, "The circuits planning approach: " + Arrays.toString(CircuitPlanningApproach.values()) + ". " + CircuitPlanningApproach.DEFAULT + " Plan simple vehicle circuits (default). " + CircuitPlanningApproach.NONE + " Omit vehicle circuits and sent a new vehicle from depot to depot for each route (needs a high depot capacity)."); - return comments; - } - - @Override - protected void checkConsistency(Config config) { - log.info("Checking consistency in railsim preparation config group..."); - for (Field field : this.getClass().getDeclaredFields()) { - if (field.getType().isPrimitive() && Number.class.isAssignableFrom(field.getType())) { - try { - field.setAccessible(true); - Number value = (Number) field.get(this); - if (value.doubleValue() < 0) { - throw new RuntimeException(String.format("Negative value %f found for %s", value.doubleValue(), field.getName())); - } - } catch (IllegalAccessException e) { - log.error("Unable to check field {}", field); - } - } - } - } - - @StringGetter(STOP_LINK_LENGTH) - public double getStopLinkLength() { - return stopLinkLength; - } - - @StringSetter(STOP_LINK_LENGTH) - public void setStopLinkLength(double stopLinkLength) { - this.stopLinkLength = stopLinkLength; - } - - @StringGetter(STOP_TRAIN_CAPACITY) - public int getStopTrainCapacity() { - return stopTrainCapacity; - } - - @StringSetter(STOP_TRAIN_CAPACITY) - public void setStopTrainCapacity(int stopTrainCapacity) { - this.stopTrainCapacity = stopTrainCapacity; - } - - @StringGetter(STOP_SPEED_LIMIT) - public double getStopSpeedLimit() { - return stopSpeedLimit; - } - - @StringSetter(STOP_SPEED_LIMIT) - public void setStopSpeedLimit(double stopSpeedLimit) { - this.stopSpeedLimit = stopSpeedLimit; - } - - @StringGetter(ROUTE_TRAIN_CAPACITY) - public int getRouteTrainCapacity() { - return routeTrainCapacity; - } - - @StringSetter(ROUTE_TRAIN_CAPACITY) - public void setRouteTrainCapacity(int routeTrainCapacity) { - this.routeTrainCapacity = routeTrainCapacity; - } - - @StringGetter(ROUTE_SPEED_LIMIT) - public double getRouteSpeedLimit() { - return routeSpeedLimit; - } - - @StringSetter(ROUTE_SPEED_LIMIT) - public void setRouteSpeedLimit(double routeSpeedLimit) { - this.routeSpeedLimit = routeSpeedLimit; - } - - @StringGetter(ROUTE_EUCLIDEAN_DISTANCE_FACTOR) - public double getRouteEuclideanDistanceFactor() { - return routeEuclideanDistanceFactor; - } - - @StringSetter(ROUTE_EUCLIDEAN_DISTANCE_FACTOR) - public void setRouteEuclideanDistanceFactor(double routeEuclideanDistanceFactor) { - this.routeEuclideanDistanceFactor = routeEuclideanDistanceFactor; - } - - @StringGetter(DEPOT_TRAIN_CAPACITY) - public int getDepotTrainCapacity() { - return depotTrainCapacity; - } - - @StringSetter(DEPOT_TRAIN_CAPACITY) - public void setDepotTrainCapacity(int depotTrainCapacity) { - this.depotTrainCapacity = depotTrainCapacity; - } - - @StringGetter(DEPOT_IN_OUT_CAPACITY) - public int getDepotInOutCapacity() { - return depotInOutCapacity; - } - - @StringSetter(DEPOT_IN_OUT_CAPACITY) - public void setDepotInOutCapacity(int depotInOutCapacity) { - this.depotInOutCapacity = depotInOutCapacity; - } - - @StringGetter(DEPOT_OFFSET) - public double getDepotOffset() { - return depotOffset; - } - - @StringSetter(DEPOT_OFFSET) - public void setDepotOffset(double depotOffset) { - this.depotOffset = depotOffset; - } - - @StringGetter(DEPOT_TRAVEL_TIME) - public double getDepotTravelTime() { - return depotTravelTime; - } - - @StringSetter(DEPOT_TRAVEL_TIME) - public void setDepotTravelTime(double depotTravelTime) { - this.depotTravelTime = depotTravelTime; - } - - @StringGetter(DEPOT_SPEED_LIMIT) - public double getDepotSpeedLimit() { - return depotSpeedLimit; - } - - @StringSetter(DEPOT_SPEED_LIMIT) - public void setDepotSpeedLimit(double depotSpeedLimit) { - this.depotSpeedLimit = depotSpeedLimit; - } - - @StringGetter(DEPOT_LINK_LENGTH) - public double getDepotLinkLength() { - return depotLinkLength; - } - - @StringSetter(DEPOT_LINK_LENGTH) - public void setDepotLinkLength(double depotLinkLength) { - this.depotLinkLength = depotLinkLength; - } - - @StringGetter(VEHICLE_PASSENGER_CAPACITY) - public int getVehiclePassengerCapacity() { - return vehiclePassengerCapacity; - } - - @StringSetter(VEHICLE_PASSENGER_CAPACITY) - public void setVehiclePassengerCapacity(int vehiclePassengerCapacity) { - this.vehiclePassengerCapacity = vehiclePassengerCapacity; - } - - @StringGetter(VEHICLE_LENGTH) - public double getVehicleLength() { - return vehicleLength; - } - - @StringSetter(VEHICLE_LENGTH) - public void setVehicleLength(double vehicleLength) { - this.vehicleLength = vehicleLength; - } - - @StringGetter(VEHICLE_MAX_VELOCITY) - public double getVehicleMaxVelocity() { - return vehicleMaxVelocity; - } - - @StringSetter(VEHICLE_MAX_VELOCITY) - public void setVehicleMaxVelocity(double vehicleMaxVelocity) { - this.vehicleMaxVelocity = vehicleMaxVelocity; - } - - @StringGetter(VEHICLE_MAX_ACCELERATION) - public double getVehicleMaxAcceleration() { - return vehicleMaxAcceleration; - } - - @StringSetter(VEHICLE_MAX_ACCELERATION) - public void setVehicleMaxAcceleration(double vehicleMaxAcceleration) { - this.vehicleMaxAcceleration = vehicleMaxAcceleration; - } - - @StringGetter(VEHICLE_MAX_DECELERATION) - public double getVehicleMaxDeceleration() { - return vehicleMaxDeceleration; - } - - @StringSetter(VEHICLE_MAX_DECELERATION) - public void setVehicleMaxDeceleration(double vehicleMaxDeceleration) { - this.vehicleMaxDeceleration = vehicleMaxDeceleration; - } - - @StringGetter(VEHICLE_TURNAROUND_TIME) - public double getVehicleTurnaroundTime() { - return vehicleTurnaroundTime; - } - - @StringSetter(VEHICLE_TURNAROUND_TIME) - public void setVehicleTurnaroundTime(double vehicleTurnaroundTime) { - this.vehicleTurnaroundTime = vehicleTurnaroundTime; - } - - @StringGetter(CIRCUIT_MAX_WAITING_TIME) - public double getCircuitMaxWaitingTime() { - return circuitMaxWaitingTime; - } - - @StringSetter(CIRCUIT_MAX_WAITING_TIME) - public void setCircuitMaxWaitingTime(double circuitMaxWaitingTime) { - this.circuitMaxWaitingTime = circuitMaxWaitingTime; - } - - @StringGetter(CIRCUIT_PLANNING_APPROACH) - public CircuitPlanningApproach getCircuitPlanningApproach() { - return circuitPlanningApproach; - } - - @StringSetter(CIRCUIT_PLANNING_APPROACH) - public void setCircuitPlanningApproach(CircuitPlanningApproach circuitPlanningApproach) { - this.circuitPlanningApproach = circuitPlanningApproach; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java deleted file mode 100644 index 49b45bc8eb3..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RollingStockRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; - -/** - * Rolling stock repository - *

- * Implement a repository to provide attributes (maximum velocity, passenger capacity, acceleration and deceleration) for the vehicle types. - * - * @author Merlin Unterfinger - */ -public interface RollingStockRepository { - VehicleTypeInfo getVehicleType(String vehicleTypeId); - - static void addRailsimAttributes(VehicleTypeInfo vehicleTypeInfo, double maxAcceleration, double maxDeceleration) { - var attributes = vehicleTypeInfo.getAttributes(); - attributes.put(RailsimUtils.VEHICLE_ATTRIBUTE_MAX_ACCELERATION, maxAcceleration); - attributes.put(RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION, maxDeceleration); - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java deleted file mode 100644 index cf49925a115..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteDirection.java +++ /dev/null @@ -1,30 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -/** - * Route direction - *

- * The two possible direction of the routes. - * - * @author Merlin Unterfinger - */ -public enum RouteDirection { - /** - * The route starts at the origin and ends at the destination. - */ - FORWARD("F"), - /** - * The route starts at the destination and ends at the origin. - */ - REVERSE("R"); - - private final String abbreviation; - - RouteDirection(String abbreviation) { - this.abbreviation = abbreviation; - } - - public String getAbbreviation() { - return abbreviation; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java deleted file mode 100644 index 991085de8ef..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteStopInfo.java +++ /dev/null @@ -1,92 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import org.matsim.api.core.v01.network.Link; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; - -import java.util.Objects; - -/** - * Route stop information - * - * @author Merlin Unterfinger - */ -public class RouteStopInfo { - - private final StopInfo stopInfo; - private final Link link; - private final TransitStopFacility transitStop; - private final double waitingTime; - - /** - * Create a RouteStopInfo - *

- * A route stop info is a stop on a previously defined StopInfo on a transit line. - * - * @param stopInfo the stop. - * @param waitingTime the time to waiting at stop, is 0. for pass. - */ - RouteStopInfo(StopInfo stopInfo, double waitingTime) { - this.stopInfo = stopInfo; - this.link = stopInfo.getStopLink(); - this.transitStop = stopInfo.getStop(); - this.waitingTime = waitingTime; - } - - /** - * @param link the link where the depot sits on. - * @param transitStopFacility the depot transit stop facility. - * @param waitingTime time to wait in depot. - */ - RouteStopInfo(Link link, TransitStopFacility transitStopFacility, double waitingTime) { - this.stopInfo = null; - this.link = link; - this.transitStop = transitStopFacility; - this.waitingTime = waitingTime; - } - - /** - * Checks if the route stop info is a stopping pass. - * - * @return true if stopping, else false (non-stopping pass). - */ - public boolean isStoppingPass() { - return waitingTime > 0.; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - var that = (RouteStopInfo) o; - if (Double.compare(that.waitingTime, waitingTime) != 0) return false; - if (!Objects.equals(link, that.link)) return false; - return Objects.equals(transitStop, that.transitStop); - } - - @Override - public int hashCode() { - int result; - long temp; - result = link != null ? link.hashCode() : 0; - result = 31 * result + (transitStop != null ? transitStop.hashCode() : 0); - temp = Double.doubleToLongBits(waitingTime); - result = 31 * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - public Link getLink() { - return link; - } - - public TransitStopFacility getTransitStop() { - return transitStop; - } - - public double getWaitingTime() { - return waitingTime; - } - - public StopInfo getStopInfo() { - return stopInfo; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java deleted file mode 100644 index b17941991f4..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RouteType.java +++ /dev/null @@ -1,35 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -/** - * Route type - *

- * A transit line can have those route types in two directions (F: forward, R: Reverse). - * - * @author Merlin Unterfinger - */ -public enum RouteType { - /** - * New vehicle is created of vehicle type and route start is in the depot. - *

- * The departure time is decreased by the travel time from the depot to the station plus the waiting time of the origin station. - */ - DEPOT_TO_STATION, - /** - * Existing vehicle at the origin station is used. - *

- * The departure time and the route profile is not changed. - */ - STATION_TO_STATION, - /** - * Existing vehicle at the origin station is used, but the destination transit stop facility is the depot of the destination station. - *

- * The departure time is not changed, but the route profile is extended into the depot. - */ - STATION_TO_DEPOT, - /** - * New vehicle is created of vehicle type and route start and end is in the depot. - *

- * The departure time is decreased as in DEPOT_TO_STATION, and the route is extended from the depot at the origin to the depot at the destination. - */ - DEPOT_TO_DEPOT -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java deleted file mode 100644 index ebe2cb7e262..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RunRailsimSupplyBuilder.java +++ /dev/null @@ -1,154 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import org.matsim.api.core.v01.network.NetworkWriter; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.pt.transitSchedule.api.TransitScheduleWriter; -import org.matsim.vehicles.MatsimVehicleWriter; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; - -/** - * Example run of RailsimSupplyBuilder - *

- * Create a small example with different lines: slow, express, internation and cargo - * - * @author Merlin Unterfinger - */ -public final class RunRailsimSupplyBuilder { - - public static void main(String[] args) { - // Note! Overwrites test04 example - final String outputDir = "contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test04/"; - final double waitingTime = 3 * 60.; - createOutputDirectory(outputDir); - // configure - var config = ConfigUtils.createConfig(); - config.global().setCoordinateSystem("CH1903plus_LV95"); - var railsimConfigGroup = ConfigUtils.addOrGetModule(config, RailsimSupplyConfigGroup.class); - railsimConfigGroup.setCircuitPlanningApproach(RailsimSupplyConfigGroup.CircuitPlanningApproach.DEFAULT); - var scenario = ScenarioUtils.loadScenario(config); - // setup supply builder - var supply = new RailsimSupplyBuilder(scenario); - - // first add the stop information - supply.addStop("lyon_part_dieu", 2399392., 1070947.); - supply.addStop("geneve_la_praille", 2498624., 1115476.); - supply.addStop("geneve", 2499965., 1119074.); - supply.addStop("versoix", 2501905., 1126194.); - supply.addStop("coppet", 2503642., 1130305.); - supply.addStop("nyon", 2507480., 1137714.); - supply.addStop("gland", 2510108., 1141628.); - supply.addStop("rolle", 2515307., 1146292.); - supply.addStop("allaman", 2520200., 1147698.); - supply.addStop("morges", 2527534., 1151505.); - supply.addStop("renens", 2534852., 1154073.); - supply.addStop("lausanne", 2538101., 1151907.); - supply.addStop("puidoux", 2548503., 1148595); - supply.addStop("vevey", 2554287., 1145945.); - supply.addStop("fribourg", 2581093., 1182309); - supply.addStop("bern", 2598563, 1200033.); - supply.addStop("bern_express", 2599563, 1200333.); // some minor shifts of the coordinates - supply.addStop("bern_cargo", 2599563, 1199833.); // some minor shifts of the coordinates - - // add transit lines - // slow line - var slowLine = supply.addTransitLine("slow", "slow", "geneve", waitingTime); - slowLine.addStop("versoix", 5 * 60., waitingTime); - slowLine.addStop("coppet", 4 * 60., waitingTime); - slowLine.addStop("nyon", 5 * 60., waitingTime); - slowLine.addStop("gland", 3 * 60., waitingTime); - slowLine.addStop("rolle", 4 * 60., waitingTime); - slowLine.addStop("allaman", 3 * 60., waitingTime); - slowLine.addStop("morges", 5 * 60., waitingTime); - slowLine.addStop("renens", 6 * 60., waitingTime); - slowLine.addStop("lausanne", 6 * 60., waitingTime); - slowLine.addStop("puidoux", 10 * 60., waitingTime); - slowLine.addStop("vevey", 5 * 60., waitingTime); - // express line - var expressLine = supply.addTransitLine("express", "express", "geneve", waitingTime); - expressLine.addPass("versoix"); - expressLine.addPass("coppet"); - expressLine.addPass("nyon"); - expressLine.addPass("gland"); - expressLine.addPass("rolle"); - expressLine.addPass("allaman"); - expressLine.addPass("morges"); - expressLine.addPass("renens"); - expressLine.addStop("lausanne", 45 * 60., waitingTime); - expressLine.addStop("fribourg", 41 * 60., waitingTime); - expressLine.addPass("bern"); - expressLine.addStop("bern_express", 24 * 60., waitingTime); - // international line - var internationalLine = supply.addTransitLine("international", "international", "lyon_part_dieu", waitingTime); - internationalLine.addStop("geneve", 128 * 60., waitingTime); - internationalLine.addPass("versoix"); - internationalLine.addPass("coppet"); - internationalLine.addPass("nyon"); - internationalLine.addPass("gland"); - internationalLine.addPass("rolle"); - internationalLine.addPass("allaman"); - internationalLine.addPass("morges"); - internationalLine.addPass("renens"); - internationalLine.addStop("lausanne", 45 * 60., waitingTime); - internationalLine.addStop("fribourg", 41 * 60., waitingTime); - internationalLine.addPass("bern"); - internationalLine.addStop("bern_express", 24 * 60., waitingTime); - // cargo line - var cargoLine = supply.addTransitLine("cargo", "cargo", "geneve_la_praille", waitingTime); - cargoLine.addPass("geneve"); - cargoLine.addPass("versoix"); - cargoLine.addPass("coppet"); - cargoLine.addPass("nyon"); - cargoLine.addPass("gland"); - cargoLine.addPass("rolle"); - cargoLine.addPass("allaman"); - cargoLine.addPass("morges"); - cargoLine.addPass("renens"); - cargoLine.addPass("lausanne"); - cargoLine.addPass("fribourg"); - cargoLine.addPass("bern"); - cargoLine.addStop("bern_cargo", 193 * 60., waitingTime); - - // add departures: slow line - addDepartureTimes(slowLine, 0. * 3600., 3600., 900.); - // add departures: express line - addDepartureTimes(expressLine, 0.5 * 3600., 7200., 3600.); - // add departures: international line - addDepartureTimes(internationalLine, 0.25 * 3600., 3600 * 3., 7200.); - // add departures: cargo line - addDepartureTimes(cargoLine, 0.75 * 3600., 3600 * 3., 7200.); - - // build schedule - supply.build(); - - // export - new NetworkWriter(scenario.getNetwork()).write(outputDir + "trainNetwork.xml.gz"); - new TransitScheduleWriter(scenario.getTransitSchedule()).writeFile(outputDir + "transitSchedule.xml.gz"); - new MatsimVehicleWriter(scenario.getTransitVehicles()).writeFile(outputDir + "transitVehicles.xml.gz"); - } - - private static void addDepartureTimes(TransitLineInfo slowLine, double initialDeparture, double nvzHeadway, double hvzHeadway) { - for (double timeOfDay = initialDeparture; timeOfDay <= 24 * 3600.; ) { - slowLine.addDeparture(RouteDirection.FORWARD, timeOfDay); - slowLine.addDeparture(RouteDirection.REVERSE, timeOfDay); - if (timeOfDay < 6 * 3600. || timeOfDay > 19 * 3600.) { - timeOfDay = timeOfDay + nvzHeadway; - } else { - timeOfDay = timeOfDay + hvzHeadway; - } - } - } - - private static void createOutputDirectory(String outputDir) { - try { - Files.createDirectories(Paths.get(outputDir)); - } catch (IOException e) { - e.printStackTrace(); - } - } -} - - diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java deleted file mode 100644 index ae0c3b18c49..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionPartInfo.java +++ /dev/null @@ -1,57 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Section part information - *

- * A section part is a route between two stops, which is can be shared between multiple transit lines. Which means the rail capacity is also shared. - * - * @author Merlin Unterfinger - */ -public class SectionPartInfo { - private final String fromStopId; - private final String toStopId; - private final List sectionSegmentInfos = new ArrayList<>(); - - public SectionPartInfo(String fromStopId, String toStopId) { - this.fromStopId = fromStopId; - this.toStopId = toStopId; - } - - public void addSegment(SectionSegmentInfo sectionSegmentInfo) { - sectionSegmentInfos.add(sectionSegmentInfo); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - var that = (SectionPartInfo) o; - if (!Objects.equals(fromStopId, that.fromStopId)) return false; - if (!Objects.equals(toStopId, that.toStopId)) return false; - return sectionSegmentInfos.equals(that.sectionSegmentInfos); - } - - @Override - public int hashCode() { - int result = fromStopId != null ? fromStopId.hashCode() : 0; - result = 31 * result + (toStopId != null ? toStopId.hashCode() : 0); - return result; - } - - public String getFromStopId() { - return fromStopId; - } - - public String getToStopId() { - return toStopId; - } - - public List getSectionSegmentInfos() { - return sectionSegmentInfos; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java deleted file mode 100644 index 2d50f281377..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SectionSegmentInfo.java +++ /dev/null @@ -1,69 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import org.matsim.api.core.v01.Coord; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * Section segment information - *

- * A section segment is a segment of the rail network with constant attributes (number of rails, speed limit, grade). - * - * @author Merlin Unterfinger - */ -public class SectionSegmentInfo { - private final Coord fromCoord; - private final Coord toCoord; - private final double length; - private final Map linkAttributes = new HashMap<>(); - - public SectionSegmentInfo(Coord fromCoord, Coord toCoord, double length) { - this.fromCoord = fromCoord; - this.toCoord = toCoord; - this.length = length; - } - - public void addLinkAttribute(String key, Object value) { - linkAttributes.put(key, value); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - var that = (SectionSegmentInfo) o; - if (Double.compare(that.length, length) != 0) return false; - if (!Objects.equals(fromCoord, that.fromCoord)) return false; - if (!Objects.equals(toCoord, that.toCoord)) return false; - return linkAttributes.equals(that.linkAttributes); - } - - @Override - public int hashCode() { - int result; - long temp; - result = fromCoord != null ? fromCoord.hashCode() : 0; - result = 31 * result + (toCoord != null ? toCoord.hashCode() : 0); - temp = Double.doubleToLongBits(length); - result = 31 * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - public Coord getFromCoord() { - return fromCoord; - } - - public Coord getToCoord() { - return toCoord; - } - - public double getLength() { - return length; - } - - public Map getLinkAttributes() { - return linkAttributes; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java deleted file mode 100644 index 4483f9746e4..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/StopInfo.java +++ /dev/null @@ -1,102 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import org.matsim.api.core.v01.Coord; -import org.matsim.api.core.v01.network.Link; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * Stop information - *

- * Can also be passes for some transit lines. - * - * @author Merlin Unterfinger - */ -public class StopInfo { - - private final String id; - private final Coord coord; - private final double stopLinkLength; - private final Map linkAttributes = new HashMap<>(); - private TransitStopFacility stop; - private Link stopLink; - private DepotInfo depotInfo; - - /** - * @param id the unique id or name of the stop. - * @param coord the coordinates of the stop, - * @param stopLinkLength the maximum length of the stop link in the station. - */ - public StopInfo(String id, Coord coord, double stopLinkLength) { - this.id = id; - this.coord = coord; - this.stopLinkLength = stopLinkLength; - this.depotInfo = null; - } - - /** - * Checks if the stopInfo has no associated depot. - * - * @return true if no depot is connected, false otherwise. - */ - boolean hasNoDepot() { - return depotInfo == null; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - var stopInfo = (StopInfo) o; - // only compare id since uniqueness in managed by builder - return Objects.equals(id, stopInfo.id); - } - - @Override - public int hashCode() { - return id != null ? id.hashCode() : 0; - } - - public String getId() { - return id; - } - - public Coord getCoord() { - return coord; - } - - public double getStopLinkLength() { - return stopLinkLength; - } - - public Map getLinkAttributes() { - return linkAttributes; - } - - public TransitStopFacility getStop() { - return stop; - } - - public void setStop(TransitStopFacility stop) { - this.stop = stop; - } - - public Link getStopLink() { - return stopLink; - } - - public void setStopLink(Link stopLink) { - this.stopLink = stopLink; - } - - public DepotInfo getDepotInfo() { - return depotInfo; - } - - public void setDepotInfo(DepotInfo depotInfo) { - this.depotInfo = depotInfo; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java deleted file mode 100644 index b95d21ab973..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/SupplyFactory.java +++ /dev/null @@ -1,224 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Coord; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -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.core.population.routes.RouteUtils; -import org.matsim.pt.transitSchedule.api.Departure; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitRouteStop; -import org.matsim.pt.transitSchedule.api.TransitSchedule; -import org.matsim.pt.transitSchedule.api.TransitScheduleFactory; -import org.matsim.pt.transitSchedule.api.TransitStopFacility; -import org.matsim.utils.objectattributes.attributable.Attributable; -import org.matsim.vehicles.Vehicle; -import org.matsim.vehicles.VehicleType; -import org.matsim.vehicles.VehicleUtils; -import org.matsim.vehicles.Vehicles; -import org.matsim.vehicles.VehiclesFactory; - -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -/** - * Supply factory - *

- * Create MATSim instances and add them to a transit schedule and network. - * - * @author Merlin Unterfinger - */ -class SupplyFactory { - private static final Logger log = LogManager.getLogger(SupplyFactory.class); - private static final double DEFAULT_LINK_FREESPEED = 999.; - private static final double DEFAULT_LINK_CAPACITY = 999.; - private static final double DEFAULT_LINK_LANES = 1.; - private static final String DEFAULT_LINK_MODE = "rail"; - private static final double DEFAULT_VEHICLE_ACCESS_TIME = 1.; - private static final double DEFAULT_VEHICLE_EGRESS_TIME = 1.; - private static final String ID_FORMAT_DEPARTURE = "%s"; - private static final String ID_FORMAT_TRANSIT_LINE = "%s"; - private static final String ID_FORMAT_TRANSIT_ROUTE = "%s"; - private static final String ID_FORMAT_TRANSIT_STOP = "%s"; - private static final String ID_FORMAT_VEHICLE = "%s"; - private static final String ID_FORMAT_VEHICLE_TYPE = "%s"; - private static final String ID_FORMAT_LINK = "%s_%s-%s"; - private static final String ID_FORMAT_NODE = "%s"; - private final TransitSchedule schedule; - private final Vehicles vehicles; - private final Network network; - private final TransitScheduleFactory sf; - private final VehiclesFactory vf; - private final NetworkFactory nf; - - - SupplyFactory(Scenario scenario) { - schedule = scenario.getTransitSchedule(); - vehicles = scenario.getTransitVehicles(); - network = scenario.getNetwork(); - sf = schedule.getFactory(); - vf = vehicles.getFactory(); - nf = network.getFactory(); - } - - Departure createDeparture(String id, double time) { - var departureId = Id.create(String.format(ID_FORMAT_DEPARTURE, id), Departure.class); - log.debug("Creating Departure {}", departureId); - return sf.createDeparture(departureId, time); - } - - TransitLine createTransitLine(String id) { - var lineId = Id.create(String.format(ID_FORMAT_TRANSIT_LINE, id), TransitLine.class); - var transitLine = schedule.getTransitLines().get(lineId); - if (transitLine != null) { - log.warn("TransitLine {} is already existing", lineId); - return transitLine; - } - log.debug("Creating TransitLine {}", lineId); - transitLine = sf.createTransitLine(lineId); - schedule.addTransitLine(transitLine); - return transitLine; - } - - TransitRoute createTransitRoute(TransitLine transitLine, String id, List> routeLinks, List stops) { - var routeId = Id.create(String.format(ID_FORMAT_TRANSIT_ROUTE, id), TransitRoute.class); - var networkRoute = RouteUtils.createLinkNetworkRouteImpl(routeLinks.get(0), routeLinks.subList(1, routeLinks.size() - 1), routeLinks.get(routeLinks.size() - 1)); - var transitRoute = transitLine.getRoutes().get(routeId); - if (transitRoute != null) { - log.warn("TransitRoute {} is already existing on TransitLine {}", routeId, transitLine.getId()); - return transitRoute; - } - log.debug("Creating TransitRoute {} and adding to TransitLine {}", routeId, transitLine.getId()); - transitRoute = sf.createTransitRoute(routeId, networkRoute, stops, DEFAULT_LINK_MODE); - transitLine.addRoute(transitRoute); - return transitRoute; - } - - TransitRouteStop createTransitRouteStop(TransitStopFacility transitStopFacility, double cumulativeTravelTime, double departureTime, boolean awaitDeparture) { - log.debug("Creating TransitRouteStop at TransitStopFacility {}", transitStopFacility.getId()); - var transitRouteStop = sf.createTransitRouteStop(transitStopFacility, cumulativeTravelTime, departureTime); - transitRouteStop.setAwaitDepartureTime(awaitDeparture); - return transitRouteStop; - } - - TransitRouteStop createTransitTerminalStop(TransitStopFacility transitStopFacility, double cumulativeTravelTime, boolean origin) { - log.debug("Creating terminal TransitRouteStop at TransitStopFacility {} (using builder)", transitStopFacility.getId()); - if (origin) { - return sf.createTransitRouteStopBuilder(transitStopFacility).departureOffset(cumulativeTravelTime).build(); - } else { - return sf.createTransitRouteStopBuilder(transitStopFacility).arrivalOffset(cumulativeTravelTime).build(); - } - } - - TransitStopFacility createTransitStopFacility(String id, Link link) { - var stopId = Id.create(String.format(ID_FORMAT_TRANSIT_STOP, id), TransitStopFacility.class); - var transitStopFacility = schedule.getFacilities().get(stopId); - if (transitStopFacility != null) { - log.warn("TransitStopFacility {} is already existing", stopId); - return transitStopFacility; - } - log.debug("Creating TransitStopFacility {}", stopId); - transitStopFacility = sf.createTransitStopFacility(stopId, link.getToNode().getCoord(), false); - transitStopFacility.setLinkId(link.getId()); - schedule.addStopFacility(transitStopFacility); - return transitStopFacility; - } - - Node createNode(String id, Coord coord) { - var nodeId = Id.create(String.format(ID_FORMAT_NODE, id), Node.class); - var node = network.getNodes().get(nodeId); - if (node != null) { - log.warn("Node {} is already existing", nodeId); - return node; - } - log.debug("Creating Node {}", nodeId); - node = nf.createNode(nodeId, coord); - network.addNode(node); - return node; - } - - Link createLink(LinkType linkType, Node fromNode, Node toNode, double length, Map attributes) { - var linkId = Id.create(String.format(ID_FORMAT_LINK, linkType.getAbbreviation(), fromNode.getId().toString(), toNode.getId().toString()), Link.class); - var link = network.getLinks().get(linkId); - if (link != null) { - log.warn("Link {} is already existing", linkId); - return link; - } - log.debug("Creating Link {}", linkId); - link = nf.createLink(linkId, fromNode, toNode); - link.setAllowedModes(new HashSet<>(List.of(DEFAULT_LINK_MODE))); - link.setLength(length); - link.setFreespeed(DEFAULT_LINK_FREESPEED); - link.setCapacity(DEFAULT_LINK_CAPACITY); - link.setNumberOfLanes(DEFAULT_LINK_LANES); - putAttributes(link, attributes); - network.addLink(link); - return link; - } - - /** - * Creates a new vehicle type or returns if already existing. - * - * @param id vehicle type id. - * @param length the length of the complete vehicle in meters. - * @param maxVelocity maximum velocity the vehicle type is allowed to drive (m/s). - * @param capacity the passenger capacity of the vehicle type. - * @param attributes a map holding further attributes (acceleration, deceleration) - * @return the corresponding vehicle type. - */ - VehicleType getOrCreateVehicleType(String id, double length, double maxVelocity, int capacity, Map attributes) { - Id vehicleTypeId = Id.create(String.format(ID_FORMAT_VEHICLE_TYPE, id), VehicleType.class); - VehicleType vehicleType = vehicles.getVehicleTypes().get(vehicleTypeId); - if (vehicleType != null) { - log.debug("VehicleType {} is already existing", vehicleTypeId); - return vehicleType; - } - log.debug("Creating VehicleType {}", vehicleTypeId); - vehicleType = vf.createVehicleType(vehicleTypeId); - vehicleType.getCapacity().setSeats(capacity); - vehicleType.setMaximumVelocity(maxVelocity); - VehicleUtils.setDoorOperationMode(vehicleType, VehicleType.DoorOperationMode.parallel); - VehicleUtils.setAccessTime(vehicleType, DEFAULT_VEHICLE_ACCESS_TIME); - VehicleUtils.setEgressTime(vehicleType, DEFAULT_VEHICLE_EGRESS_TIME); - vehicleType.setLength(length); - putAttributes(vehicleType, attributes); - vehicles.addVehicleType(vehicleType); - return vehicleType; - } - - /** - * Creates a new vehicle for a given vehicle type - *

- * The vehicle id is set to the vehicle type name and the count of the total amount of existing vehicles of this type. - *

- * Note: The vehicle type has to be created beforehand using getOrCreateVehicleType. - * - * @param vehicleType The type of the vehicle to create. - * @return The created vehicle. - */ - Vehicle getOrCreateVehicle(VehicleType vehicleType, String id) { - Id vehicleId = Id.create(String.format(ID_FORMAT_VEHICLE, id), Vehicle.class); - Vehicle vehicle = vehicles.getVehicles().get(vehicleId); - if (vehicle != null) { - log.debug("Vehicle {} is already existing", vehicleId); - return vehicle; - } - log.debug("Creating Vehicle {}", vehicleId); - vehicle = vf.createVehicle(vehicleId, vehicleType); - vehicles.addVehicle(vehicle); - return vehicle; - } - - private static void putAttributes(Attributable object, Map attributes) { - for (var entry : attributes.entrySet()) { - object.getAttributes().putAttribute(entry.getKey(), entry.getValue()); - } - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java deleted file mode 100644 index c1a3770b8e9..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/TransitLineInfo.java +++ /dev/null @@ -1,231 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.network.Link; -import org.matsim.core.utils.misc.Time; -import org.matsim.pt.transitSchedule.api.TransitLine; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Transit line information. - *

- * Create an instance of this class to add transit lines and routes in both directions to the transit schedule using addTransitLineWithVehicleCircuits. - * - * @author Merlin Unterfinger - */ -public class TransitLineInfo { - - private static final Logger log = LogManager.getLogger(TransitLineInfo.class); - private static final double NO_STOP_TIME = 0.; - private final RailsimSupplyBuilder supplyBuilder; - // line - private final String id; - private final VehicleTypeInfo vehicleTypeInfo; - private final TransitLine transitLine; - // route profile - private final EnumMap> routeStopInfos = new EnumMap<>(RouteDirection.class); - private final EnumMap>> routeLinks = new EnumMap<>(RouteDirection.class); - private final EnumMap> travelTimes = new EnumMap<>(RouteDirection.class); - // route departure - private final EnumMap> departures = new EnumMap<>(RouteDirection.class); - private boolean built; - - /** - * Constructs a new transit line object and makes shallow copies of the input lists to prevent from outside manipulations. Reverses the route stop infos and travel times. - * - * @param id unique name or id of the transit line. - * @param firstRouteStop unique name or id of the first stop in the route. - * @param vehicleTypeInfo the vehicle type information. - * @param supplyBuilder the supply builder the transit line resides on. - */ - TransitLineInfo(String id, RouteStopInfo firstRouteStop, VehicleTypeInfo vehicleTypeInfo, TransitLine transitLine, RailsimSupplyBuilder supplyBuilder) { - this.id = id; - this.supplyBuilder = supplyBuilder; - this.vehicleTypeInfo = vehicleTypeInfo; - this.transitLine = transitLine; - routeStopInfos.put(RouteDirection.FORWARD, new ArrayList<>()); - travelTimes.put(RouteDirection.FORWARD, new ArrayList<>()); - departures.put(RouteDirection.FORWARD, new ArrayList<>()); - departures.put(RouteDirection.REVERSE, new ArrayList<>()); - built = false; - addFirstStop(firstRouteStop); - } - - public void addStop(String stopId, double travelTime, double waitingTime) { - if (built) { - throw new RuntimeException(String.format("Cannot add stop %s to already built TransitLineInfo %s", stopId, id)); - } - StopInfo stopInfo = supplyBuilder.getStop(stopId); - this.routeStopInfos.get(RouteDirection.FORWARD).add(new RouteStopInfo(stopInfo, waitingTime)); - this.travelTimes.get(RouteDirection.FORWARD).add(travelTime); - } - - public void addPass(String stopId) { - if (built) { - throw new RuntimeException(String.format("Cannot add pass %s to already built TransitLineInfo %s", stopId, id)); - } - StopInfo stopInfo = supplyBuilder.getStop(stopId); - this.routeStopInfos.get(RouteDirection.FORWARD).add(new RouteStopInfo(stopInfo, NO_STOP_TIME)); - } - - public void addDeparture(RouteDirection routeDirection, double departureTime) { - if (built) { - throw new RuntimeException(String.format("Cannot add departure %s to already built TransitLineInfo %s", Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), id)); - } - this.departures.get(routeDirection).add(departureTime); - } - - /** - * Build the transit line - *

- * After calling this method no further stops, passes or departures can be added to the transit line. - */ - void build() { - if (departures.get(RouteDirection.FORWARD).isEmpty() && departures.get(RouteDirection.REVERSE).isEmpty()) - throw new RuntimeException("Transit line needs at least one departure"); - if (routeStopInfos.get(RouteDirection.FORWARD).size() < 2) - throw new RuntimeException("Transit line needs at least one additional stop to origin stop"); - log.info("Building TransitLineInfo {}", id); - sortDepartures(); - initializeReverseDirection(); - createLinksAndConnectStops(); - logEntry(); - built = true; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - var that = (TransitLineInfo) o; - // only compare id and supply builder instance since uniqueness in managed by builder - if (!Objects.equals(supplyBuilder, that.supplyBuilder)) return false; - return Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - int result = supplyBuilder != null ? supplyBuilder.hashCode() : 0; - result = 31 * result + (id != null ? id.hashCode() : 0); - return result; - } - - private void addFirstStop(RouteStopInfo firstRouteStop) { - routeStopInfos.get(RouteDirection.FORWARD).add(firstRouteStop); - } - - private void sortDepartures() { - Collections.sort(departures.get(RouteDirection.FORWARD)); - Collections.sort(departures.get(RouteDirection.REVERSE)); - } - - private void initializeReverseDirection() { - this.routeStopInfos.put(RouteDirection.REVERSE, new ArrayList<>(this.routeStopInfos.get(RouteDirection.FORWARD))); - this.travelTimes.put(RouteDirection.REVERSE, new ArrayList<>(this.travelTimes.get(RouteDirection.FORWARD))); - Collections.reverse(this.routeStopInfos.get(RouteDirection.REVERSE)); - Collections.reverse(this.travelTimes.get(RouteDirection.REVERSE)); - } - - /** - * Create transit line links in both directions - */ - private void createLinksAndConnectStops() { - final var routeLinks = new ArrayList>(); - final var routeLinksReverse = new ArrayList>(); - final var routeStopInfos = getRouteStopInfos(RouteDirection.FORWARD); - // F: iterate over route stop infos and create links - StopInfo nextStop = null; - for (int i = 0; i < routeStopInfos.size() - 1; i++) { - var currentStop = routeStopInfos.get(i).getStopInfo(); - nextStop = routeStopInfos.get(i + 1).getStopInfo(); - // add terminal link - routeLinks.add(currentStop.getStopLink().getId()); - // add links to next stop - routeLinks.addAll(supplyBuilder.connectStops(currentStop, nextStop)); - } - assert nextStop != null; - routeLinks.add(nextStop.getStopLink().getId()); - // R: iterate over route stop infos and create links - nextStop = null; - for (int i = routeStopInfos.size() - 1; i > 0; i--) { - var currentStop = routeStopInfos.get(i).getStopInfo(); - nextStop = routeStopInfos.get(i - 1).getStopInfo(); - // add terminal link - routeLinksReverse.add(currentStop.getStopLink().getId()); - // add links to next stop - routeLinksReverse.addAll(supplyBuilder.connectStops(currentStop, nextStop)); - } - assert nextStop != null; - routeLinksReverse.add(nextStop.getStopLink().getId()); - // set route links on transit line info - setRouteLinks(RouteDirection.FORWARD, routeLinks); - setRouteLinks(RouteDirection.REVERSE, routeLinksReverse); - // remove route stop infos with a waiting time of 0 minutes, since they are no stops, but needed for route link creation - setRouteStopInfos(RouteDirection.FORWARD, getRouteStopInfos(RouteDirection.FORWARD).stream().filter(RouteStopInfo::isStoppingPass).collect(Collectors.toList())); - setRouteStopInfos(RouteDirection.REVERSE, getRouteStopInfos(RouteDirection.REVERSE).stream().filter(RouteStopInfo::isStoppingPass).collect(Collectors.toList())); - } - - public RouteStopInfo getOrigin(RouteDirection routeDirection) { - return routeStopInfos.get(routeDirection).get(0); - } - - public RouteStopInfo getDestination(RouteDirection routeDirection) { - var directedRouteStopInfos = routeStopInfos.get(routeDirection); - return directedRouteStopInfos.get(directedRouteStopInfos.size() - 1); - } - - public List getRouteStopInfos(RouteDirection routeDirection) { - return routeStopInfos.get(routeDirection); - } - - void setRouteStopInfos(RouteDirection routeDirection, List routeStopInfos) { - this.routeStopInfos.put(routeDirection, routeStopInfos); - } - - List> getRouteLinks(RouteDirection routeDirection) { - return routeLinks.get(routeDirection); - } - - void setRouteLinks(RouteDirection routeDirection, List> routeLinks) { - this.routeLinks.put(routeDirection, routeLinks); - } - - public List getTravelTimes(RouteDirection routeDirection) { - return travelTimes.get(routeDirection); - } - - public List getDepartures(RouteDirection routeDirection) { - return departures.get(routeDirection); - } - - private void logEntry() { - var sb = new StringBuilder("Transit line log entry of ").append(id).append("\n"); - // departures - sb.append("Departures:\n").append("- FORWARD: ").append(this.departures.get(RouteDirection.FORWARD).stream().map(d -> Time.writeTime(d, Time.TIMEFORMAT_HHMMSS)).collect(Collectors.joining(", "))).append("\n- REVERSE: ").append(this.departures.get(RouteDirection.REVERSE).stream().map(d -> Time.writeTime(d, Time.TIMEFORMAT_HHMMSS)).collect(Collectors.joining(", "))); - // route stops - sb.append("\nRoute stops and passes:\n").append(this.routeStopInfos.get(RouteDirection.FORWARD).stream().map(r -> String.format("%s (%s)", r.getStopInfo().getId(), Time.writeTime(r.getWaitingTime(), Time.TIMEFORMAT_HHMMSS))).collect(Collectors.joining(", "))); - // route stops - sb.append("\nTravel times:\n").append(this.travelTimes.get(RouteDirection.FORWARD).stream().map(d -> Time.writeTime(d, Time.TIMEFORMAT_HHMMSS)).collect(Collectors.joining(", "))); - log.info(sb); - } - - public String getId() { - return id; - } - - TransitLine getTransitLine() { - return transitLine; - } - - public VehicleTypeInfo getVehicleTypeInfo() { - return vehicleTypeInfo; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java deleted file mode 100644 index 87e0afdbadb..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleAllocationInfo.java +++ /dev/null @@ -1,18 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import java.util.LinkedList; - -/** - * Vehicle allocation information - *

- * Denotes which vehicles are used by a departure of a transit line. - * - * @author Merlin Unterfinger - */ -public interface VehicleAllocationInfo { - - LinkedList getDepartures(RouteType routeType, RouteDirection routeDirection); - - LinkedList getVehicleIds(RouteType routeType, RouteDirection routeDirection); - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java deleted file mode 100644 index f7f27ebabc8..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleCircuitsPlanner.java +++ /dev/null @@ -1,15 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import java.util.List; -import java.util.Map; - -/** - * Vehicle circuits planner - *

- * Takes a list of (built) transit line information objects, plan circuits and allocates a vehicle to each departure. - * - * @author Merlin Unterfinger - */ -public interface VehicleCircuitsPlanner { - Map plan(List transitLineInfos); -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java deleted file mode 100644 index fdb41504cb7..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/VehicleTypeInfo.java +++ /dev/null @@ -1,80 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * Vehicle type information - *

- * Use this class in combination with TransitLineInfo to provide a vehicle type for a transit line. - * - * @author Merlin Unterfinger - */ -public class VehicleTypeInfo { - - private final String id; - private final int capacity; - private final double length; - private final double maxVelocity; - private final double turnaroundTime; - - private final Map attributes = new HashMap<>(); - - /** - * @param id the name of the vehicle type. - * @param capacity the passenger capacity of the vehicle. - * @param length the vehicle length. - * @param maxVelocity the maximum velocity of the vehicle. - * @param turnaroundTime the time needed to change direction of travel in a station (change drivers cab). - */ - public VehicleTypeInfo(String id, int capacity, double length, double maxVelocity, double turnaroundTime) { - this.id = id; - this.capacity = capacity; - this.length = length; - this.maxVelocity = maxVelocity; - this.turnaroundTime = turnaroundTime; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - var that = (VehicleTypeInfo) o; - if (capacity != that.capacity) return false; - if (Double.compare(that.length, length) != 0) return false; - if (Double.compare(that.maxVelocity, maxVelocity) != 0) return false; - if (Double.compare(that.turnaroundTime, turnaroundTime) != 0) return false; - if (!Objects.equals(id, that.id)) return false; - return attributes.equals(that.attributes); - } - - @Override - public int hashCode() { - return id != null ? id.hashCode() : 0; - } - - public String getId() { - return id; - } - - public int getCapacity() { - return capacity; - } - - public double getLength() { - return length; - } - - public double getMaxVelocity() { - return maxVelocity; - } - - public double getTurnaroundTime() { - return turnaroundTime; - } - - public Map getAttributes() { - return attributes; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java deleted file mode 100644 index 8c23520a613..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlanner.java +++ /dev/null @@ -1,256 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteStopInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; -import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleAllocationInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleCircuitsPlanner; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.Scenario; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.utils.misc.Time; - -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -/** - * Default implementation of the vehicle circuits planner - *

- * Uses the RouteType and RouteDirection enums to determine the type of route and the direction of travel. The RouteType enum defines the possible route types for a transit line in two directions. The - * RouteDirection enum defines the direction of the route, either starting from the origin or the destination (F: forward, R: Reverse). - * - * @author Merlin Unterfinger - */ -public class DefaultVehicleCircuitsPlanner implements VehicleCircuitsPlanner { - - private static final Logger log = LogManager.getLogger(DefaultVehicleCircuitsPlanner.class); - private final double depotTravelTime; - private final double circuitMaxWaitingTime; - private final HashMap allocations = new HashMap<>(); - private final LinkedList routeDepartureEventQueue = new LinkedList<>(); - private final VehicleFleet vehicleFleet = new VehicleFleet(); - private int fromDepotCount = 0; - private int toDepotCount = 0; - - /** - * @param scenario the scenario with the railsim supply config group. - */ - public DefaultVehicleCircuitsPlanner(Scenario scenario) { - var config = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); - this.depotTravelTime = config.getDepotTravelTime(); - this.circuitMaxWaitingTime = config.getCircuitMaxWaitingTime(); - } - - /** - * Plan the day with vehicle circuits - *

- *

    - *
  • Creates a TransitLineVehicleAllocation object per TransitLineInfo.
  • - *
  • Sorts the departures according to arrivals
  • - *
  • Loop through the day and creates vehicle circuits
  • - *
- */ - @Override - public Map plan(List transitLineInfos) { - // reset state - fromDepotCount = 0; - toDepotCount = 0; - routeDepartureEventQueue.clear(); - vehicleFleet.clear(); - allocations.clear(); - // create departure events, sorted according to arrival time - sortAllDepartures(transitLineInfos); - // process departure events - for (RouteDepartureEvent routeDepartureEvent : routeDepartureEventQueue) { - handleDepartureEvent(routeDepartureEvent); - } - // run consistency checks - log.info("Checking planned circuits for consistency..."); - if (Stream.of(checkDepotCounts(), checkEmptyStopQueues(), checkDepotVehicleCounts()).anyMatch(check -> !check)) { - throw new RuntimeException("Not all consistency checks passed! Aborting..."); - } - log.info("PASSED - vehicle circuits are consistent"); - // convert to interface type - return new HashMap<>(allocations); - } - - /** - * Handles a chronologically ordered route departure event. - * - * @param routeDepartureEvent the current departure event. - */ - private void handleDepartureEvent(RouteDepartureEvent routeDepartureEvent) { - log.info(String.format("Handling departure event: %s %s -> %s (departure: %s, arrival: %s, ready: %s)", routeDepartureEvent.getTransitLineInfo().getId(), routeDepartureEvent.getOrigin().getStopInfo().getId(), routeDepartureEvent.getDestination().getStopInfo().getId(), Time.writeTime(routeDepartureEvent.getDepartureTime(), Time.TIMEFORMAT_HHMMSS), Time.writeTime(routeDepartureEvent.getArrivalTime(), Time.TIMEFORMAT_HHMMSS), Time.writeTime(routeDepartureEvent.getReadyAgainTime(), Time.TIMEFORMAT_HHMMSS))); - // get next vehicle from stop or depot, needs: transitLineInfo, routeDirection, departureTime - final VehicleFleet.AvailableVehicle availableVehicle = vehicleFleet.getNextVehicleForDeparture(routeDepartureEvent.getTransitLineInfo(), routeDepartureEvent.getRouteDirection(), routeDepartureEvent.getDepartureTime()); - final boolean fromDepot = availableVehicle.fromDepot(); - final Vehicle vehicle = availableVehicle.vehicle(); - // increase counter for validation - if (fromDepot) { - fromDepotCount++; - } - // check if destination is depot and send vehicle to depot - RouteDepartureEvent oppositeDeparture = routeDepartureEvent.getNextPossibleOppositeDeparture(); - boolean toDepot = false; - if (oppositeDeparture == null) { - // calculate time it takes to drive to the depot and back: - // arrival time at stop + waiting time at stop + travel time to and from depot (2x) + waiting time at stop. - final double readyFromDepotTime = routeDepartureEvent.getArrivalTime() + 2 * routeDepartureEvent.getDestinationWaitingTime() + 2 * depotTravelTime; - toDepot = true; - // increase counter for validation - toDepotCount++; - vehicleFleet.sendToDepot(routeDepartureEvent.getTransitLineInfo(), routeDepartureEvent.getRouteDirection(), vehicle, readyFromDepotTime); - } else { - vehicleFleet.stayOnStopLink(routeDepartureEvent.getTransitLineInfo(), routeDepartureEvent.getRouteDirection(), vehicle, routeDepartureEvent.getReadyAgainTime()); - } - // determine route type - RouteType routeType = fromDepot ? - // from depot to ... - (toDepot ? RouteType.DEPOT_TO_DEPOT : RouteType.DEPOT_TO_STATION) : - // from station to ... - (toDepot ? RouteType.STATION_TO_DEPOT : RouteType.STATION_TO_STATION); - // allocate vehicle to departure - allocations.get(routeDepartureEvent.getTransitLineInfo()).addVehicleAllocation(routeDepartureEvent, routeType, vehicle); - } - - /** - * Sort route departures - *

- * Sorts route departures of all transit lines according to the arrival time of the route. Further it initializes a TransitLineVehicleAllocation for each TransitLineInfo. - */ - private void sortAllDepartures(List transitLineInfos) { - log.info("Sorting departures of individual transit lines..."); - for (TransitLineInfo transitLineInfo : transitLineInfos) { - // create allocation object for each transit line to store the allocations - allocations.put(transitLineInfo, new TransitLineVehicleAllocation()); - // retrieve total route time (symmetric in both directions) - final double totalRouteTime = calculateTotalRouteTime(transitLineInfo); - // create linked lists for reference to opposite departures - LinkedList plannedDeparturesOrig = new LinkedList<>(); - LinkedList plannedDeparturesDest = new LinkedList<>(); - // forward: starting from origin - double destinationWaitingTime = calculateDestinationWaitingTime(transitLineInfo, RouteDirection.FORWARD); - for (double departure : transitLineInfo.getDepartures(RouteDirection.FORWARD)) { - RouteDepartureEvent routeDepartureEvent = new RouteDepartureEvent(departure, totalRouteTime, destinationWaitingTime, circuitMaxWaitingTime, RouteDirection.FORWARD, transitLineInfo, plannedDeparturesDest); - plannedDeparturesOrig.add(routeDepartureEvent); - routeDepartureEventQueue.add(routeDepartureEvent); - } - // reverse: starting from destination - destinationWaitingTime = calculateDestinationWaitingTime(transitLineInfo, RouteDirection.REVERSE); - for (double departure : transitLineInfo.getDepartures(RouteDirection.REVERSE)) { - RouteDepartureEvent routeDepartureEvent = new RouteDepartureEvent(departure, totalRouteTime, destinationWaitingTime, circuitMaxWaitingTime, RouteDirection.REVERSE, transitLineInfo, plannedDeparturesOrig); - plannedDeparturesDest.add(routeDepartureEvent); - routeDepartureEventQueue.add(routeDepartureEvent); - } - } - log.info("Sorting planned departures for all transit lines..."); - Collections.sort(routeDepartureEventQueue); - } - - /** - * Check if outgoing and ingoing depot counts are equal. - * - * @return true if check passes, false otherwise. - */ - private boolean checkDepotCounts() { - final boolean passed = fromDepotCount == toDepotCount; - final String message = String.format("Outgoing and ingoing depot counts are %s (%d OUT %s %d IN)", passed ? "equal" : "not equal", fromDepotCount, passed ? "==" : "!=", toDepotCount); - if (!passed) { - log.warn(message); - return false; - } - log.info(message); - return true; - } - - /** - * Check if all vehicles queues on stop links are empty at the end of the day. - * - * @return true if check passes, false otherwise. - */ - private boolean checkEmptyStopQueues() { - final int totalVehicleQueueSize = vehicleFleet.getTotalQueueSize(); - final boolean passed = totalVehicleQueueSize == 0; - final String message = String.format("%s", passed ? "All vehicle queues on stop links are empty" : "Not all vehicle queues on stop links are empty"); - if (!passed) { - log.warn(message + " (vehicles left = " + totalVehicleQueueSize + ")"); - return false; - } - log.info(message); - return true; - } - - /** - * Check if all created vehicles are collected in a depot at the end of the day. - * - * @return true if check passes, false otherwise. - */ - private boolean checkDepotVehicleCounts() { - final Map vehiclesCreated = vehicleFleet.getTotalVehicleCounts(); - final Map vehiclesCollected = vehicleFleet.getTotalVehicleInDepotCounts(); - StringBuilder sb = new StringBuilder(); - sb.append("("); - boolean passed = true; - for (Map.Entry entry : vehiclesCreated.entrySet()) { - int created = entry.getValue(); - int collected = vehiclesCollected.get(entry.getKey()); - passed = passed && created == collected; - sb.append(entry.getKey().getId()); - sb.append(": "); - sb.append(created); - sb.append("+, "); - sb.append(collected); - sb.append("-; "); - } - sb.delete(sb.length() - 2, sb.length()); - sb.append(")"); - final String message = String.format("%s created vehicles are collected at the end of the day %s", passed ? "All" : "Not all", sb); - if (!passed) { - log.warn(message); - return false; - } - log.info(message); - return true; - } - - /** - * Total route time - *

- * Get total route travel and waiting time between route origin and destination without first and last waiting time at station. Note: The direction does not matter, since it is symmetric. But - * could be changed in the future. - * - * @param transitLineInfo the transit line information. - * @return the total route time. - */ - private static double calculateTotalRouteTime(TransitLineInfo transitLineInfo) { - final List routeStopInfos = transitLineInfo.getRouteStopInfos(RouteDirection.FORWARD); - final List travelTimes = transitLineInfo.getTravelTimes(RouteDirection.FORWARD); - // sum waiting times at intermediate stops - return routeStopInfos.stream().limit(routeStopInfos.size() - 1).skip(1).mapToDouble(RouteStopInfo::getWaitingTime).sum() - // sum travel times - + travelTimes.stream().mapToDouble(Double::doubleValue).sum(); - } - - /** - * Destination waiting time - *

- * Total time to wait at destination before next departure is possible. The calculation depends on the direction, since the waiting time at the last stop is taken. - * - * @param transitLineInfo the transit line information. - * @param routeDirection does the route start at the origin? - * @return the destination waiting time. - */ - private static double calculateDestinationWaitingTime(TransitLineInfo transitLineInfo, RouteDirection routeDirection) { - final double turnaroundTime = transitLineInfo.getVehicleTypeInfo().getTurnaroundTime(); - return Math.max(transitLineInfo.getDestination(routeDirection).getWaitingTime(), turnaroundTime); - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java deleted file mode 100644 index 6426358bb67..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlanner.java +++ /dev/null @@ -1,65 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; -import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleAllocationInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleCircuitsPlanner; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * Implementation for no circuits - *

- * Creates a new vehicle for each transit line vehicle allocation. Maintains a global counter of vehicles per type. - * - * @author Merlin Unterfinger - */ -public class NoVehicleCircuitsPlanner implements VehicleCircuitsPlanner { - - private static final HashMap counter = new HashMap<>(); - - private record DummyVehicleAllocationInfo(TransitLineInfo transitLineInfo) implements VehicleAllocationInfo { - - @Override - public LinkedList getDepartures(RouteType routeType, RouteDirection routeDirection) { - if (routeType == RouteType.STATION_TO_STATION) { - return new LinkedList<>(transitLineInfo.getDepartures(routeDirection)); - } - return null; - } - - @Override - public LinkedList getVehicleIds(RouteType routeType, RouteDirection routeDirection) { - if (routeType == RouteType.STATION_TO_STATION) { - final var vehicleTypeInfo = transitLineInfo.getVehicleTypeInfo(); - return IntStream.range(0, transitLineInfo.getDepartures(routeDirection).size()).mapToObj(i -> createVehicleId(vehicleTypeInfo)).collect(Collectors.toCollection(LinkedList::new)); - } - return null; - } - } - - @Override - public Map plan(List transitLineInfos) { - counter.clear(); - return transitLineInfos.stream().collect(Collectors.toMap(Function.identity(), DummyVehicleAllocationInfo::new)); - } - - private static String createVehicleId(VehicleTypeInfo vehicleTypeInfo) { - return String.format("%s_%s", vehicleTypeInfo.getId(), getAndIncreaseCount(vehicleTypeInfo)); - } - - private static int getAndIncreaseCount(VehicleTypeInfo vehicleTypeInfo) { - int count = counter.getOrDefault(vehicleTypeInfo, 0); - counter.put(vehicleTypeInfo, count + 1); - return count; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java deleted file mode 100644 index 2f8b6795c4f..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/RouteDepartureEvent.java +++ /dev/null @@ -1,140 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteStopInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.core.utils.misc.Time; - -import java.util.LinkedList; - -/** - * A route departure event is the first departure of a route at the origin station. - * - * @author Merlin Unterfinger - */ -class RouteDepartureEvent implements Comparable { - - private static final Logger log = LogManager.getLogger(RouteDepartureEvent.class); - private final double departureTime; - private final double totalRouteTime; - private final double destinationWaitingTime; - private final double circuitMaxWaitingTime; - private final RouteDirection routeDirection; - private final TransitLineInfo transitLineInfo; - private final LinkedList oppositeDepartures; - - /** - * This time is important for sorting, we want to process the departures according to the time when they are ready again for another departure. - */ - private final double readyAgainTime; - - /** - * @param departureTime the departure time. - * @param totalRouteTime the total time until the destination is reached (without waiting times at origin and destination). - * @param destinationWaitingTime the waiting time at the destination station. - * @param circuitMaxWaitingTime the maximum time to wait at the destination for a next circuit departure, otherwise the vehicle is sent to the depot. - * @param routeDirection the route direction. - * @param transitLineInfo the transit line information. - * @param oppositeDepartures the departures from the same transit line in the opposite direction. - */ - RouteDepartureEvent(double departureTime, double totalRouteTime, double destinationWaitingTime, double circuitMaxWaitingTime, RouteDirection routeDirection, TransitLineInfo transitLineInfo, LinkedList oppositeDepartures) { - this.departureTime = departureTime; - this.totalRouteTime = totalRouteTime; - this.destinationWaitingTime = destinationWaitingTime; - this.circuitMaxWaitingTime = circuitMaxWaitingTime; - this.routeDirection = routeDirection; - this.transitLineInfo = transitLineInfo; - this.oppositeDepartures = oppositeDepartures; - this.readyAgainTime = departureTime + totalRouteTime + destinationWaitingTime; - } - - /** - * Search and get the next possible departures from the opposite station. - * - * @return the next planned departure or null. - */ - public RouteDepartureEvent getNextPossibleOppositeDeparture() { - // delete all opposite departures which are departing before this current departure would get ready again in the destination stop queue. - // we can do this, since the denatures are sorted according to arrivalTime, therefore no sooner readyAgain vehicle in the stop queue is possible than the current one. - // e.g. ready again at 11:05 at destination, opposite departure at 11:00 could be deleted. - while (!oppositeDepartures.isEmpty() && oppositeDepartures.getFirst().getDepartureTime() < readyAgainTime) { - RouteDepartureEvent oppositeDeparture = oppositeDepartures.removeFirst(); - log.info(String.format("DELETING - %s", renderDepartureInfo(oppositeDeparture))); - } - // get the next departure that is departing after the current vehicle is ready again and check if it is in the possible time window for departure - if (!oppositeDepartures.isEmpty()) { - RouteDepartureEvent oppositeDeparture = oppositeDepartures.getFirst(); - log.info(String.format("CHECKING - %s", renderDepartureInfo(oppositeDeparture))); - if (oppositeDeparture.departureTime >= this.readyAgainTime && oppositeDeparture.departureTime <= this.readyAgainTime + circuitMaxWaitingTime) { - // match! Remove route departure event from queue and return - oppositeDeparture = oppositeDepartures.removeFirst(); - log.info(String.format("SELECTED - %s", renderDepartureInfo(oppositeDeparture))); - return oppositeDeparture; - } - } - return null; - } - - /** - * Sort according to ready again time (=arrival time + destinationWaitingTime). - * - * @param other the object to be compared. - * @return comparison. - */ - @Override - public int compareTo(RouteDepartureEvent other) { - return Double.compare(this.readyAgainTime, other.readyAgainTime); - } - - /** - * Render route departure event info to string. - * - * @param oppositeDeparture the next opposite departure. - * @return the departure event information as string. - */ - private String renderDepartureInfo(RouteDepartureEvent oppositeDeparture) { - return String.format("departure: %s, ready: %s, maxWaitingTime: %s, oppositeDeparture: %s", Time.writeTime(this.departureTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(this.readyAgainTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(this.readyAgainTime + circuitMaxWaitingTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(oppositeDeparture.departureTime, Time.TIMEFORMAT_HHMMSS)); - } - - public RouteStopInfo getOrigin() { - return transitLineInfo.getOrigin(routeDirection); - } - - public RouteStopInfo getDestination() { - return transitLineInfo.getDestination(routeDirection); - } - - public double getArrivalTime() { - return departureTime + totalRouteTime; - } - - public double getDepartureTime() { - return departureTime; - } - - public double getTotalRouteTime() { - return totalRouteTime; - } - - public double getDestinationWaitingTime() { - return destinationWaitingTime; - } - - public RouteDirection getRouteDirection() { - return routeDirection; - } - - public TransitLineInfo getTransitLineInfo() { - return transitLineInfo; - } - - public LinkedList getOppositeDepartures() { - return oppositeDepartures; - } - - public double getReadyAgainTime() { - return readyAgainTime; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java deleted file mode 100644 index c56adf5d796..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/TransitLineVehicleAllocation.java +++ /dev/null @@ -1,84 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleAllocationInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.core.utils.misc.Time; - -import java.util.EnumMap; -import java.util.LinkedList; - -/** - * Transit line vehicle allocation - *

- * Container class to store the route departures with allocated vehicles per line. - * - * @author Merlin Unterfinger - */ -class TransitLineVehicleAllocation implements VehicleAllocationInfo { - - private static final Logger log = LogManager.getLogger(TransitLineVehicleAllocation.class); - - private final EnumMap>> directedDepartures = new EnumMap<>(RouteDirection.class); - private final EnumMap>> directedVehicleIds = new EnumMap<>(RouteDirection.class); - - /** - * Ctor - */ - public TransitLineVehicleAllocation() { - directedDepartures.put(RouteDirection.FORWARD, new EnumMap<>(RouteType.class)); - directedDepartures.put(RouteDirection.REVERSE, new EnumMap<>(RouteType.class)); - directedVehicleIds.put(RouteDirection.FORWARD, new EnumMap<>(RouteType.class)); - directedVehicleIds.put(RouteDirection.REVERSE, new EnumMap<>(RouteType.class)); - } - - /** - * Adds a vehicle allocation for a route departure to the transit line. - * - * @param routeDepartureEvent the departure event. - * @param routeType the route type of the route. - * @param vehicle the vehicle to allocate. - */ - public void addVehicleAllocation(RouteDepartureEvent routeDepartureEvent, RouteType routeType, Vehicle vehicle) { - final RouteDirection routeDirection = routeDepartureEvent.getRouteDirection(); - final double departureTime = routeDepartureEvent.getDepartureTime(); - final String vehicleId = vehicle.id(); - log.info(String.format("Allocating vehicle %s to departure %s of line %s (%s)", vehicleId, Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), routeDepartureEvent.getTransitLineInfo().getId(), routeType.name())); - // add departure time - EnumMap> departureMap = directedDepartures.get(routeDirection); - LinkedList departuresList = departureMap.getOrDefault(routeType, new LinkedList<>()); - departuresList.add(departureTime); - departureMap.put(routeType, departuresList); - // add vehicle id - EnumMap> vehicleIdMap = directedVehicleIds.get(routeDirection); - LinkedList vehicleIds = vehicleIdMap.getOrDefault(routeType, new LinkedList<>()); - vehicleIds.add(vehicleId); - vehicleIdMap.put(routeType, vehicleIds); - } - - /** - * Retrieve the departure times for a route type and direction. - * - * @param routeType the type of the route. - * @param routeDirection the route direction. - * @return a list containing the departure times. - */ - @Override - public LinkedList getDepartures(RouteType routeType, RouteDirection routeDirection) { - return directedDepartures.get(routeDirection).get(routeType); - } - - /** - * Retrieve the vehicle ids for a route type and direction. - * - * @param routeType the type of the route. - * @param routeDirection the route direction. - * @return a list containing the vehicle ids. - */ - @Override - public LinkedList getVehicleIds(RouteType routeType, RouteDirection routeDirection) { - return directedVehicleIds.get(routeDirection).get(routeType); - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java deleted file mode 100644 index 928f68a4149..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/Vehicle.java +++ /dev/null @@ -1,14 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; - -/** - * A package internal vehicle class. - * - * @param id vehicle id. - * @param type the vehicle type information. - * @author Merlin Unterfinger - */ -record Vehicle(String id, VehicleTypeInfo type) { - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java deleted file mode 100644 index b3c53d3801e..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleDepot.java +++ /dev/null @@ -1,116 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.core.utils.misc.Time; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; - -/** - * A vehicle depot - *

- * Contains a queue for each vehicle type that enters the depot. Can create new vehicles if empty. - * - * @author Merlin Unterfinger - */ -class VehicleDepot { - - private static final Logger log = LogManager.getLogger(VehicleDepot.class); - private final HashMap> vehicles = new HashMap<>(); - private final VehicleFleet fleet; - private final String name; - - /** - * Ctor - * - * @param name the name of the depot. - * @param fleet the fleet, the depot corresponds to. - */ - VehicleDepot(String name, VehicleFleet fleet) { - this.name = name; - this.fleet = fleet; - } - - /** - * Gets or creates a new vehicle - *

- * If there is a vehicle of the corresponding type is ready in the queue, it is taken. Otherwise, a new vehicle of this type is generated and returned. - * - * @param vehicleTypeInfo the vehicle type information, used to determine the requested vehicle type. - * @return a new or existing vehicle. - */ - public Vehicle getVehicle(VehicleTypeInfo vehicleTypeInfo, double departureTime) { - // get vehicle queue of corresponding type, add back to hashmap (only relevant the first time) - LinkedList vehiclesOfType = vehicles.getOrDefault(vehicleTypeInfo, new LinkedList<>()); - vehicles.put(vehicleTypeInfo, vehiclesOfType); - // check if there are vehicles in the depot, if there are any, check if the first one is ready again before departure - if (!vehiclesOfType.isEmpty() && departureTime >= vehiclesOfType.getFirst().time()) { - VehicleReadyEvent vehicleReadyEvent = vehiclesOfType.removeFirst(); - Vehicle vehicle = vehicleReadyEvent.vehicle(); - log.info(String.format("Leaving depot link, remove available vehicle '%s' (departure: %s, ready: %s) from depot queue '%s'", vehicleReadyEvent.vehicle().id(), Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS), name)); - return vehicle; - } - // No vehicle was ready in the depot queue, create a new one - final String vehicleId = String.format("%s_%d", vehicleTypeInfo.getId(), fleet.increaseVehicleCount(vehicleTypeInfo)); - log.info(String.format("No vehicle ready (departure: %s) in depot, creating new vehicle '%s' one", Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), vehicleId)); - return new Vehicle(vehicleId, vehicleTypeInfo); - } - - /** - * Add a vehicle to the depot. - * - * @param vehicle the vehicle to add to the depot. - * @param readyFromDepotTime the time until a vehicle is ready again for a departure from depot: (arrival + waitingTime + 2 * depot travel time + waitingTime) - */ - public void addVehicle(Vehicle vehicle, double readyFromDepotTime) { - log.info(String.format("Entering depot link, add vehicle '%s' to depot queue '%s'", vehicle.id(), name)); - LinkedList vehiclesOfType = vehicles.getOrDefault(vehicle.type(), new LinkedList<>()); - vehiclesOfType.addLast(new VehicleReadyEvent(vehicle, readyFromDepotTime)); - vehicles.put(vehicle.type(), vehiclesOfType); - } - - /** - * Get the vehicle type specific count of all vehicles in the depot - * - * @return a hashmap containing the counts per vehicle type. - */ - public HashMap getVehicleCounts() { - HashMap vehicleCounter = new HashMap<>(); - for (Map.Entry> entry : vehicles.entrySet()) { - vehicleCounter.put(entry.getKey(), entry.getValue().size()); - } - return vehicleCounter; - } - - /** - * Create a log message with the inventory of the depot. - *

- * Useful for debugging. - */ - public void logInventory() { - StringBuilder sb = new StringBuilder("Depot inventory of "); - sb.append(name); - sb.append(":\n"); - for (Map.Entry> entry : vehicles.entrySet()) { - if (entry.getValue().isEmpty()) { - continue; - } - sb.append(" - "); - sb.append(entry.getKey().getId()); - sb.append(": "); - for (VehicleReadyEvent vehicleReadyEvent : entry.getValue()) { - sb.append(vehicleReadyEvent.vehicle().id()); - sb.append(" ("); - sb.append(Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS)); - sb.append("), "); - } - sb.delete(sb.length() - 2, sb.length()); - sb.append("\n"); - } - sb.delete(sb.length() - 1, sb.length()); - log.info(sb); - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java deleted file mode 100644 index 60bcfca027e..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleFleet.java +++ /dev/null @@ -1,190 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; -import ch.sbb.matsim.contrib.railsim.prototype.supply.StopInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.TransitLineInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; - -import java.util.HashMap; -import java.util.Map; - -/** - * Vehicle fleet - *

- * This class manages the depots and vehicle queues at stop links. Gets or sends vehicles to the queues. - * - * @author Merlin Unterfinger - */ -class VehicleFleet { - - private enum TargetQueue { - /** - * Get the queue at the origin of a route departure. - */ - ORIGIN, - /** - * Get the queue at the destination of a route departure. - */ - DESTINATION - } - - public record AvailableVehicle(Boolean fromDepot, Vehicle vehicle) { - - } - - private final Map stops = new HashMap<>(); - private final Map depots = new HashMap<>(); - private final Map vehicleCounts = new HashMap<>(); - - /** - * Default ctor - */ - VehicleFleet() { - } - - /** - * Request a vehicle for the next route departure from the origin of the transit line. - *

- * Note: The origin is switched depending on the requested departure. - * - * @param transitLineInfo the transit line information of the departure. - * @param routeDirection the direction of the route departure. - * @param departureTime the time of the departure. - * @return A pair holding the information if a vehicle came from the depot and the vehicle itself. - */ - public AvailableVehicle getNextVehicleForDeparture(TransitLineInfo transitLineInfo, RouteDirection routeDirection, double departureTime) { - final VehicleQueue vehicleQueue = getQueue(transitLineInfo, routeDirection, TargetQueue.ORIGIN); - // set initial value to a vehicle from the stop queue (STATION) - boolean fromDepot = false; - // log the current state of the stop queue for debugging - vehicleQueue.logState(); - // get existing vehicle from stop queue if there is one waiting - Vehicle vehicle = vehicleQueue.getVehicle(departureTime); - // get a vehicle from depot if no vehicle (null) is available in the stop queue and set origin to depot queue (DEPOT) - if (vehicle == null) { - VehicleDepot vehicleDepot = getDepot(transitLineInfo, routeDirection, TargetQueue.ORIGIN); - vehicleDepot.logInventory(); - fromDepot = true; - vehicle = vehicleDepot.getVehicle(transitLineInfo.getVehicleTypeInfo(), departureTime); - } - // return the vehicle and a boolean indicating if the vehicle is from the depot or not - return new AvailableVehicle(fromDepot, vehicle); - } - - /** - * Send a vehicle to the depot. - * - * @param transitLineInfo the transit line information. - * @param routeDirection the direction of the route. - * @param vehicle the vehicle driving on the current route. - * @param readyFromDepotTime the time until a vehicle is ready again for a departure from depot: (arrival + waitingTime + 2 * depot travel time + waitingTime). - */ - public void sendToDepot(TransitLineInfo transitLineInfo, RouteDirection routeDirection, Vehicle vehicle, double readyFromDepotTime) { - // set target queue to destination, since we want to send the vehicle to the destination depot - VehicleDepot vehicleDepot = getDepot(transitLineInfo, routeDirection, TargetQueue.DESTINATION); - vehicleDepot.addVehicle(vehicle, readyFromDepotTime); - } - - /** - * Let vehicle wait on the current stop link for the next departure. - * - * @param transitLineInfo the transit line information. - * @param routeDirection the direction of the route. - * @param vehicle the vehicle driving on the current route. - * @param readyAgainTime the time until a vehicle is ready again for a departure from the stop link: arrival + max(waitingTime, turnaroundTime). - */ - public void stayOnStopLink(TransitLineInfo transitLineInfo, RouteDirection routeDirection, Vehicle vehicle, double readyAgainTime) { - // set target queue to destination, since we want to queue the vehicle at the destination - VehicleQueue vehicleQueue = getQueue(transitLineInfo, routeDirection, TargetQueue.DESTINATION); - vehicleQueue.addVehicle(vehicle, readyAgainTime); - } - - /** - * Get and increase vehicle type specific count - *

- * This method is used to generate globally unique vehicle ids. - * - * @param vehicleTypeInfo the vehicle type of the vehicle. - * @return the not yet increased count. - */ - public int increaseVehicleCount(VehicleTypeInfo vehicleTypeInfo) { - int vehicleTypeCount = vehicleCounts.getOrDefault(vehicleTypeInfo, 0); - int tmp = vehicleTypeCount; - vehicleCounts.put(vehicleTypeInfo, ++vehicleTypeCount); - return tmp; - } - - /** - * Get the number of vehicles in all stop link queues. - * - * @return the summed size of all vehicle queues. - */ - public int getTotalQueueSize() { - if (stops.isEmpty()) { - return 0; - } - return stops.values().stream().mapToInt(VehicleQueue::size).sum(); - } - - /** - * A counter of all created vehicles - * - * @return a hashmap containing the counts for all created vehicles. - */ - public Map getTotalVehicleCounts() { - return vehicleCounts; - } - - /** - * A counter of all vehicles currently located in a depot - * - * @return a hashmap containing the counts for all vehicles in depots. - */ - public HashMap getTotalVehicleInDepotCounts() { - HashMap globalVehicleCounter = new HashMap<>(); - for (VehicleDepot depot : depots.values()) { - for (Map.Entry entry : depot.getVehicleCounts().entrySet()) { - int count = globalVehicleCounter.getOrDefault(entry.getKey(), 0); - count += entry.getValue(); - globalVehicleCounter.put(entry.getKey(), count); - } - } - return globalVehicleCounter; - } - - /** - * Reset the vehicle fleet - */ - public void clear() { - this.stops.clear(); - this.depots.clear(); - this.vehicleCounts.clear(); - } - - private VehicleDepot getDepot(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { - final StopInfo stopInfo = getTargetStopInfo(transitLineInfo, routeDirection, targetQueue); - final VehicleDepot vehicleDepot = depots.getOrDefault(stopInfo, new VehicleDepot(stopInfo.getId(), this)); - depots.put(stopInfo, vehicleDepot); - return vehicleDepot; - } - - private VehicleQueue getQueue(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { - final String key = constructQueueKey(transitLineInfo, routeDirection, targetQueue); - final VehicleQueue vehicleQueue = stops.getOrDefault(key, new VehicleQueue(key)); - stops.put(key, vehicleQueue); - return vehicleQueue; - } - - private static String constructQueueKey(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { - // key: transitLineName_vehicleTypeName_stopInfoName - return String.format("%s_%s_%s", transitLineInfo.getId(), transitLineInfo.getVehicleTypeInfo().getId(), getTargetStopInfo(transitLineInfo, routeDirection, targetQueue).getId()); - } - - private static StopInfo getTargetStopInfo(TransitLineInfo transitLineInfo, RouteDirection routeDirection, TargetQueue targetQueue) { - return switch (targetQueue) { - case ORIGIN -> transitLineInfo.getOrigin(routeDirection).getStopInfo(); - case DESTINATION -> transitLineInfo.getDestination(routeDirection).getStopInfo(); - }; - } - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java deleted file mode 100644 index 8024c109007..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleQueue.java +++ /dev/null @@ -1,93 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.core.utils.misc.Time; - -import java.util.LinkedList; - -/** - * A vehicle queue (FIFO) - *

- * Stores the time when vehicles, which are waiting on a stop link, a ready for the next departure. - * - * @author Merlin Unterfinger - */ -class VehicleQueue { - - private static final Logger log = LogManager.getLogger(VehicleQueue.class); - private final LinkedList vehicleReadyEvents = new LinkedList<>(); - private final String name; - - /** - * Vehicle FIFO queue - * - * @param name the name of the vehicle queue. - */ - public VehicleQueue(String name) { - this.name = name; - } - - /** - * Add a vehicle to the end of the queue. - * - * @param vehicle the vehicle to add to the queue. - * @param readyAgainTime the time when a departure is possible again with this vehicle. - */ - public void addVehicle(Vehicle vehicle, double readyAgainTime) { - log.info(String.format("Staying on stop link, appending vehicle '%s' to queue '%s'", vehicle.id(), name)); - vehicleReadyEvents.addLast(new VehicleReadyEvent(vehicle, readyAgainTime)); - } - - /** - * Get a vehicle from the start of the queue, if the departure time is after the ready time. - * - * @param departureTime the departure time. - * @return the vehicle or null if no vehicle is available for departure. - */ - public Vehicle getVehicle(double departureTime) { - // check if vehicles are waiting in the queue - if (vehicleReadyEvents.isEmpty()) { - return null; - } - VehicleReadyEvent vehicleReadyEvent = vehicleReadyEvents.getFirst(); - // check if departure is before ready time of vehicle - if (departureTime < vehicleReadyEvent.time()) { - log.warn(String.format("Departure time (%s) is before ready time (%s) of vehicle (%s) at stop queue '%s'! Skipping...", Time.writeTime(departureTime, Time.TIMEFORMAT_HHMMSS), Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS), vehicleReadyEvent.vehicle().id(), this.name)); - return null; - } - // there is a vehicle, where the departure is inside the time window of availability, remove it from the stop queue and return - log.info(String.format("Leaving stop link, remove vehicle '%s' from queue '%s'", vehicleReadyEvent.vehicle().id(), name)); - return vehicleReadyEvents.removeFirst().vehicle(); - } - - /** - * Get the size of the queue. - * - * @return the size. - */ - public int size() { - return vehicleReadyEvents.size(); - } - - /** - * Create a log message with the state of the queue - */ - public void logState() { - StringBuilder sb = new StringBuilder("Queue state of "); - sb.append(name); - sb.append(": "); - if (vehicleReadyEvents.isEmpty()) { - sb.append("Empty"); - } else { - for (VehicleReadyEvent vehicleReadyEvent : vehicleReadyEvents) { - sb.append(vehicleReadyEvent.vehicle().id()); - sb.append(" ("); - sb.append(Time.writeTime(vehicleReadyEvent.time(), Time.TIMEFORMAT_HHMMSS)); - sb.append(") "); - } - sb.delete(sb.length() - 1, sb.length()); - } - log.info(sb); - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java deleted file mode 100644 index a490d273d4c..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/VehicleReadyEvent.java +++ /dev/null @@ -1,14 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -/** - * Vehicle ready event - *

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 RrfNpm`0>3wD>p^H{|9O_)Z_pF diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitSchedule.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitSchedule.xml.gz deleted file mode 100644 index 5989dd29a2da50987ae36f330d55f9c789b06a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70575 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 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/transitVehicles.xml.gz deleted file mode 100644 index 25ad72a9773194a1f7bddab3c9f2e74ec47735fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1366 zcmb2|=3sz;w{vp+Z+nQe+8WtG7dpIZW5%5%)s>QGW>C=PbCIYUO zdzF_P_0{bETqO9|`u3}+*ELD2T<6)W`}C|%vA4j;q}!+E zgYR%^3!OZ(%WL|5rOdchTs&6(aaFH9XQl1eO$mE}_=I=@xmwKLV{l0C@VFdwh!Iq@unhnJPBXC++B+j#S0X3oFwq)0W%p<5J&!SUPB1 zd-6uU$Eh{ii?`SmS~|>4+U@Rqd7JL<2VWO(U)r<7Z&{|;^s5zfPJUke>VZ>k`>e@l z>O^n8c%NFKenq7FP6SI)y;7%6Smw9u%hnvbciq`o;O5~8nN!xeUCxwf;rjIZZJqSx zPnXYSUbuO9fsmNITUU=*HwdPzNi+7oZGCiqkIVP{CtS{N|I-})|9R7*z5ZJQwglKX z8b;}x&CICZm~nE2Yuoe4d;d=V=RBB^FR3e`E76}2Q@<}aUsP96SCHLh%lD3H9sdml zRe8_c7h~@Bbn`Hz-=r&gA!%e-32ajz?NRBvg<@Ui14-z5@40J3O?1eKW#AJB5`Px{0 z7RK;!>$S0pA8zVSJjj}B2xA`I@E|GTz?%*2i3bmFc#s(J!9b@%Ld+&$DTL(s}CTxASvq6n{7Vk`(d0<>A`t0U} z@Wgq%l`9|KoM7`Y$74Z-nM$CJv`@~9b-d@<4l~`|`tW8##Dd*sHqt!YCD+^1n-d$~ z-TIJYpp)>*w>-DPOhOE-a*H{Xfusd!%tFG(l+A#ccOh1K9&a1#uI$5`8AG{Hj5%`+ zN%G)^22Wl!lRIbL*gjnP{>B@7rZC65*~{|Eigr)DmARx$dgWfXto;*zt(my%zDMo3 zu=nk;v!{hO*Inq(1S!f5(O$hZaP69FZd*4+Nnbscg(?v0^!HDuMr~ZYjdAGJfgz?wodiST^b$z@@m+xty9ytPW^l9RPnS=Q>A~u^E|oO%lFSJt=IEI sg!gv=nOa(=A6Kuqa%BCYAXI_;coEQo;t0MYE9%K!iX diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml deleted file mode 100644 index 4fbe6d6c452..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/trainNetwork.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - 1 - - - - - - 1 - - - - - - 1 - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml deleted file mode 100644 index 09d5c02e458..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitSchedule.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml deleted file mode 100644 index a5f1849ff76..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test5/transitVehicles.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml deleted file mode 100644 index 72cf067be18..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/trainNetwork.xml +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - 1 - - - - - - 5 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 5 - - - - - - 5 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 5 - - - - - - 5 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 5 - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml deleted file mode 100644 index 0d6c5472cca..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitSchedule.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml deleted file mode 100644 index 05c114f2309..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test6/transitVehicles.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml deleted file mode 100644 index bd8d5c71223..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/trainNetwork.xml +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 5 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 5 - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml deleted file mode 100644 index 33a88a1cc16..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitSchedule.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml deleted file mode 100644 index c900e79b69c..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test7/transitVehicles.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - 5.0 - - - - - - - - - - - - - - - - - - 5.0 - serial - 5.0 - 5.0 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml deleted file mode 100644 index a5c7658af5c..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/trainNetwork.xml +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml deleted file mode 100644 index 9cc7ea08496..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitSchedule.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml deleted file mode 100644 index 2642a38d9e0..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test8/transitVehicles.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - 2.0 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml deleted file mode 100644 index df9461ed7f6..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/trainNetwork.xml +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml deleted file mode 100644 index 1feafed0f84..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitSchedule.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml deleted file mode 100644 index 2642a38d9e0..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test9/transitVehicles.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - 2.0 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml deleted file mode 100644 index f45e23b0f3b..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/config.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitNetwork.xml.gz deleted file mode 100644 index 199db02001776e43822aca5df1a186f84e459bda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97116 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�=h^wQ;ka84!G$4%MBl4Q0x zGsI0Yug+ESeeZa}+MGSM<=dyfrC!&L%Wb6E zhjYX)qvqr>Nfgvop(C!~vmH!L)VPY4a6DgaP99UsGD&-}f@)4Kom?8QB?jlxi!Z{D z2!Xt)f##lm-A`@}e5*>q5-5)AC&XGvR8yVcU8s2nf$=J3sv8eRoHag9?V|0n}L<+s6yq#`|WdktI}7*|zpjd)-bkL1NC#k{2OHXK+YgYA-C zKjs-xU=L*#(Xa3|B?Yi70@8t6SGBcXNZ>bPk4?FY`?WH`Jo61#{vLQ&p~DbQSDUfN zq#UjlzYr-1Qn|%4cLrV}UGmv@y4s9AM*1w(AfxGAonHzmRK2U9?ZqR*$YWE@uh7V* zhjfFLz(`FMiN}k($jD&A)EDWWk;mjFZv(>+4LN^c&h|uw zv`MJKxJDk6+k(cVwoB?Qls87w3E9UT*F|VX9;0#FS=C23xh>vA~@l*JOPt4NLOi>%VPV~8pxWQO;qcx?_Wk%4tpYyCwQY}7GCP4IHA z_+*=CDFoK69e(-a;v<+=J&xrp#4aS^j_0i4&6R<36&~X+lFI6VtTK#=0zBWANw!=S zYF#BKQ+|p}&SdTvA96M8dciA}bXWeu1f1i_Z>bhjMLjpexXl9@_9T-@ z34!kOYMQywjj2E4Mmo(ZaOwbb={exXyglz$kx3~BUg#(`D+X}R#3isw{jzTiy*(HK zB4(Cyi4VD&B`0z*2tvkmZt2SB7ncj8N^K|My}|>mM9eK?x^zp zVqB&A1}{Efi(`sPb^(dEVX&O*Q%m|WUSxj8A7i%1rkMmn>3$!}eF3{)QrP@r)wdaa zjIm_Lf}|$qQPZB6`+`%WVoVZ0CXGM#A?i+Ce_S+LU>Dn|$egfA5{I=YmQOxo6#}Jn z8!VRvo>i@{@gnUr@|gS`^F}lh*Yv&pXXCbD$x1$p7l*HL#{^eOZ#mQJ9LY_J1a~&I ztkNSR{*@Vb4Cd>&)E=5g_uq}{0?VqL(+jhkX5=wS%7$4;W2t(3TyUUmt1 za(uSch6nO)Y<|fT&paKM%VMaL;+tNK(Z{qL#MMI`4R5dzM&xAQ%IA#ZS|lrx6c%aa zsXk+zY7?;(x zYA~vdU-L#En@~L&0rXX8F})kSg__z{kqEsw)Qmp%BYZNlPu2RLi>?uZvGSd~a3pL- zA7lAFUx}-d2A@ALr#mHi885oa#vYrP>SF9cy!G4Ta<=!}HQ-)+!p0uM^^{%eh48fB z$vtsHp>$D8T+N~xc?{kST{!4}+qg~gxDpw-R!M=pU*wa<9{Vse3lrE>rsHyBw4HEc z8!yty>5(k=?}a3oyoELW1MYjNZxx$nai&n49?DY5$Sd zqg;(~Ln)q`uqvshRoqnZbJD0|ANpjcVNHX_4<7CdJgahP^+iHy)Uk>1_C*y7lf!7B zjQ`mhlnYe;=ocxaQOB6ZSDZZxv2pw1INI^7Dw+<&fh~}`eE1j6q>_=f>4ChZJfPn< z;fTC=VhiQYQNTo!h%+Ln1h9O=SV75mmR_6@#vMbN366~%J9*j24Agt_^&q@U?i6 zR~mVYLaSVok8nJCsa&`<=#3)f^dhY^^4Mha4u6jXPw$2+v4OT5lHV^ri0-iDAsyb$ zw84PnY!VslsJvJlXvP~;aRIoK2!NFvD2(3uF=B`_{F~9nrrhPQbfr#8wRQ*QTqiw4 zo$*ETw>^#}pfLBq7o*t?hovYf0Fo0az33Ng4`lt_+tzrqfLjP>s}~gU7Vj6B#=cYl z2(PduxUBe=Fw4lz!=_hWN)XgpV{+D9kgX&Pk`X}3bZGjK4z?Nh_UOq^hrP6XN5^9r zu)K2#wo&-M<`$o8b)$_!0x#>=dCbwX9qk@|B_w=RX{@(2mq6{2F%_h*qdh-<+s)nG?@oGdgq4_<~Bqtdc`u~RiXcBhoD$YyWJQE!h+CzZ7@ zlI*zQ@bKW3dQF~7?sgbP_PB$-&Pba&e#F`Q!^3w|ymGhqY|LWBOPeZ9NL-irvZ3`4 z8+=z%addC{4OSuoTZf9+M>v43d#K*AFto8r^lhraO^MFLQd{3J>~z?LoOyv#J#Vl( ztIR}eBW{vsokOTE8A+l6%fnGQ#?hFNnlrB5W{pGorwNjhMwydfaAa4B38E~HJ3V?K zRWUecZN$DWz7SQNZmH{JDhO-gD>=a;<(d?H{t`G8Qpx0|HBQ;FwqZk^_<|rRFEYE1 z6o$1bFd@ZSiW3N|ZHO*VL*$4$r5M|ljyG!v1O22KA8A_GQ0>dF%GfyVT0MNZA`qCs z!XrLlv9=);;shgQ?p1E50wiu_K}cc}K5V8(@1m?sXcS=f?yyM8vntTx*o1LCd>74l zNeD>9{b^UPsx8rwTZn&S9=;nBU6PuVUC|p>oGMFb|1=J3@vB0iGs}%O+q%KdrNR=O zF+=>*wE)J&@?xRoPoN<=hp>dkSv@ zm0&9;{%$RL1-G*Gi5YFZJuVkSg-;8SCmavfDQxmf=S#<|^5fhd7p6I)5{m&y{8#Ia zi-j7Fj%&CUGSk8V#U@w%<-)iYzX%=Rsh89%z+vZ7O^Ka83IIV^i{Cb->Vu2TNXB8i z8V)Z>`t}Q_z2)({r0RnSDKWJimZN;`E@#CHCDP^LyYX^j{5jXYp^l+)62w_NzuOkR z61Q+V5Wd^D@Eubv6nYvj3ZCuZtD5DsTBSPkhvj;hoH(%7!Y8A32OC>Fs2cSxS);YD zShdM2B{AqOguh#B4zkN6Zvj`#PwV{)%l&{vPGQw4{uV5J5#m77Xh_aqT?iUpt13xM zvYYS+X1&3th5lMeJ097?mm2~a8#*-cw_wrR>(~ey4TkJSYK8EgRY!s@a`>>Z?qDMz zF8s=;q(=UuK|)X!iH5{~9M__^cLd8NAK$8)=D0|=(UXT@7Jm&EzMryuk|mCn4VI%E z35kweP&j_AHOQ%2P#)4iyTNWw;;Dfa=mq`9|M)jxe}Pt6vc};p>3)oTx+SrFl@X=b z{U6_FU({@<@=h-rt_{Xg7dxD#m>dMiG{Mi`vb1~b|=0;j?c)!ZWn zn%xy4(6#cq9l4yCyxLX*x6TjNc8iA;VQguye&Zc5dxl>{J_VgG3~R0DAxF%`urp(5 z?;qGipKD`nR9m3Y_QSQV4Ytu&X1jXtTRB;l7Cds?bd*@bxK>XNR;(xb4l~^zSLZyE zC!`wj|NEg@mWvsKl%pS!>U4Wrnl5O2NkteB2)lg+4|ncDm<_*uH=^ZhX_)=vEB%XF zJb2T%xDuzT7J2mIcPxJaDST&udhvs`=Ek zY_v=FhR0^ta*HS=E*JrBjm`7~}V#WR`jW+MER5kK}-;SR zR#KQ-kGOv1kI}|i5o?_vtOYP;UQ*zdPpI8+;c_r$TyqBU=P#~(W0HR9s;lH2rQQz5 zQfRtP_!beb?jNj&FgaHn3nselHOl*}`BITAMv3vK^$5mvfJJS_(tbLs9x+zWIUr9*W;*>fn-?pBq z0VHWc6Q{KRspki4@he3&{h*J@_3apw&)}NKsFjFs=MUE6x9Q>*T2e~9a@e&m70FC3 zuuT1fwfJSH4!;+TGe5*uMU$g{!YmwT_9aYsZX4qxR1;n11+T$)7gu zkFxx(w|B4@@eIb|QmMvaEqt{xnAqGJ&1ar8c} zXgP&(Er8`7DDty!%!Rx|+Bk|St{JMw?{(XpP~HcQzNmNI)bOSBM_dYqMZ`1j2W#;w zd6RM;B%kwzLnsH5HTA9pssNYr2W#a6(JNQSov}%KDDzdNov$NBZ`_oSPNhp%#zH}7vFCC!^BQ&?NrC z+LtidL?$bhNb_q)ZUt3@Z%QW(qk1JJ7omLjS=%6cq<6BmU?>!6grbT7nPehsx@(8 zc&g7UF4Ogf%MrntK2xiNajlCxH4Wvc?H>W3FD?sPYqL2sM}XD$!?iZ%j$0geGu;_m*Nz<)PPycD zOsM1GYyH*+U+tRsRZF#440fR=m2qzv5mCz@ti|slQKF-mvr8^EI!o8W3X2|hILxfy z+SZ3l&AnZG?N4x}#KzVSid;N=t>4-th?3+O8PoWh9asKGC05un2rOIta4me-`uMUX zOibDhm(EIt+mv=ZezV7~B(axVF*?^;4ogU=@Dn4BFs#L|)QMcPO#>|(EZ!r7t|t5A z;g7*eosDjQx@jeBbuEA;Ifr((%5DKV`480sm{9-%vY8=l^M^%OY_cfaBJG+Vti`Vc z6y9_r%Rlc6Y4Yzjopa=tdyB`fbzGaA2fD~rkcmw<+zy|7>1iGhVC%R_Uwb@Dk;B>3nwrv!tra-V#$G#+z;2|mporiMh!G89~vtV7vl#Bv=R?s>nUuqBluu) z8H{!dU_z~qzTl(%<2mXN_7}mOT{9Rp@=z|M(O3pW>pron8joOWxb~#Bl~O(667Dx; zLC_Hr9YO+e*kZ7#@+OG$QkvWn2018{xx(Bwz)k+)R>Or?B^UUWMewUdY&U-`_BU^Y)z8vG74Rh{j(peMX;Rgq@PO#(Cw;3PZ5$Pve+vgz}9l@EZXaS z6#f&QF^q}OKLWk-nZ*OxTCSbXUk68U5$g@NBxW1(jg*K;zz^3V7#2OM(UQ__H^jj+ z430#ig^>g7?;oy(aAwAX<1F$9jtXx|4&T&1PFg`!i(tH8u2dvDg}dRfXw}erO1;Jd z*m|yA{`OMr;!QK#Eg&#ERX3wCA|#$KthHR(>iB0egcL z;7dXnBZd_ZU)#V_Q}T-;S*1VWu=O@QH+rQ{&_@p^mk=T|&@Z3&O6SMCRd8zhgposC(Yb|_#|gjfH=wfM!% z>>|c?ORfeWXq=hQ;!&=Q<5~b$xg6AR7wt>W7UUvATN*^b)rWbV-YYCL*v2TwUX7k-uWnT=~Bxbk1iK_SQR zh)Cmpacz1gkA+A9me<8+^;wBQjl5 zo1N{co32?0QuT&PZ-M8J52UOkws5hi_`|Nd(+!3Nx>!WUX-e>3y(NSus+c*}kz=va&R=wQf5+fblk%TwDdkAf!*7X(qoEGRxRg z<$x{I7t}o%ySk1`vLP9*vF)Vp>k@5+wqwAH{0r)KaGzFn*0}NwXS4}$fO->G*8M_q z33JPw8j`a1*GHZSl?f+6|SaL`Lm?jDQIA7u9_OSe@b1ly~U+4hfFCd{gWN z&Tzb1+e=RBDz`vk&$L;&h#Tj?k4W-a$i7ij*Ho5TnQq+1Xx&?&iACRnr|^y#Wqwgz z-<9NA>7S^(`HLNN4hqerIDx>eiZg9*d8>QVZNH&*XtPKom4Hym7u9v|I>h-*)ZZit z5{9RUQ><0PN4DEHGAzFsv=UmD2S8Gx?fxdRtkt!&iM|mH+rCeg7nW+c0;hvJw&yyOjhf|B2nH9QSX<}kIwxFhEZ4#SE&ft@_^#w-2zqx7!GVnT2{n$;0xr`nWiWE5!|R|l3Z$}=%sb4>acNlg zFRn$48)Sd21@8RD;J(IxteZ%UWUU*88b*LORHaKxcD!47V&jT=5?&c^8$!#Kw&X-$ zlWc!ND~js!U954-&(HYYmu!({ydX5!5%HY(V&3#=kn|pM-BTFZlcFf1jY@Rdqi@xv zrRa&FLs1C=f2puab7GP(XEOXsw^;jGLYyE$h&PcbfTa) zVH!zX*V?Q~n6Ha`qJty}Wz~q-@E6o|EvTpP!j(J4+X7N1t&3NU+(AGj;fw0BG^`hA zT6YqiPCGyi?xO#nxopC)u1nJ}TS0+wvx5n8(iv1yFjr6~UFnZ{6E zkp>3OKr+Y64EhfEYZa-AKa#C6P?w`&vx46QE4k{ZQr1p6(6<~20pYqYs*BNVbcK-z z%=^a0+A!qhQ)V)r!vX7BQWVB$cEf^Q`v!{%QNc|sPA7FKDM{IpxZ0|QTyCf-sa239 z-x0~}FRW`>u+Ils1KkSiVr>Ri8(JaCc;S!0r7S8>)zp~697Y=CHq?XUMx%<4qPmnt z-K?P%jV+rSY!$p`>1)o2Z^;MMg`@^|bVnhl4KIv!2!`MQ@IVj0wB+yMM|Bw~B>Qx& z6EO>)MeHV3s9XeW_^oirx{MT!7Szu$balZ=fmP8er2F;`XIicz#f0-(xtHh37- zV^$sq|EUe7)>#WQTkyJv$zB732wzy&ut+GyWVK`P{=kPxx3)6gTA2Lo0#fvYFT~{< zHpC8_vD0fT{Ei-uS{IOFQRQMti6DX}1$Yo8RljEcGm7d07D~@o>)mm?5wcvA%+@5j zW;kkHK5Ax01&L*qwr)|2Y{BF(+u?lM)uSjpA?Ihg{;J=I=#cWRm98HBwp=_4(#D0G zP@5kC%pYv0eLN6jjo3JSVO@L%y^(~(KrQK(6(ln8G`T5rIBH!x3c)axMKU??Z4W>R zbQrwJ?G+ANmyVKyi+&K=Q=V(E>Wx|->N}D3urH`9&q!U33lJhP-FE=wN21jyC0%a; z`K&Lh2Q5@3M)i0m`U(JomQ5MeBO>7WMYWh6tPoIgAY*jfgCMg}F1g%Z;h1&NDBOdj zI$$yA<5BvBm6*CH73D{q;h=TZDBPOVahCAvH&`qPuMH}7PBhPp0#hhOb$fu^o5jdMZq(NL)P`8 z7#7LhH{>o!_4Za->A?a~rNqOq7P9o1uL>DIm%MJU=tZeSXsh8>1(%CrOY5TmF`F-k z$>*Q~x$<$O-oioaa#08tApy%G-|b-~9lz>IBu5darZ1`m?I7qUgY-Sk?+1o6x_za> zgbD|(YemVIE?FRL5l%isg(ilLBBR4_%(_yPlr557GmVxE$g=`T-#E}w3f^(ngDXYN zjK8uRStu}hR-o%K^gA;)TL;+x`S1TBfM2wNh~#zC!lkcs?Gtf?e_o#J2LOxqzXkA5 z|NO82%HJ$G63qBq^M+x3BZ(|+1Y#U*N!X3PvGC#grpq05J@tfy@vxsgXJ*U36Fy~ z@HF6s(Vt`!X?U|KQMp#EB=k3p!!CoJl2+l?o*1|Kj6iM@1TML$>zf~L8!+R$f%wK~ zxA!mxbIC-&9hDs(xb^{0MvxNCY;fNwGf4kP7Ke#-{OdK+h&*f}xtt81ce>B@SYpoV zT7D9vd;(h_!(YCT>xLq5A8t*Ua257iJMC>gf$cpohoR7Dh&JD5hGdvZGAr#QAM(Vt zIF?8)7wN_PLv=|_eeBT?9H)}|iED8z&r?u5Ub=kmuxPGfKu?+(hqXB7ra@yajqWru zRaK>%(OFbGq?u1xk75?^=~~G7;_pDtuxqGv<~lNSp0F0fi0LvtZn{5m8^~o{IE3_c zS{ixcS`e%6pQIWrXu7UnqD9}(B-!UG=80=D+*CBRY+^0lX6RCZTyixLZKp=b1J}MW zJayDTabh^&wE-(YFm2O5j-{Tk$sboTIH^_A*VE!RL82OE^H4I_Q+e>jwIGI^dFjSe ztA6Nw23~Lz#yS!|p12mn*pggw)V6}f^D#YGs`GT2EjLV>ZxLaWB{5?$=G7&?P@hOWwnq zpUwgqZpEdY@72bw&9iG(`E)49rw08K*h85Tze0PcWjXFzOFm)wL$jni@oUovCWS?p zDR#WDsmQp#nGId9D&i^_4d5j2b6aP~>acRPtL1W@z1 z-N+e#NOX-Poa5h`aZMz>rK^E0EbT3Cc_AuV*``^qa(3gJ6#B-PD5C$nZY{d06|`iO zXvw{J6Bf=CV@z%EmrVYKnJ~!_M9RHhd=f@EDcIRv7#rZfI&Z+0R%OZPJDrsNp1}6o zRp=2T(~L@9@$OD(sm|{5CSJS-n!@&WoZgf87LOY~3inEF$A@lyQfMgIXXL zD57~H4ZQd5L_-OHJ_yQGr~dsDI6E*JODL}=7gT7wtWHUk5jA=7ycWi4zBD#l^yoJ^ zE^ae&NX~gy9ex5^9Lw*b%fXnDjoZj&Z%n2~k@Dk3Hf3CtqT1MK%BY6%ZR*nDR!blw zz!bkcjg6wrLv|t6xBPbSWacqaAJ^7jeP@WH;Mo}qQ{-PHw0u`TjT$MdiLCA92N`B&6Fsu7H zb3%F5yWNK-i_!?$(ek1pV?>pbQ2ntv*>=C za!CC=67m)0PK>X@pfp3TXFk{&_-?Y+&q(Ql_35_t%EpwaW9M>jp1AuML(TRG$% zmmMlZ&?T9A@g6Le<#8p?o2qf`^2vzRQ)@`Ij0W(F=e1x?OA2u^Gzjkgsa4t(%*N(X zaW%{;$C!6?0Mx3z=~<$s_p^?FnluY zFg%vxk850(A~!F(i)*4?cstS()FU>|8-^coU<+jGh-=CrJXJ8`2RvERMFeY){idb1 zJd~Maq1D9{$^~gK{2b``?K)nxWsK2+L(F3D31x0K5@PJaTu0Ka{HlL#oE8i%SpLH4 zb>AiT24kJNRnsDVMi{Ln>tgL}ZPLm27r}ufsOkxK5~_aU4#Snd=G7%I=o37&{3?|7 zwU2A@PvB|5U5dnl0B^hNG)?x+hznSS@!QkLEppwUHD6e=F1`xbuuH$#Vj8qn;%~x2 zd2}g1Ie_Tj`UnwyCb}o8BoQfM9 zJk6B~A;jcGX8yfEa4oo@C?+L^Ir}!O~t2W9aq{Ous*7$7SvJUS9tLk${p+VS!*iC z&6_8#g)(OF%Cem*B4W?mBrBA1088X@qAERsEtGdtiNng4q;Cz&TW#tHgGyf{jd=Pj zK_U>`t!m)D-N>h{q*gw&NFVg+vm`IJlb+z}_6gb_F#bS-+*o*xf5ASjMmyuev|G}Y zboX^b4HVTO9b(3TEtZ)$T^~15pMn?tYp_qW#>sB#iRzKe+7(pA(U$LvFr1-fiHJ?w zkqPC0aaxi>K!A~?89Uu1^Ho1wuKdg=@#2dxPB(5!=*#Nqc5|XzklN`{?4#4+6L&i< z2`lREJhI^~Bt_EFh&##3JaBztEl(teI3C$|?FZ(~DyLq@QQ!RuYhoo3xv-*_$Y5{4 zpO=b%Qrpd=iPIC<^h)GcSqNH7C77Ic!U2j2V&?V6e>QhplB%0qeX&XBod}%HBwEm} z?2$&qe{ow%@{cj+9c#0W_`{WVGF26LN)C4-Ha~%F4IaZOgzhtWsq;hR4@^j|qBS%B z5l!+AZ0owLqarMf?$WqDFiN@dg$!N&isRZ&VOr=TV>+dowgJPK*b8ANDAjRbk7NoX zh>qyb&l~VkyUo>@n8a}{kd>0SFg@HigQ$YAzPc2qNHOqVJeO4Ya^a4I2#UYo!aX9D z81WYAUq4)z!-cY+*##&)LK*rm&Py2AA}86A7jfUTibV)aYX0ZNi?C46XuNF+ zHk9&i-d=F*H+(IL=^6hd+25^hs=IfnuT+})aa@aJ3Eze4HrLs5<8VnS zRp%Hhs{P{Zar32&lQp952^~F;4LQ;o2`{{_USvIPzm!qk-Rh9i`^`#kundH~OPwWB z+W!{=ri{xbBOL4%ZZ2{IZ-hP7q58!);n6I|*|p`i*P!#?PBOQv;?IR^L;UdZ!YEXR2K_d(NwF-0so#B^&hxkQoO`JU>jFsS0k{nVf(nD z?ktIg{9c@Caa4C zZ-qwW#ktt;VkJ<%nB=Q7SY$@>Fn^QlOKu@jQY>Bl1I}C7b(xR)=oa(9-Hy9e4C&FY zmAv;-L4}snX<_N#{`dc}g(-QB6*l|t&Z&%UnDib0@qf2{sRWt$zw^)k)BpAF|Lfj` zR_-w7q?&h4#~T$jS;&XEo?2~xVY9&^x?c3?yKUP_zR?E0)|(x>Q;P9`wZ~OS{DpgU zYQw&9JVbCrLYA%_x8WYR_PhgM&O$QVoyl0SiMk*=>moq-e$EXQw)amSJg+tY_b>47>Owdyi30!q`G zu26IP#SkQ+TNVElmf<$JHxewn={`#sH?tj@$i$B(-VfOAuyV^aq~fzTFsmfWsKMzf z>*-4Ui&{K-QDI<(l7qm47c+LH5apwx%mdV8motG~60G7(Sys|SNDlH=6EgSV@1S_> zvXUrQ392l%IaoV{cikG~v_D)U4_J>~${x>whvqnUIcn2k~jq|71ZAC7uUYA$)}qT^k)=f zr-EF{C@Rn5uS_Joxdo>%nC3k;Z5?Yp~Rz4crJzy<>`&0&VL3Y?*E{c`c>df8i zkpTFBEe_j|7S5G%q2+-Ui5BQ|>^>ef5g)krcDs~DDA!4Huzh-_());>EQFxK--5N8 z1r{G&m~ZlL{b{FEr8DQ%AbP}qGmGHPElEMks-tI2$m?7vc|)E$jdmWm!*OvvNzgQ> zQIRT&FbKL$xhPLeuLr8dF9x00kROMIWIM(h1H_IYwj4*N<>v>i$8SS%lX2(T-foBA z#opw`O<~5<#_I9Ql%$b~F!FR*d^F`c*Dwoz3eCeeD=DqB^Wdj~{HLs>J#~){KW+2y z<)TD2V=ub-_P9k#07?5uA6qOGEH(GOUUUv5gt^V!)R zo{6=@0vGg|d`Ad3l0&B{*(P=e8g~JyP7J5zp&=fo5{H3%*AlbPFyUrr3}g+ zs%#k5gO_V^Nt!ToDxNacg~b1&p#S9mL0k`BIb_h*k>hJvM+(+C;+cq0)e%odt+AXK zaH=RmO2z4vl7yjEBGqLnZJ+q658UOr_@l`+t4_WCqro#TuD%1oF z7dy#oa$4+u6tsKbS^(>fDD@e#o7StY^vlpX<{Un?CLgFCzg&}&aN!}eJYy>Jb15g< zwkJBw1J~nMKhiA`MT%$VLK)gn+sJ^Qw!R;*9=?JgKU(=4Xn0i0rJgm;;*mByelrRq zQcV`$?x^bbnK%+8Ej?NFJWxG;xg_QEk~(#{U0_uXQyXP7+dNI19=IOB1&NWJKfj*A z%6~wBZnMFEnl3zGJ$|_+<)@K)J!QMor4~j!3=6M*+#-6wdi-)tZa8KYV=2SstKVT$7l%Lx(GliY+H&QVEDtL;V43UznsA z>_aRD*<(8{T&XxPw{VIJ1n}tC@|%0A|Ck2R=nvo&Q6Snzp(ZaCdwW-pVbha_^Ag9Tq7e%Ke=Jpb* z8s#P3sgK`gw&av5cjV*%TWmQQZb3k?$^VCPo-aLcJ$xI6-Rual_GUN4$018jAo<^N zA}u{|J$&VmLa>6OvN~)+VjUSsx!6y0%m=JTFSjHfqNt>J%7nb19PqG6Mtp8GJ$efc zk8^97dB*f{ULsrwv+%dDJbaT*_m})mE?P~P;HoSC=f*~E7}mp=YjRFGvozrrfna^2Gd^%VfTiZ6)}P1)Ic$SSBZ*i-<}i$E0bIBzbt2cg zZ{F08sSWmIX#IpgT_2fj+*``jiKjiR+_;_9MICqsdQAWFumAjakjn5)DqW^r`!+N| z#dac(*m@4+cVAc^q{4hB8I`2#Hr;?uC1E0eoH_)AVf}G8{Xb+CS92R8)YbNd{HN4d zM;G7+u0O9HD@&K6JSSgV^%-T2mILwdL$!E-xO!>xulvRg%@yRY*eRX)#3g#bR)<}< zPeMwj%WSyFSId>oCVdT@shlO6bo>`QQ)>+t6TU?o~@i|ZHwGY_tuyWQEImm0@ z-g1=_IA5%%$)PY#8WIm!yU$jEV_GJCZKDxYwp1@jR;-Ae$aN3c>9D<{T!_NvEqWRK zUrEiQ%8?iOz+H|jRb!H50}ijDawoIOB&XQXxA}o;hXR@gOn5ZRyf8t+@0w-&|Lnb8 zuQbVZop~+)3JveOlo{V)NCqsAWeDJi1~q8NR|GA$Y|c<05IG)Te*MIXIu((XwN^5Z z?Thim<^{X^yl?NWcUA1j{Y1uI`+I3NU!-CVI_#g(D^_OqR&0y`*WO{@dpqnlKW$sM z__i?8XNr)zuL>@dVaVX^up@afX1rxTu17a$oJ%3Y+%nd-x%+gZ9DT)|>Dy`|SW>iS zS=i@FfTd~5eMEe@8mD*)JJUC|g_}J_9oBs`KlloxcWjoJtoR3R=K&Q_SPVZL%|D-3tYue!ueacBDCNG$H~J!!#aTnxv> zuyGhM+&IB|#hv+^(6!Yka+J|M*&e-^$kEmU?%>>4VP8N>V0MQ4UY^bOsCY>RKSl3n&$b7*V|k=LvF7$(Ghd^Y zZYcxjzm~`Ivqt#GwVknU*oSV1o!ML9*VIY^+}{~C^mx_&h<)5<^f!*fUSVhQ_G_CJ zHgW#YSKZ2}wpU<|Ck%J)Ydf>I;z8j0b9}mF;XvU5{*9O>a9`Y+ybWm{6&(}KPrn{4 z5KNz~c;^3Me0W72$&2~KZD+(i>vAwmLt7>e^>Bgl@=oC?Zklo1x@{k+SKf1eM4vRg zLv)-*-p+^l9<^I+Wsi#hbA`dtq5HY!wo2}cP-ajxo~8tfh~pWFOA)05HN3WO9F4x> z))^PCVd3$O+Gd2sK*%lDXO`XmxbrP4W|M3kPyu}-dx7Z{Cwea4-xlXf)NbPjC)12V ze}3A43GbL@AKM4My%Oe2)NVW1QWOrT(}@HQ=#Nx!>#F^6Zl}Y1iP~NAFnqb~Hevc% z?3e@wr@jy_ZL!|N&UaiHcederv(-GFknqCO6VJG`R}<2V4rA<+w** zd@J*D59!A7(pT7dB3SScKzDXVB{RZe;%bHOeA+LE?!JeeCl#f)GRAJgGf!L5uKj3h z1WsFb-ll(r9m&uZ?B3_pyE+Nob#+nV6m@>s3J>$I@d<|>leY6e+K%78i*VUQd5SweZhIa++Xi3;GnXk;a0d9S;TWrs% zKeQMRaYqXeT;d-dCs5%q;VE`IS-fn)sod4QnN!@+!h;3>z{Rh&nDQJV(ch6{vMdJc zUL9(j;*Jg;j2LgE(mpDhVvfz3@ao%Ol)hixb~?q)GcMk;IthC;Zg+8#ymki)pEh`SQ4n7N#@eTqAC7!!$L zX@LO;haBE;XA6eurCiyb_?U;&-MRl+p$o+!Z4zrGWb@+dZnrGAsQ}u2FI+vJ3ziV+AwY%Sc zN=$1DmjN6t(mRAqS%L>B61D~9UEMgFE8$%!j9BUea0E8OMe)- z-D%mmzqZ8Nu1*e5VTZI&(@xkZwI|LrBJmsA zTw%Hu+|?=6DeTN!91_4f2xa8Vgg#saw@6&NZ0`9-r0N!2gTHCy|!W2tAnlgs6$tr+2FGJ-geV*kstl2@MYZ5&v|J9@E&#O z8gMLKb4L5=xZD%*CZYEeWBAYQZ!6EBtq9#=XS~fxm>6>24J+}AG+bR(eUCae!;tZ6 zvN3zBJK~|1$rZK@@XDy^>agrR?AQ+b%}>ly1dSX@nT+dtsLio-Z6+L1OEEgNa z<0Z|(-4NcgrQ1igdhGH}+9~er8E%j8$G!t#%VEA$aF{663eyl;DSbnRSKRr5Hy2=I zh@&}zDb$(`}?W#~EXG%G+?7rY32fj7f$xc6AP?bubr%sK9~D=6^P^{{@= z2QTBM4|>axaC^SjBEAGK|q(b25 zeB@~m5(})UGX{Q!JBJ~!xYI!huk=GptG4^p168+82YY3$ja|R-d8dOAn%}S&Bn%gt zVfz*Fwwtw0?;Dvhk!LS`+Ez|z>z+OyZBKvT>Ml~_s(tkwcNz%g9Xh}pxK*Wxz->p` z`elO6UtP652R=9mIn3+IfRucAbOxB9W2*3W=_h}VI}L;R;*II1#Dw&N1OD?#yJoYu>tfxSM8-9W7zhsCBo9rn(t-_wceq0q^eM1Wu};x@dORD# z%@U6V6TXoc2Uov@E!bi6!He=47z4r!x?5wq146IB)7CG-O18U49nYLFA-!%JNc$V# z+w}B{*nwllTmC~_3=--@+nxKrp~H0N4A_EOxzL{a2bV{@JE$q4RqzhAyuwa%P7Go8 z?Ky^gn*d)>Tw})k^Zf8;*s&U570_yHytZ=gkj1z^QTp?Ky8|w-um^8WZ0qm`_ilD! z!=u&p-k%ir?7s1Nr#C0wrC{tZ1|1z_z~vnnLgaAki0Bn}rt$W?hK+NwIe-qPhp+|4 zaNBa}`gsmKZFVd6^#;MQ4@8)OafXO%U?^Dc)7Bh7*S;SpfyxheTW4T2GPcJmAneL4 z@EmyB?6yJ`M_7%I!qL9Bc$n1pkt*Lvj5&a=VtjGi4f+-Tc?<`yeH5~_$$aPd|3Y%Dv)F&pAxyNw}YbRXmST!Ll)8K;3@lMdK^(v_wnTf*YYkbjZ%Q zVAoC1$}2MzfT4A_FF$iaiKU=(VruTi_*k2+^qcx>yKwB@in&W{U}O&GC`mgW0_ z=L`!B4%~JeEysG0Ve2l$1*v>>*ZCZG&ah}9VOR|7@q_XG2A0K0s07QP`*G(83*2G! znD`CDv>6vD+n#I=z5w_0Vh*2!O)(zn@F4St&kK^pp9Q0F@8|IxK9_OTzddx(?Vl%m zV=(!{b#IhgZ%1EoM;^CFMZbyNs`Ya8^aIIa?`WxfKP~3yIb3vx-v}(8X2Wyb3qZDZ zi`mQDv**Agk29>}aHSWDqxix_ORZ*-;m_^sbnf@(qPYCS%XRRWJ;YcQdN*cR(jEr5IGW3#_5eBOx0@XCw0zxesQ zJc#xX{_=csrD+(q zHG#wYz`513vAba42KD{8W0D@uFEOMU2O#iHmW z*>QU~!x`%GkoXjK4AR5v7>t7KOKX1Ig7JDVlP;GIf9J5TjS1jU9`L%dXzV|mJ^Yix6sqG=y9#`@5P4g*k9dY4roqI_i>fr;|K2|vN ze7ihndk;L;Z^TTc_T&z@D=_mo^va`EMi|?CU*PdN4bJrrMOCZ2!@OJ^Qg;gT-7bH- zEHX}kM_-5iw=$V*c>)y(^%X`bGxFeS_IR=BPka7os z;S=w-+*9uI3e|huvC!~l_OWA@bo(REp2HM;?5)VH7QLMNcaA%9xnN}Xj_Jg$pgZV* zgKbPZ#*n`IDK}oIwY3k^fS6M6Xq>5xt)o3x8~N09dGLA)JaV}$CiI1&BPJfcI3EKp zoTEAWs=xOfb_o2{*;@M0iR0%jFvr25>Q*Y>Ul8Ms+Fo$WrO)PZ$ERH1>e`dJn`2!0 z51#{%R4$klhZpV``hEDs7y{y-w^ichUG`Jpk;^D|U^?p)M&2Agam$#k)qgNvxjX_n z1s=KF&?Y|6A%|1iqCjhDmkoFF^JyL(Fs7k;xby@_?IOXn%Xi z3_POn-vVG>-v2rU9;sZrX>003Q(*eGE->8E}RwUfg@@2#E=#gGp;f9h0#*6#qtNi!4 zBP%d~56-PX&ZE8wy@a?Lc8@tvE-`pzg!_agq6`_`$;kl*;OpAk^>2rrDBByg8WXs! zY?HWgu>-I05-us-|Fn~3L-#_(h}96r`@eu&=?_e=*xGk_c_HT%cYNOVY5MSNc-@bo zQz7E8rY{lq2VVc3;<_0Z=SVnH>EoDZ;H?i$ZKXSgoZb&S9-MtRPs=U*;rF@B+TM$q zJsU`AySyHH3Ox47fS&Be6bZx4;tULdaH89a)yn~s=fGowK!g6H55LBd+97a%A%^CZ z9KZA3jUA$;Z`-;sZ9T!nbp*y$WXxniXTo>BygUPUegS^FdEY~jvI4fe|`M5nMLmpvv-pd+VkUT$`R$F18=>`3zc4k({pV{mmN$8fiq&1lxbd@?+vF1@;3X1JR6`J!pt+|1_oxNoH!S|L)d-&qqQn{nYC<3Sw^@WzQT zPUi~lXVlQPitl`RGn?BQd-S;&Xu6$&Tcrm6z)|(3(*Kyfq-`4TkL*J)K_KHt{qQLNni;@j9 zQQ|4^gj&qv!F~+Wg;}6AJjJ}ZV?Xo?BSOH|8>|qek1V6XNOA@uw}xvzGYW@%yN4NOmHXJtJ4?ObO!5Fb6ZGq9?v?l3y16?<50XWgy%S^ zufS;J&uA1$`}M_?Q{XfM@1Red=9f7;0`4EUOOBVjC4J}18~NP6vG$z6^^@qP7XZxY zVOkn-U-EKL+&S<_Xxyo4UtZ(eR#(o1_LwER?G=72TfTvcXIq)Q&r8MoVdULR=ti{Z z>CK6=^4oD|#fDbL+?Gb{U6?bWxBb!0Z2UK>p9!|Dm~QnNj%F~rY-AlQm+;h14M8t2 zx1Qq8Y~H$Yc@A)TbceWYziesP^4z&^EQkrVI}Yw$?8bw05@A4>?W0eg59QWj-7E0O z<{htcL%+J2LO2BO9+Eq(EYdd?#JDZGg91;O->cw@__=#NE+y`M}$QW{K zzJKsdh(~SpH;g~;mlET$Xn!~I=r0Uw3Wr2?ZBOe>?a%RScjJychH>Tt{p-lLZLVPV zJd(g1ka~GJt&gzrXEn_u%3@0rdNpacQMA7494*z2eSH?vFFvW!MizD_Z*T2F;cIy5a8> zcINSxF@&%WzZk8s=!6Xqc!{KN>;2>_?8stROEs=&#i_$^s~OkfPF$bGc6le`6gSSe zJMP>jnAcAC{D9u&M=RdCTj!mZC9l9Ek6}ak*!e}oZWp6JTkdG8Fw_<_L-tHlQBX7m(1HAB3-uh_J#J2JT~r--)<33dATysap77%Z1v%Z_iXh;dyM5D$logu5+sM{|8fXy_}^ ze`7(6>!RonM~^Kov7|J=w)UuiDPpMmwtZtgq?yZ@2ZYzZZ6&8TQ=^q74-W?vO!oao z;F-)j#u~flILt#b!@U$XG7$$=_sg_2leuk@7~_ggw}TO6Zx-GQ+%qheqAm$K{8oHYje5UY-7=cs8eDrQMa_z74 z9(X*A+Qy4hx%MD}hsszGxwq$)Zb0>o^ONx?YJhv7=+kHVZrGY!0z;e3XHY%yN>@tot1&+GQsRd<-I^IGb-GwreSVTfA%L*Kp(;!Z>b*J@0roJcYxozd8Uo#~rEJND*G~t?u@7#sTi6=Wd>!pDinQ!}=N4 z1$Y7XYv_6EfWZ>a;fJtHz8^Tuz}-N#eOOpS&%h0NA2{v9mCCCdXXn6kKnLaw!xer` zfQ1)l#j!vtxW{~d_0D+&u2tII4inYujEg4#-sGTf>*~pJjys;Pt<0#HJ?7l=2JTnf z+Q-GUeH<_Nzp)(3j2m!)wh#WClighDopK(w@A+!qJIAdvZo2_ZZfmQ$wqn5f zaCHuHjyuyB2OfJ{RyeyGpSQpX4O<7iuCLBu&v9oOcLU&rWXrQj2Tn*0CIVjdmU{JG z@Emw1GF};Ai^TNwI+bXD|8U(CDdnpdndiVWkwd>GSau9IKS&($)at$vcyW9`@J!^E z0Ju(t$;;z?d~Z*9z*i=w%Ok>b*qO=blYl9$-)FN&OWyMI&#!*FY|}Z%otX^pql$@~ z@E)8xls@n)dxy8h-&hhem7BJY?2)lsoLf7(5OMXYzWC>E;F-$_qxpO{i=J(>uELEJ zZ}gCJR}I9cz2TYE%lNps_6Zh& z{qYr`p5xA3PO;A-#OuFt#~#iL9uBZD3)${(5pzaCKbfeo&%wpBDX;I{Ke503zQ484 z83hhB0)v=}&W#xt*VyoaK3=<=p5x9$hMQJ@SyOy)Q{RYLAO#&^?$Yw-9C#*jMh^p8 zIB|)5q+G?JJj}-M&0W3DJqMnt3?IJsXuzwsEzZCV#&B1A>pRE&J#9`Zz|igXd?=_F z=I&l$8;DCl6`eTWSZOnr3oZ)Q_Q=^wnhIm^E?%wqaCs7P4m%UMV6bYbc-eKByo-}* z3^1)oxcf591<{LzfQ$5KZXSt@Yqc0frUVWT~>5y3ZV%DI3yn8ctu$C=YuX9?3cF)*mNs^1Pf zmoEltw8f99QEBWun6%IqHSUalXZM|J7qb~Ryne{r++YWL zt@Fc7)uHzp1_`Z$)9vepuXN?OknQ9wzh~ z1l@qG!jA4nI(&tlpEh77S?K4Db1GRMulrFy^_0LGHL=W)a{Y`~#a ze?y+{tb?zpvu5bC(qMy|X2TTT2fE&2zu%+nuc%`O!@agk`JJJ@@!?6^_fvNwX8T@Z+Y%C%wJghw7MqyjOOPN7dB|y)+eZ?s}AE++?l}`PuX_G_CWV@0I|d6dUUF` zCvUhKhItA*Qy7h^nBkP-k>9cZv^}@`9^GFHGlAiKheNS`_i+SGcqxJDyQuwdM;+;l zw*cMSrzV&oS+_vNT?Gp65mBR!Ls^ zo}J>(1Wu^vbHZ8UHr0h;9olr;hI~2s?i6+=aN6Ai=J0KA9am0VA4q}EkE>(1gU&&%z@CbQM|IOK!N5iSVaV4Y{&xs7AOX2Q3He}9C9p#@&nW6Lae&Ue4U&IFDj zb(1W-NgAcI!|kqC>f=khv?e=+o%tK#=9>D1-NT86gE1cNZHCl7^Zk!I6Bu94o-p&t zPmk9Yw>{0;GH6xq)7kN6+?l`uCoBop0>RI-N4Mc7L&ivMzkNgAnZP*Zje;W%kecSH!$hf450pugkb{vjid^+yW-QUK-%wG>nB#d=yd*Rr1+tO>i z(u&_ww#zX+r?4}9Veq`dMEhkw9*3FX1K z2@TV!+9w5IeBAvlEY9R@TQ25XM~sY~m2>;A#eOt`7S#J&Se(g=E8(@j_?<0>Zo`Z% zjQYz>kE@$Tr?6ki3oA`n5#hAGmCLhu-r6JMfmiMD@Vn}1IK?e9ZcK2;Mej+PVIwX~ zwB-zYm-~k+ue*o{>srO+HZ-=(MmzYvVHhL+t7vJs8+U?9W3Na1;?RFF?TH+EW*)G4 zgSY*CrhZ*T#4YgJO`mb-Gq2aQkGo+8I_@H*d>_wVR}m4n--3>?fA?c2-&#VqojkRi zyBtAyit1+6_9W;pQ{%j1j%~&_2EZxqe&t_rit0zyjH~4P=K7a2zcSeSViZu@aqk>v ze}xS*Y;1{&%DnAXb0^<08mHi0TB}O$?_#e@h=_}k9$Q4Y9AA5RTUs74SQ0uU@9$!- zD~JJhe`v~Xk3);Eph_EP}Q#1A&;V*_e@$e$O9e1AK;ms#Z zS8!}MU(LZPry)dXYvFmyeKYLLUpO7K8a%d|%hmB|+cO94T00H~??2e{Oka!}E*Kf# zg2V8FJyRdj}idgrp&SKOJv;n~y+XANKHYG|wOfgVg)q}+MA@fCNbaCmwpwW49G z!)F5{FGJhSJ-P<=`<$9*4#U|RwevQcr(j=+mLV8fy89%RX9Bl0ZACp^PNLV~6&8Mh z57^P*Zr1H>EYAdnYfwL~#8vGP6>rpB>K?`SpJ#cdFNWW4?F(yF@@Vgdm0iM=>WX%h z`{(+3rY}ZF_sIyG&&Rm!x#L~M?pCkvexKsb^mVX;LGp(==(DB4mfN1&O8)IR?(JUK zc~IUq@JBxiN}i|y~n zm>=QeU#~a*^}~PuKF zJvPX}Wnw%d;lp<~>iDpzUAl8I-Zspxg0>m-4yw{0WmlN~9`+R%#sm4`TzgWYL>zar zTa|!Qf$kW4g~gblbBh7(7)ZjbM*_MzSw;TziH^XlpF9yg4*zF&{?SWM%NRKQHp zR_@e3&FgB)%_(dgVR8KxH)?RtZ#oavP`WE(#GRJQ=fiv4G~;4)Y=J+$A5WcDKVh6T z?m(vN7oP7?^N70lHtlX$>9`0sE}~scx%EoM7?sSKSdqc z>j6=)UyJo%V~OV42e5kUXUDh0j_mchl}eZlg7>p^hV3VX8E@1sP1#RjNA{w{7neQZ zicn{}J??6>XJo(``6WrdhaK7L(We1=$f%&_LTEeBV*?Aq@8k9Y8}}93<{j`_5^wYl zR-Uz$UfuA)w_4%yCF*)uq zd)s*ZWi#d}>d0PK+EQwdELan!!wLgDgi>$~{F=<(#4W7%lh4(8AlZ?%aLnRH-z9wErhnIZx$)}Y&q<)(8p;~XG3dLb$9frQ=&@K zrTY3Z0vH>+^%nk|o#GW;hNV-j5hHhm1RvNERZ1z@ZldA6It%GVjzq2rnP2jMF{%)J z=7#&n=3AFi=41;SbkbgaU~`-|dx*CvIW)F6D3o(1eUkB8Bv(7GpG$ONR{9&gr}xJt zNolTuQfA|CA$*rUFD$Z4luR|ByHzYrezzQy)>FFuL8z0nEalPM)Ly-drt;U)11|wm zvxfXZfa^!Qj%Vgwhq$88(oB~&l67C^Yw}4O8VJ52{jIqN#Ir?8sRXa>W`%M zyAod_vdnpZt#w4wOMv)M@*LHZFL%J&lygr}J`QoXm34S$v>IIAncT5pMEV4=SXSuV zBI=?G&7Tal#tN!Vy-*DM(A58MZhy*>m%*hI=~Z*J{7gL?%(exSM0Aw)bTO=CDg=w2 z&ocCE`Dfx6dL+d@1torl)s-pxG)~htbydR*`blS~=9~vwaIwZ@bN2WB-@XBx&oI6- z2L8y-OR=HP)a+ywW44um+rpQ?3pp}xhTqpron5~%da5c(?_<@sJ%_Q$sOi8j0}-4< zUQ^A_@E6+%zr2D}SNiN6deCt2Q}qLN&yv<*Jt0xkn4;QTE_yDxUtF~F%#E%=Www%g zgXLpW&_vJPdfa5h^kiM&WY`cQT?p${+01_Rt&NUNUYc~ih|G(IwNV=vC0b)L=ebI^yU*vis!#oLL=b9eA1RFz+5bo!;7J?1FZoM&jX~RVL@I6;R z@1Bb_nO*w;BoDOQaRmyTFO^N$#^;!K z9Q3chaug_4_@J%t`0PDP%b6gxul#Oj+5O?QtMHopHEtmSL>rUD0mX*SrZ7u-I&JYf zWHzX&B3t*;T!|cBZ+cL}0@CXKn3sThcDe;hnB&W<8VPEfvk9Z;offeQS9TOg-eHP0 ztj8@Zv*^XoZ0*dtQJr_nYFLUZ{&+L~Q@TmlSXIV)a*-8?Xl$j4)tfuioLd%CuOld6 zcBP%_%ns%JQw$Ys$$(AHN`@(*SDsUbY_svuIVHyx{pPi5apE&)xx*^uSDXjgm2F#XcP>+)!*KbT4 z+u#YtxZtCjvkX^g7(zc$+Ag}+j4m_o@HY85&=3^r;v@X#-#^<)2n}J$X#7(B;46=b z-W--9_ZUTz&Y-Vwgm-lRXc23_MqgMFrEE`lwT}D{kwH9FUg~|zMcGq8s)F!odY9Ip zrs1FRPSrOi=*ejlf~YdBOF<;ND|8EF?xj)KcQ!_=~vNcY>t?)kR zeS6dVtHja5Nw4nw&w1rq7lYPaw>|hZwHnOYA9p37b}PivYHYB zK+X8;8_iUnXSUKF##l$2H3s(Ml*@H=iHZHh=%248lpG?iprGq8ihHT27u*7YLuTIO{$>P&sg zE3fR5J54VY8$6{W?Ogr%FiC-}2=g@A@{DEs)*Bv^i+&iuZFae=53E$K`_w;2uE1`*TSR%0uw#};-f=AKYnQ)RC}F>UYCqLRuVu7&;ZdQs z2Il5_PfuK}AzXd-y(><)mL86(wZ7u}A5@&^tmdzM8ht|B@C~`+ zLpCAMH{!veBb)LVI1$N_tfk>+BojW*QuuB)o3=VrHn^jDLvnnW>(eF%#y>J%uUEOW zY4vr&c^+95W~VgKVlBjZoe>YP! zIG>EoNSc)Yj4yB}aJ|nrAsV(GZdYGM$v=jgk<0VLa4#^9k}yZC~uD3Fp*v znzUukS;|vnL_9*HXIiwMYxt`nN1Il8(^}HkN^@{qp6+Dj`)t~xngzp_kz#i7@25~J zg*>_`koCz1ByEXLKOy0YFF;_{+k$Age|pr#R2H~6o^@e=Ga9sIBP#jM7{OFBC(Spc zHpMMDio<>S%*Wen*6+je-yj|~mTxZE^O$cv9dWfEXAeGeu{L-UL)-}^$Vcd=X4gUR z@bt@3qFB3|q3JaZOnyQ5PaG~AYn&g_3h+y-Ti!Fe^wt^p`&KgTEQ0X#H#A6PY?6`i ziiE-`Q<_4HQ&215rC5{p`a%MVE-ldD*)SUg$@by}GQ!Z-ZKaOg>aVncrXRcZs^3)s z!NhYHdsY^?PIZ0douI7;&TE=z!dhVN^{KlaACpVSV*J9aOG8h|3D9nl+K8B}cMcH_ zit56j-ksQSEHcL-bfhG#p7?;%oGIAMSdYS^nA^?KA63Le-4-V?s&>nJ7~kf%amgL) z6e3+~ht;wgRM*=>+El-x1iTwoLKTwwkxA=L4f;50gXxsMlN6-Jn>q5h!aG9a&L zz`OK5CTe=Nmw3Pko5S+kjqwTv$>zK9w>}wsEA6_#0Dn-(dVO-{L4`|NP>{sV{?|8s zA?#hKK6pJbVT4kcqjTE81+r>zE^;i+m3&1lt)&t^FcY(0k};*!VZgu)BZCSOX+`Y4 z^ZG)aSTZ5iAC`YkEXqRWx2<`lGoQp=4P0fcOFeCGEw!pq@(O*eCyj73&L#{>(tG=2 z?G_d#ZeR(wZH$#}clw7a@6(p2IQ1Tdv{q06M=Cpi?GuV zyssp=f6K%+cT^YN&vTPlUf=&_h6Ks_6Y4-uV7%>-=Q6qMBV7y8>T%F z>O&PK?XBl|tdFJ_cxUdGO_cqWtkr_7uk%qeKxmX&!g8BKmcTnqBa{CpDK| zy6kT-zukuSzjT3mA`V1wdOeVRGn@DwM!kcNg?DE%WDe$|ujlDNt=a^WI+qWK>Nk>v z99{P6%?oyIYPRVg)ajh97xeh1WW3vB-m}~9;mQ3+p|uGl+wyKjH1aDMP8w%Bd5cbo8_a6!pp?~4-N8DbroomY_P88E}{Cvb`zDhZsj zS0k3hVWk~t{Mn&zb~CVJzW3ogYj5@b3Uzi!tkW(bzVXQJ)WO%L7d9-x{TrKsthp~a ztUmB8OHBLGBVG!NRCnI>*j{aMc(u1ZrSXc|qrp4&=E1heV=}QH6qMO%VDIg}?XGMc z%&Y3yP`N*(G1@g3s1ByYtU6%W{$QT4FG5IrkY+d{$4wo1<hnv0Lwdt` zp!NZ6`h-p#~-yh`=)4U+QH2QSYHan>OU9fC-_!MF~2xb^8id~4;z=}3k*&VKm znC1hyq|xV@wmCr8?t-?=hNYah)7Zpy)3xRlsWVKHGisxfFMB8?q`Z^l`p;3Rg4eSZ$jGx zVBG0&OGG3Y$Ve7EY|6|EZI6c)bVF1Sk$fPdGxBvPh(K1{R}#7o6lfh@Q}&2gT9H3%vmPM}xLA3}l@&(STjQqDE34 z=(p>Y!ln&$e=#UrJ^-eZOc>E=I{!BYQY-sPoTDi8K3MoO8zVN%9=ZYh`?m&iopKF} zz@~Gj!4C(|mv%xs7U@*UwbY7p`wBi0SmWg4#Gfi%EvdJWoxrc(N;*)c_{ibD6J5j4 zpDpOIOFF+?lJhxxEw_6=w_7w?E}wk5IYRBCYBr?E(#lGj<4O#3vH#;G;5)&_cq^T5 zw9F7caVDOAU7EXvUgIm<9Gkj)xC{HRw+Wv~TWUC)Z5z2@2Ok&~X&44BzU+`h-yvPl zNUuR$(G^ZcuRa?Ny7$4PEh{|7whiX@MN6eXi8I@POcQyyvEddnAZ`t#id1+)Z%;r- zf=L@GXgMCH@?c32{L0j94uMEDm{aGL01KqU$*2Xo!kM8Xe4y8#hg@JX-97Kj%p_Xg z6&ePh4?R|Xr$l;11i}Fd?ap7Pi)HJ`&{c`N-y&OZ_#Kh$?&-*1-!%Y^xSk&WN<`xW z^igV>`M(gPi_PHz9O=oNIMV*^=7$r_uP1zGqq9>k?j?d$10|)2Pu9&YA>2H-RiyR)A z$DV*w_D8V*80PmW0f#19P>R9XgC%BpuQxv;hyGOz`WW4?UlKeRGG%6g27I43a1@>x zYd!Y4;BukqRBvL;;W>M9?+y>pfB@Llbhs&klnf*x3+^_RvNi%DpcObkfrI!j4p3&O zbUZA)8v;Q@@`C8o;4h_v=*$UNxqo%?(yfQ%8LP*Vq0ir+AD|A<_Pb#9ZpbVmG8k5n z3hy*yz6dgs0LS)1fD<^2r6ZNCu$=9Olb61%HAAYpxS%WnFuZiQ zK0=8UL?#PvGRB!Q&W3`nJKMjUurNZ0IGO0ky65yy_$PH*#0E{IaZjMkQ11Zab`%N=hpjq*-tZs-h zLWvKgoCeP_&9sMR-30>%W)Mn%fvml3rv2!=eP!k5X=wG;h(Z_>)9_3B5~;eU`V7Mi z?+Yv%=rm|R;|w?RbAiL^X2Jbrqe0+c7lfAo+mSKwm{Mnn>Cdl7hF2fPS_RagLd~$a zz3-`2i=?H%i~Qxj7@?NACBBP@xxO627C$TA7sZp2letqN74O)b6YR(B$DBH1zrYk< zZ-s1q5U{9r&N^sbJ$=c9npak<-eH$KvC!h7EDZR@u(tPg@G^dA9$w^S^w+#x##I(! zmbun*zV~~=^NU0#9v#nZ7eIVJWI}Wn1S~Pil-h^+PB5$z9fEBaLEjS^bH;Uj*21dJ zUnMet;v8c7&x+whGAuDd>`&14(20x$*sB-93B@}MH(dnjNkWtHR^-|EMDf8dGoT+7 z>5f7Z2rwW~`duP(oTvO4X3SZ0Mx;bCuKIU>C2$zgsomjY?Z6j23`k^vaQ#Ul^QSmG zNob-drTQpU4C#K60ZGl$lXVtwv!cWJkBQ7*$KgpPq^t~+P&al|2Qr`<)A^k);7Mnc zir?`Za*qB?`-}^OOi$+Z3_j<)m#9L|CC*5U(_1y1DEt}sy0;vis5|O9wsZJA)$-L8 zW5{|viJtxC=g{F#S!gdCk;?hI?$mf-VL$ZJ!KE!4-eM|NXxm2tkn-+X9j4mz+^ zBBvOetS4ZPtidiR;dI6~vaYKFb&lNO)37zbUoVm`CLimsmjOklW0xQ}CGm|+>-N|> z-`y!wx8=cKFOXLuA8V@*Vv7vLF3IPlz%Lr-DS}NbMLog3^@j5R&q$|koVBy^j*F`8 zBA!F8d^6ctNxda&qy_e^*PLB=M(TCctetP}>?qq-;5lT=83n&|N(Wf|Ee<33x;^I3#5-k*whnmfujB_v$71V)m?Jr`OW>TUct+B7Ld>1v zclaQ-VtDIm@=l~M&fmDOr5@W%H(ZH@Ya*%=Ue-r!WW_ruu76R zw{VSw>#7(#AKl@Tvz@|SPmnJr8FQ?cVT_!^Dv9T8!8PKo+hgdozEgJJwh(tcPF{s% z%&b0$A+k&S6=!6$UkaNplOkn%N5gZdHZWBR)pMMY%mh(0dKoyBETq2eYn)101Y~$( zMlO^rn7(*EDFuQAWEf&T_44X8z5$*ocl0vYh{PmntF0Kgd+dnNfwQK?QU)Iyl%Fr$;h1tsToM zpLZyf?Jn43Ux`k)_vxPrxRZN(CLp^b>ZxW}dz4BvD}LGeDAqi7)pPbnEUM~Sxh&Hx zO1b5wnpsQw`mePg@L}8*pub$gkHIH;JLLRxjRep5dlD@Ub~}coSkp`xW&OCVxR~~l zo?{DG(^wdM(zmT0G3~uQFa4l%`LZN#>ucHDvc%8Ru%=ZQFDpxO=Bwmgo&9a3OG3O4 zn&4S^IeBn*xK~b<@iGTat0LxFHpz1ef#^An!8O1{Ok__#BZA=gt+WFeYEd`F!I6a-+8&mk9?G zFVeGq0gDt116%sGo+Bonej3r^bRG9>3M^7444mnIOzU>$!W|W7MxF1FN{`*F3<^Zc-6Um`t7+QO_F^}>-olX9x6P^adp0$5 zLz?9x2k!&@5lP~4<&Oi3I5-Aeq}*^7Goi)BH{sF5ous4U!cIVi4U2f87{08@Z4&N6 zA3GK962ZpQCK$~YW2hiSUTWA)2bL~qr3W*ptAx|DtKV!32xoI>3y@NYGV}qfL`Tc~ zMeS#eK(L}qyXCv!qhjzMT_|8n^Hx{5DHPNS)ekt#@i<7*)q6?5?D!IbkLC;{h#VG&bN-Hfon(H0 zDCGVE`#Mo<8&ASBcJ%+|A;iE5g z?{Gy}l9&z`0YF_S%$nqd8c?YjU;KR)_n&(1AB}hrkx^`9oxlXu&9+#Aa&AF+M_^{rOnT zSQ|pg#ms;H?_$tDmTuuUPLp-A5CHIs-6013V+B?e;gTOUtUdeRFz6pc_89dOMcey5 z27MBTC#758EN$b8&8Ww(FzAywJgND@D@4dTk^2C|Z2MCT`Xmld5R(3N?tn9|gt*d9 zKH0$(N5gV1O5M1&s!eAh0ZF)5d9Ll- zbES*tzuYNcpzD9qsPR1Kl#~!nEVM)9lJeF$8{vWv*ZLnd3WVmY-rJ;JExxwM54kT{ zIoApeQEEA7!&~5ft>3z_A~~m_3JkZ1m)D- zv)V!zKd$|njjn+?MfV)ZSDUXcvO=sRDzjVT0+i&=*)SJ`Tm_S5gEY!dTFdC3^Mluo1%;+)UQ?`@(uB~xKViOqQiMhB?wqyEo z`=nF~5*T5K#n#Jf)A$y6rpV|qaS(~A*UBH)QdxPO9UR#;SU>7y&NTHw-R*$F2~hnn zg%hU|(|@bK6kd~S=R&K1;_0y1dLAB_X{3*fuGju<;q*7^F9}B-7nd4aw}bT>U+uZI zeJ%LDtGilhhDX4LLNAe)a1*pN_2B&C6UwA|r7YO%*Z1e?v3QzphJ|WfWQd)aMtTh& zHX)uM!39J;Xmnm9rU+_UE(I-|prk-n_C?`olP@&AyWoFp`KN2eix zM8Eq362$^aXa$31er&z{c-n0Ahy!=J)PD+?F zQx{oEaEMv|4zc=E3-0E@Whal({U@o%J-Z*z%M+;USTTHTFH^U8OY%_cI`mJ7)iEIT zpWa3>f}bT{YF1R&TSHRu?y){NYSmnzk?kA zDt}SBq9IOi0k=Gcoar+u$vQUx^{|R#ZtNcZISw~{E=J94;7B|D-8d+?7k+^6Z z1E}mi9s?;)+f6SR5Rb!art>%R{T+c4p*YM66I2284QIN0{_Tic46NP;smlo&#|CX4 z_K8ONsYaWCFnq;eDIlyrIviw`-n_&oY|=HHER=3+#Vc&tFpMBfCvc{A9&>4i%4~XLSSlx}5s2hpqOXwZi|mis1q&y0!jSt#GS-(WU+3 zy)*wM4o`aM^kpc;I?FGihw06K8Z^HQGX)`zw1>JQBs_l#)E}Dc(<;ChJeZ9c-7@-p z2j^FDc!D5jHQq~YeslHO4{-=T>7jFTWa`4AfN%m@@IUF`{4x$tdgxgNZ0+3H9Y3~y z!UKV}H8_U}-E#k{4$iOQ@T3Klai{p1^nmp6N$Ur~#S$~GOona{{hbcZuj25e1(Y>X z6Y$AnXF(^5QYLq%1Am@IRRqEmfhd+>*%eu?CCY}51R1OUpT_ckNn`nADWBC|z|@h$-tj~1O0fg=BPc)Bl-c1AP1OGu zf`975;tgMAQD-#@JXVxy%FunK3N%r9f7(R-Zz1@nF0Ae&-ofgN1?b=8U*tAjr=&VKRKJZ9_wH90^`1Bhe4sLB8H)a>eD zqKQE^2Ax#(zsO$c%c2zOEU$qN#ePQ|`_;qmJBY@@6`sMB(|Yem#@VrAcqf=8#>fb0 zoAMqL$Nm^`Kk^=ah&<-(EVjUc&V>RX?frw|^i?3qKF@k6{!e%hKZb19b5Lq{5v2Gy zRKt~khwmeR_W;l;{tfTp$B@lJ=A~}yRDV-_ArfJ+6Rg88C@;_L;aBo>-yvnzGW$a47VSgxt9oU*w2rX3shqBibKPJ!pl**C_&W$)^g z?PZtMu&k@D?3oYb9U&KK1#UCBdB)$e1a-=0viYi68dO)d%$@Sum!n=(#SLZ`JerZs zGv3I0tX=jtyR4EWLUm=;oIJ06GU{SwTupX?^UO-F@k&;uR#{OtUq#jtUcs!YYQ12d znA_eHB~TuhlD+0Ia0cOih$Ya;gmi=6$ zSVcbF6rrvCc_u>}f9;c0-sO!or95nx)t9QIkFc`_y(=H~u;Nx-?Y9us7Mzdr(e_^` zy3CQd!Ez^0W)u3HLhg}rifYOuP*-5quD&@)=P_U19RZ&!C4vNe7-Avy&oyX#<6G~x z^meZ))ILw+oQC;bVbpC^ptJ`X?IfJjOax^!dabyW_N2aJYn;y**QI>oEbHebHL^ zWKL3`vwztMRa@dZ`URT+8&6Eng%S_b7xTa9Tsfvxq#YUq`t=I6?=3#=&8GwXeo{_S zW`fte(a4w4o+)@8Cl7^ob#1A~ef8Ou9@EN~j(CvJ?k~SXll0 z00%R2Tp(8L&HS zx}R@E13mK-5K zw=FhrBC)-*Q?r5xQ1-X`UyN?Ax9D7@_UP)iuKd4ZHBet_79l_E*aKVz-@0uSns&el z>0Z^;6b8ETf1k#{h|{WoX%A2pNRHVM_xjJttAf5~20^Y#>^zg)wQ;UaoF@%(cgG(qB+1hG)p+6djtfB5zMfttAZ z>qDvnmCC0tXx4%E<>cGAOgXZ*MTzB;u}IORhnwGOoxjbLJUSsQ{6qwYNgfjqXzmjq zb06+4Zvczf+Iix2vIqs@_Z=dY2fd%D{M)_A&t?x2rdlQ|H!Mgz$+%$J_U2L3WxUf+ zjvNj4-8Bl4gMM1e*Y5kT3~V;d2|w#_y9s`K#VwvkMi?&p$SqPurw91c#joFVu^)1Y zH4Kvy2GD>iE0tXW9g5fF5K-+8T%;RrQC~+xssT=#jBUcQSIz>ttjdFFekr7A$e}`< zJ)rPgD;iWKb5oEDcLV;<16p2K3y`;6L!+vCCAl1JM>*wE;YHLjYn%EI!39{`T89Hz zOj5$hmGP4S^~f=^TkB+ya55M_8BmWN$rLmvd#n4b4wt#99rm=kR+f)w(p_iKkT1ZO z?qWY2jCKeMl%cUlbj>&yV)RV`&^CdiyL*DtXLDK*C zX{K9u&pK`Npj3Zu<%6PjWwP8#>!Jc><=2&_T5pW^c66LE_CG~?>}038jjp%Ig?(`E z2$;3{Lbd#biu%D0sYRXrBHci9f0hzRL*qpc7{@rk>MAW7f9Pm?%iQJ!@Wyc-z&csm zE=604V)28?BMlS5#UJt03Xezx_|=^J$6WG{e2eM@Idovn0_=Pgo4P`{5uiNn2Fqb- z!;7|b+5Rh}{^YzIcWQYb#7HB z7v6UCMgszy@sO<#yggH8S=S|U6Kel^>>k>EX!MXbe&iF)8@H-Mv)0iB^CRW&R$|Kk(RXfUgtT!wEzhLk$QBS!hm-Nia#>89RZG( zBi<2*ps{oSOuP8w9PPy;TmvnGzS96P{MZ;!8lf5GCwS!l9h3FsU7o|r%03Jz0Q~_* zIY2=FH;nR=%1XJFT%irhtZ|G(O0nF<=BWZ04Iid_Xi-O$_UnO!XYIe$R zwT#Nd9SsQ`a=K|Sn#tuF(lzXG)3hs7{bC5wsO?Rw2IdB>7Ha1N^%ee^@|n_Q7|#SK z9r}g(OO^A$<1b1F!Bd31@Az%w%b(6|g9J?mzs4WEE0P&)CtDd@IA|w(zU;QWfjwg{ zWCj2rPhkCj+MkmepafoZ;jBC)p8t+fe)5W3zwB(2im_|#pWdnaFa0^Xqt`%AquX{r zW0ZgI&%N*<{(p{e9N4%z7aH8R)-rpzlKqAw`Td36VLGoBFI}9tNaee@#7hdng3G4~ zPPw6$+mpU9E$QF9&jj!{0#{GU2(2(OQC>>;A)0Fn){7 zbXa*0#00e22T_GCFh?wW@*$Y1v3a{kgdQ-{jht|AnZpH*69O$_RB80>1pj4Ug6vTBk z@U^1rk+9PiW-npBJw3Xh;JzMtC{+$T3`%9ra#5eHqs;P;TEN>OoNqMs70t}vz>Lk! zSU~p&dSpQN;qW$STqZmdaa|qEQ*=EF#$aLg3ihg}#}M?kuSW?QmjjQ5#z})Cimoey z6$uLzWeWJ(WskJzV<}I=TtMhwpqX$u0-*+GEkZ=X$Sus^Fj!BIKInB{4+IL$fk#21 z(%`@%1OyB=Gs}mmn42+!Km$F}AV9h4P}%r^-q!;)uM0xT^2}9kh z`9Vwf->(T>4CqXRZT@bwg!(cpZ&g6re0Q@Ng*0LNVK?>duRb z)Y95JN^5Z~;i`_@dNXOb7K!)O`d8_qx)?KEZc8asH${O1EQ(20m6cS=GINZT)s?NV zqZaYZkz`7n?T{H~n0YlqX(q_aYvyT6b-7xTGsSvGpz{76WEX?yz*~LBkMfgZ&@N?K zJGx;|7Of12w?nfs;aLbJb?}WMr6?GSMdmA5Ru9Ao)ZYhDhGyl!00IWxfWw=i=9%zUh(0wisHiUzMrDx+hsBNTz8KnbK-oxxgNyng;QMBo z`7l6^8Fb?(T0q7~z_zuYi?$(D)c4ncq$R{r@lbb`q>vTlH~{%w93uMEf2TdI;_EeY zmmD2^q?vGeDBb|%CG3tFvl=)IiU)`5f+WnDBVpdZ|1g;HigC~-?w< zh7OSl$MMI_MZmTWL{h|GMbh63!?*ijf&*l4%_D7yCbCO<*w0VC{bnVS;S`C#k)e$@ z3>{lYgym01pA>cy2_;~m9k)a~wk(;rU7^K)XH@VK9&gC?Cf<#8|Y80XZCA4`s=Ory`Wp z!1P5*kuW@q%$Kl$9>^cU^F+%m^9@YeTzuc-xR@GW|@VsN9LKoh^PMb zg1|Gz!dw+Uf36*C18+i7s#zPRpo8$20rtn}>w9)izq$3wwI8DJNGN=Z{xr;a1|7G5 z|G9P&jrWbjf{gg0e|Q?wQCv6;16#77AC>?3xpo|lS)xt>Qa2@skDrEgBn`FPd2sZj z@n1jJj-#<}siyI(jmTd1+oVqg6Wc}&r|vMLsK31+kfDQxRegVM{pVTs!)V>rkXB@L zj&6%XQ?1N3^KIZ(?pS%>yQYh(D;nmb^+b&YEt37)wt;f6?ToXuxnJ8((<*;#*b`Ep zNE>l2tz@ilRdOV{wPk1*I#SI9LcIk?A#F|MRFE0XH0(=l)ilb34OT+x18L=tO0yVW zJW^61+S)L@A8I~78J@WGS%FMw?0p2bP*#%PqDw)7WYoItEjg9-3}YJh**0w&M*#>F zc`I#UdQv*ka$3PhaI7W5Q7|jmuj64sprrDuEr}fYY6cBW?qnM|jdFbhwV--k8r8#6 zPezop(%#vUFNQlI3m+yI5-dL`GzpAVL=*{_yC^`Bfneno+bLP{m5gfY+|jmTYUSbv zEdlkSv^b|yQ$|-OrMG8GmJHiM7KSDR5-f)lum#5QA~yN6wEQ|83p6B^7i|k=$rm%U zsdM|=RH(@);8A+G1a>L1$oUL@YU|!MZt8w`gEIf{%d~(8rIL&m50nDWMt?T+3(0yv zc{+jZy}}-!W>Um5UzVU>g+l?qge>w_NORcQ(5`_Jr~bDi6qeTJm-g zZ#OKc?mm#+$WvR%q1nojWp|$~;ABHZ(ZdEBEIQon5>&>#y-EXIH5tiDbT8*F&=#BZNPG02=T$gU)NW3sGZvV2lud(1I15p?G^Hn!3jw$f8iJiLrj{0tlnGT@I# zQplgfWMNAF#GZT=OG*BAlh$nsoQ!18fC|q5V);WunS9>$_DNT>(7!4>V<=xq4#3P> z!OXgIn+#iN99yaBowCbf!9>MVPZqq4a{LTD3}sSzM+$i|%q*tl1@>eXEG328{aUw` zaWYapvno6RLlS61nP;x~%-?iQru;{Q1PQJD8x8Ai+LtxQn@LSCNIPy&7ZTCX}M z?QPVA2kHq$_JqgOsB3=*NAh~?*0o61j26KRhnR2oj1(X%77OauP@H zxjl4Ws|NO;Y^(I0+=t`4x6_6K?oI#L6u(I5FmT(-fjgUo{tUQnq#Nkl27Gj`3Q)Vh zRVpiKQ+HdlLy-Lj9HO?ulU4f(b=4aIEMowsrisi3;{dl)Xa%#JsT`quJ90k-|_0tH5fG?&z-< zda@pks^f3R-&oJ{k$GmeEw}wYnU|-)TwqFGVNYhq>QlHqu60`rCnL?Xyu!2mA-j`1 z=ZMG!A5VU~j0*e=d<;iYd1s10DVQ&&z8SlfON_Q-OCgVgfsMyBhE00swihOzGlmZU zuzS{P-M#`~=D+_~KL{uB+R_=^E6KMpd@4Ne;4leeZ|tYJ+%=cz_O{oN)8|V`ALj#I z501H6f{2;8!n{7LwP{A2Ha>b?JodDcS*fboGX_Go9@{V7#5bshQYW=jukc@gbElz7 zRE*SA*TdJ<&DXT6c8y?bg+L*}>+OY=rVFXJPFe7bY4C`fopv%VRW*JVh7rz9ODV2( zy4a{R+UQv>K~B4^O*^r~g`tQ^?T9N-?l*J|RoBFn40XkPT}6FAx70o&*m5FJ2=N-H zTKd!Ys{5P9CW%^zcgC^nJht0qpeLNaj59M_T4K?Bb;vt8mjQcl=O5-d|l7^ zey*#fBG@7)Q1JGmrd%PTOvN~b&pC$T5Z}Wt)+vqFdA5R|Q*CQgP25B=_OXlX z;&s?ajnYAlXHNKRCAMFRiTlZiEGM-suRH?18K|%7zY_`7P4{ty`dp4x;7W*ZY=6ZgTssa8s%_G}+7C(qUq;E!o6`EKk~@7Wx)wgJW z|LkXaw%1e?>Ty%;1*~|zv+;FDKQHCyTxMRExN(8^i6_rnFP;m$5&!C^%lQP}fby`trw#QGtoK|r?z{$L=F-MLhJU{ zze(Pk@R(<;@!z{Q&j&%DT)VzVo?HiIH~K=}JWU;QFCl%One(hH`F9&ZSa>^o3NgBRA#v&v!?9S|iCAo|r2d+Mzss-)la5Kn(Q5k8t9f|18s@7v z09)#LXj{R6txtfhk*ya)+YWQ8;^x3=dUepNp#tVYFoa%BGq7k}BznGu$uHn^lJLZ!uSCTh(inS01G^a7I?x4UqakgQ!L$tWUiFj9((D{f_%2}ft|EhP7XwuSXZybq!Zw*RRI_H)jyN>x6*63&m)EXt8iW zEUwv?vc;Z8QJf0BJ}dP4P6O++9KFU>*2u|$!bb8>k*l1?HkNyt`E0{+VA*9j@t)#2 zGj3pqE76mR1N0(n(0UAj-tH)RU)q4hU~d68;!v#qM2y6)tFdpFzv;IZBso`m28T;& zMao;~6zh#+qW3tRXx|jQro-z@ytjp+ zfp#GjwsNYt*pw;1l9ep%-KpYg_EC~~VO@(r-}8lxJ#MuFW}#s+G;BX0r%!GG<1M(=L29u=n4Kkr_*WY*9$!4 zKm1o0|5?}A&Ock%aUcQPGkSRX{DTRq3IDx4w7=L;xpXTZ*i);+lc$_$Xe2eIap_l^ z@%D?q+wNzY2r6|K{UTbm{>t{LtUW}QuX3XD-rfG1eH5zZAjxBAZ~}=e3hki1;D(B- za$VlpzV*Ur<8ha1$IRKqe4@qRUTBO;E-1g0Mq8__GN5&_t}?()TX|ZUGtWFpmBdB4 zGkbWhUqYL3eF5p>)0%Cdt(P=N?1DXeV`)9^YMz(NeN-i^)k0spLx|&sJ5cR43}zcF zYu_b~7V-_KeXm+KV%JY2CNUQ5W7ASei|vc2pD|>RfkQynD4*XVe~yynS&DvW%2iG! zVzDMbPB-JSZva|TUX*~$m4M7E#ha1^NeP+4f*Xm*RF3qm7-88B8)V=SxG85+t1fdX zVf(V^XN(wR1dL~|A)`>ywek`K%B}>;UMT^TSxDe-Zita_Dg90kk3hLmzNkf>j56z4 zig_qN9D0dZq7BLWfN;Gas)?jD#}w0no3#KYZg{2mQkoxLA>`^?;_7R(o_JW%wWqC{ zam6=I$TzN5UX0+zPvo$Csq{034S=@>BVl{S2j=%GqR%xNg{H7`ZYYO3X#&nEC>Nfk zSfj7>of8|U5!2GmxaM0fSKl(% z%VG}c4)^s(jw$0EIG+kyxRpg*J2FmEgWtia%u>k}?icjD+j@GS_u%Uj}{C5d%{^g8Pl{5iRdY zDdV1{n1!Y=b6ywW+E`6)s3(x@ny}+H6zHhyn+a7GFHsEgRig_w_ZAHryM$hJp*Q1PJacZ4uM&Ovtx&Fm z)5KBDH}@G`8SYTWxHUTNUoZelvo!(H&1Z|dF)rW7bm%JH$kltnMALi;s1^Kj7jMT~ zofCxuCDXeGbn!Tb{ear95HJMn;&BQ8fZ7**0Vw16@lB2@K1b4bM>XIgUHnm1I4H_{ zTqio%9#xLVg?z4}?Qs#vk9DGb`a~P&P#a(s9T)G5znz%dxpi!A%I3HbO`os?Rt!w^ zY|YU`Umn+K{NIc<#Gk`}5;fvP_RNXwyQ*RD9iiM%+P7lo9Rx#GR}?ELKiT;@(@r*F zg`TQJ?XovJ$A1|2bLOg;`n+l5yy6JK{Kcf-0%CU%ekUDrE5^Kn0F zvNzc*HF0Fm=+Y$jHL59S?0NLiYM3MU>ZeDXxu0lM$?V92vddfg#WnxG)~-9Q$*T)j zlxk5FBnVP0vO&Os;-Cy=2-dQ};DBfZ84d(Z5HNvSK@t^=iYO~g0a*swLU075fJh+> z1IP%7G7=F<&=B(7H=|!mwO{>x{VU)P9rz_snp1H*`sl#}t)_*V0||;9hOxUZdw}C+^HgZvu&3ZM@G{LEdS+C>S;zTC;i3GQvJyAeb5s|?T7H2dw*vXZmr zFFwU1usj9}6Fvhl(^8wCI&3yV2%$E3*^7!ohgq|0dz0XW>q;Lqt&0fvXwpPuX38OC zP}3i(g~mu!K=0dTl5ieBb%V`hW{q?cWv$)`urp(ez9Zs1kOHgc3NyL;-2&el!?RlG zw}D`zp9~9}{&~!B5N!3Yxu4$oAcK^_26X{EBeh?&2ZrxBD+C0*RTBUQ=tBs6GHgJw zfrq<@$Go922qSRn%6)YpXn}GWbU^a$&3`K+&$T3$U1aLK1R}HgD zJfW?H@%$!$E(Cb+|KM)r*|bQ@&a^3kN5swJ_MsGszO-ga@FeUv4# zAsS__$ZK- zEEas{UtM%k{{|AJ^xA<3YVuP=1X0 zau;YFQb(rM|B;D>duF5g+MA!+eTTskuU1}8j5$E$g0*+`FlvSXjy-VH3^kUyAy<{? zbCxTMu$wmN>w3ZQuO5E;3R?lcXBx|9U#n3X3n}ER8s)#ABx1Rb<1d+E{lcHU`8e%! zzwY$&+eKchi?k=JrYBcEA9xTH*~40S9FpdrG8HbD9n&ekO7QrGUYz#Se@q)5>9T&p(gV~)vD}2QXtdY86eh7Ne2$X$d0idc414%sB_|lt z=AjOiKP%&N8&pO@Ue?XuNWA&?Iv{v%&Jz7C#g0%{%NILV{@l*uPg%6#@Zc5MIc+UM zr)S*d^Be95L5(n<=}-|XdB5xGmmLf+Grq)Y2_7M*pFeYxE;K1uiYW-^ch%Ntzk5+T zGa3-qKQ%lRdy~w+dboc|w)GUHM00+~$|esV&*7?1_#=S_D_|YxG`@3C_3^T_UZH%e z|3aaUmy7l0t%jD4DFg%gmxE7Rt_SIZ#vYw8dC$j!1p<0H^4@)_PWru@8iVdS}(gN?FqQnV(XiR~>|4^Ed&mN=V_mqVBc?s~414b!(`0t0!v6ZiR!R+9p?S_qSl*+IaAvBYo zRH@Wo(t!2GWueH9lOb)`K#z?xdO+Q(prUxC4EZG#w;o27Sw#pyv__nYwYp>>dRBlT zz<>1qA3BC8aG~Ob901dfR#8#96JS7Ic4YN0N`HMj+Qfi}Y2;^hfu zL$XHm$Dqbx0Nr{5o>U9rbglfPlt3oo?Q8@Qfr7&6S`Q&0$mx;TNmA%^B|%+BpQAf~ z-fTi%rZz) zVn{)8{zl7~D|I}H?%VbVdqqY<3g%d`Sm%6%gd~h90ip<`4qA=vp~_yICAgbl`(uyo zmWzB-Bckp{M9DO7pC=tWPg*tg7YQY%gp$Sf`I4KuB{!Xq_*TldVzKX{dD5`vlM2l; zE**WO1}t;Agpy8b|Jqd51)Btc`~#ibnY)ilL2joNnqd+NM2S%TGZS^v-hNN-3O62) zN92Pu1{YoOklXg+d|7Yjvdwq5@eVn>`IyqI7pJF7oy;I>PDMEx;Ont&HMqN53ocy@ zc+3I&TebW}h~rRpsF_WH%ZJD}HcUJnWrY|K;C0o?%ihhTz!gG_Amam;knV`F`4;=) zXmqv2WRQvByD(3eL8JBSsOs};hfA$ILjnv0V1C6M5(}qOt)uqLMug(ewgjLhi*j12-XHm^&QGlC7&qrp!Ese-as*Fc~4zrw3@5kfl*&s7MB z6Kqm0hX+2ge~DWuYn>_-tUW-YU{)0c1C|M*QHYA6{NGG9NDmpaOmfY|sRd4zkN3$( zwltkU$gSCDLDHa+)m>fBBQ0|%iVHbCdhwZR7&?!4d zk3WaEcg$Z<8n8eU&mk=mQj&=t<$XYKP@B>^nhP;zIFAEHM$;YU(ME4kc-hDILE#`tBw8KczO zbD!8db>c9|Y*cc3fn0;}_(VkxA*Xa-&j@L0S2j*@^5*iZs^4qH#b#+Szb>@`FqdFwU$vaDmtyV`Ft+hzCeaAofz zp|tk;RPOg7p;r7~uM;!mlM%r;`^;w>!V%mH{Ey&Qkv_!oNBIrmvlH1wEMH|th0isg Y9p%pw`71Qjb-VMT-yTzbcbC@9$I*g!=EWtCx( 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
mhEG`e=+m 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 LVbSAW+?@Xbe3Dc6 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest/test0/transitVehicles.xml.gz deleted file mode 100644 index 3156247920716188746f17cfea00a1f98f67f97a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 640 zcmV-`0)PDGqCiB}Y?Gnn{u2IEXL;L1oUwD8oN)&h`)S zaer^GH-#~y98RHNf*A{IWIct>@YtB@C=Bbm4oYG)D+8%UVZk`)FgH1V@%Sa5frSKW=Gl$o=ZxMOK-zeXoEd`y@Y#($RYAU-zVhr>L=nz`Z=4lI>fh9+(RJIR>Nim>V&Alp9z~1o21dGqR>o4fT^YWJ>Ln`_?=-& z)B-8EtOrHGD6^t|I3v_Zg$nnziqs1m$Nn7|3{O6e4h}xO4!TiyCnY4iE1}Z~I?dMo zUzBh)_&7>9{Papl({v{##8G!wCA8Z?`&s+2N@!k-?(AjJSo`XpuIJ=nGf+wow#9_)b! ad+5P_UCXxYYuuVwp#ERAeq+uA4gdffHa9o` diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml deleted file mode 100644 index f45e23b0f3b..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/config.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitNetwork.xml.gz deleted file mode 100644 index 199db02001776e43822aca5df1a186f84e459bda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97116 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�=h^wQ;ka84!G$4%MBl4Q0x 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 Y9p%pw`71Qjb-VMT-yTzbcbC@9$I*g!=EWtCx( zrYN8kU1UKSWax|Z-kIM?X0YtK``&xsz4!P2art~EIXTIbrky)|>;yF{?+;C+Q@sKE1VgWn@%z0C}?J zCRH=_evVkuoA}3$hGF7>kdW8`|7w+D8;wMx+J z=qrd~WRJ~Vo9pR+bns}F?g~%WAII7Q67Q3zA2u);_2dMX>GCnH)Ms6cxXFr)5Y@|> zWZPYg;XIGLj-s&C+M$AslZl;0Rnu7sTH;}zeS^A}E8S$|@`{^MOmF%R>?GfRZ1HHU zr^v8!%%Z0}xxRChDmzp$JQ?IL(L2`d%cxFvpiNgcB@D@__9Un5EF5SHONmX1PFO0w zap0gcBX2S|HZQ|3w~L|QUE*&zb-??fglU-X^x*Tpf%cN{yQ$|(nUHIkH$xaZ!c#|Q5{zJXd3G*E3RO zp1B^~nP3>Ou{P{yL#>L-7KcJz&K{orb-4X8W1u)?qHWOW)m^Rdgxsw^c@*!Iz1;GG z&QUQ`QD;3h@TOw0YNBuHrAGmeufwyMxbuPWs9n;*wkj>5r0V{zsSmdP+k5PqV@F21 zYM(Xw4lL^ksmp6GjI#~Mvi_}YAkj9Dq(>r?^a28gpS{ykO&y3`{CFa)vVPLn&uye* z=;Y9dj>Kh~%DjT9#IE!@J1-BnoSYLq)B6LCCeS(qUb$Z#PoMTZ;x^J^@7Leg)HU9n z9rC=frJKxcp*j78`efqv)DzO{di|Ocxmj70O^qK@PpVIx^!iP5t^TNhhmM`Be{k0U zqeOYzUr)-lbOcn`ROIT-nNTDel>F-B6WMTlH2S1Yaf36pSAMi?cca2@!`;uuri)Ha ztZNx#cO|&ab!E#sMiX(ZP#9r%H9bd4E|TQGx|(mZ)vYL#n_`YPEf*j6R^McP)qbJ2 zb>E@mF}L5E-$e55=(S~=9Fwhx#$}w^qN9h;#PB+*+=@=J4`vEC>?32$998<}oD?mUvsr z@7PMv9te){3OZTJO1Z5nlio-)eOoH5{aH*XvqY*T+HU4$($>VHuf>EP`lD4695s7) zx&^7m^6S^`q_?-SSLjH1KF!J}mJE%TL8uN29xy8KOQIWDQWVs5g~ez*eBqn~o8VxgaY1|%-O`exuBJOz zjK;?oP9RhV2fG;;7*Dd`rq6 zHQgm*w0V5t0tA~A!NipX@mCwmEsr?dq7paJBo}rI%=I{NnzZuy|E$iP4%9O8EacL3;ei+y;t_a*DIH)bwzd{OGbdBeWc$S#Qtw5B0`hpP3$swyPvVet*msiFgrTaHvEXU_26 zmLy&jJ{Pk@RFVzEv%*4x(YGbt(l$Z0H1)ZVF=9r5c6M1owp+v|BmLN`_NB9}qDL%= zt;4sV!7ROn^4j~H&+M4*D<(t5f zt|-gtDNHvD)M1k)c+RD7o8`flIl0~JgHyU9jf>WQNcn+c95*xG$-|7Ld6_Yd702;G z`rhdp;rj&Vu}6_l{P&tt=TTd#*X}jdoe!}|qZ9p--J(#L23J<>$%_41@iA6>niZde zm_O7yc!&QzCHG^8SaAp|j%CFetoRj<^Au0YH2ln}AfFY#V8x{nkK91j<+`0Fg2K>=yW2#9j;i-U$8P3Vr>?m4Y?G zj2aFGje|kwU^H_ue&hI?8u?p${Ef;&Nh=%6&ryl+oA|ej_4|Tc9_D*&qt|Na?@c6h zp6dB2KXW_XXK`r9!wc?*Z7)tfXnVm8pOfhj!{=mL+Y43CinA6y z2~vqP10BOhExP*@-k#D+Biz_fx^1!iqrLB=Vd_?QV7DSB$MT-CTlDr!s68Z9qIqG? z+pPlApKJ7!%a%lyOT4G*U)++kehE{vxvD74o)^twB3-r=CQs{P=Dgj!1QMb}sKorJ z4rOg3&%WA*7YQ{%&iPBGCVQ)+tjUc+6yLQxx`xV}(upGM;TF7XMJdMuo2*Up`_vWa zqZd&2Z)|Oj=~BdTOOe_oO@hh~v&p%y}D6~<8v~;B~Qq4o+ zGhx5jg7DJ7Rc6yFCuO3Sghtk!m%OMJDieJKi_u7Y5E?nT_Lxj`16Cy=mBMM;oevb< zMmCsr7SnR9BLyYWKc}ExkDd#K;iYvXXb2TDOn&w`b@D>Z`7EClCHMW^AJn0)Y>&15 zK|1dHa|IkNI*oLcWBmO`pVemiuT`RNjR@RFVh?ZO(REj(Z#^5hkH{YWoJZHc3qI|v z8&(< zjKcQ2YMG6)a<9z!l>3WspUtQ1 zf4-gh^%rc0ry;r%Jq-}0mLgFRMCQ`~73q@-i}-19+AyC6?|t+%=%`~~i}-1LrNU2R zraJsIpd~r?pLSe$r0AX`m9(Br_8l1JCL4*qrpbRab_=-0UDkC~cA3XX@7FYGP`H5N z9#Zsx;z<-odDU(m=Z%-n)r9Z3psY09Bz}N5bavMF+f4SFBK71@h|K3y&3RPFy;=M~ zNv!~icuVS=(-kw-)~)F*Ui?x-gyb6b@K|9u?V5(d1FZH}e)>fU6^aa7gs%+6_4YTg zf01Ig`MDL|lA@!ZQzhrVs{eX{%&D#$`_XS#J=))&I-Z#rHAoxng%2G*ui=vqpSN9E zeWA5(O_h&fdN~Z!%Qn68tvuR$TNT)?RPDtVz_ijT&u*n<&$R&Nk5)PM#%ez8eJXM= zuh@T70F{a?65lKID8q8?C=Z@kh)qH;rwq(SOSM`gx?!FLWfSke8&&--CS>HUlr)_s^7Buyk0{A zBj(z*;RLK*C7pR-?TW-OB$go&uU(Pq%qJBR@!Iu-6m#t=po3PYl$9|1eJ$d(>sKng zc1=)+TXswiM4P-T7f`|^85$_Lg67%1Xl;2+U0(L`Mq;uZN_ea9GjV+J(>gS-YNGY& z%Y$rv=b~elyh=u?YX>1CWB+z&UE_-Qxfn@}%OA=)M+)a0bt`CZJIU~6XptqwD86C@ zX}Ry&icB}=_ibb*6YJT-D_q!$+A;&126hPQ+Jw|y3B4Lw6COQ(Vd&JA5)jWt%i*#eBn@e1TsqxrGm^sZQk$3}(QWu+T7IYSu zN+T6*V@cl&8K1q}Kks=uIPgp-ZAuNkqUy(BpxGpOgkCqxmDRAae{!qq@ANkbpWZ9_GQ_^_aMm+746rM z&mFb?oY3AE`2bomdx5?wNq)S&U?pB%f!|}vR$KvmAyH=tg)8k*ZOnvpTIM^p9i3?< zm8lLo)pp&#P6SMUGHs9y@~7nVvuOH-jr5W!xwu`^4mo2{{Y(2>_6~%Qf6cR-c7J48 zM}AjV_Pix!K*w%<&baBqdPA#G(>o)K+^M|gR0pZX%C45WgA>}eepB4p4nA(vP2HKc zafu0x!STA1cVq8L#wIf4%Ki1W#rOD6g-q@1nQm?Ch|RW3Bj3%d*B_`F>>UYs{LDR| zrLJtK=vC^_U`O4DT>;Lf-UH_39MZ%`9sRM+cnvq>c~sHXiUB-IzN)5?3CV z7V#`Kul(k8GdB4JY)uxWOjul(oV4kk+BS8?dc0Pmc)Do7W43qsUA0c#jHL9YQtzkX z#86euiDd256Ry698ROHsH{AUVDCWYaGpuLJ=4wxKayl_^m_y) z9?q-;8qR~W;CE5UfmTL>La+}lB29Mk`lh;WU!YAcagpOlqWk(E47;JOn`rl5#EP%H z?egRN@Zf>51MF=B>qzvSv)wtmcPtmFW%JOK3Y`k{xqPf4CIothV=oB~kvp($#3V^3ckhl-xPH9hQrb*gck^!agxjm@DH- z-h)vO?TnsP6=hmk+sishZ`=l6-P=N(*f2<&v`Vci%@sA!$t!ha6$#l<)n(ALg&`9h56aIO>E#6)ZZ*OW1@aeT2!9}U{%Mzlyh zt%?Wjr-b&tg$XcdZ+oKeqmdU6T2`V24cfUDFlaaFLvO76IPsvp`w^@Jl6OCPb++nO ze(=voTzJ?=Wu@~l@#f*4t;J>j*M91{%A57hV*Igzpn}eXUVUin)D;&Jdgm?8)7!vt zJ8G=#$ow_;N?pP$jXoamcZ=h)A`ZkhEfdPyaw{6=mEk-``%ig4jfgN)=y&*8z3f~} zbiH&+(A(0|1q;4$`05PrpchRB7W@>S9}+%Lok~sO5X?^7JZw@^NsN&uTT@ zynA%ca+gKAL6ETlYXjZ1Yi8FJJU(ADr{)=if;G<}6sjqLP`IY#@p&}A2bDRYUqKfR zXCFrL;={Et!*ecwbXX}M>REVx?ql?GD#zgm9CqVy`teyb$@4fJsa*r1D-PXiHSfdP zV!0Yx&&=T~Imr(x5=w0m`zU9+}plS@}M5E5xxYK^eC4Jzg319;ciwbkCP~sx30XX2(;##)-#QVW;8)MU>H; zCfE5z5;~8{k=UW!2m5qp5f|4Q;ZYMR8Y27+^WmGk zwsEt_1Qr#LNLmV7G+t2*TZ$PK600&LJBxR2y4VaIJfC$7y>`Ryz#I5$u_SF(&G~~X zVYQt+8?E-2-;l8BEdEIWYO6ePq4^o%bD}WZTna7o53hk&^j&l7$cXlI#SGo`kkYWI zM9X>cTc`?UN2slJ#}>4Th>C<&L`s^CztsZz=qz4cLqqm(!HB@7>x%SIPH~FwG@q`a zA3-`X2R3iE254s5r=q-eFUj8On$j>9S0d4UKWO`_1#t`;gBcYTU5K!(xS)ntxP;jrhNM}j?Pmk;@Lm!4Z} zQd8;r;BaE*krm~%!ggIZ{*Rw70mTzgbR?6}fNt#GI3oXJW4Gmq{G-NhixK&u z#_kOx^4d+^>qq4M?^zPNUmO**B6dH>lu@OAF~BQX51pU6Z%cY;K^R>y zderAItOQDTZ5t*VPL2&q1RQA3dJvT$wFsi~2~zwJMI=ad{Fe3LY=TstIT%?No^LvH z+)}ai0*oeaf=XL;@KU>gB z^{614@C=yybS0D!)kklmZoHV@dlD^kWY`pu}xyG<_aHud}CU$?p6!AD&6G znvhAfJM$%)NiM-DR4(z9CGEtX4oKF-RoEF&biX!ag+3v!6#*KgUm z+cct>twvao{RvO6Qhw~L2=*s@y&8epMg@D4=u0gr^VO)c#iY6U?sE{@f`TWE3WAd8 zCYF>1YE)q{X-m7AtX54xS%14?rKR{p+=PvlNRK=&rN6x37#-6V7p2WwxpP-Q5D3bh57FD z5FCyLlS~Q}uF@kdDQnfJ%3{)@eD`??ZO4LrObU#z(yv)ktkkILV$yT@?(-2Gjt6I( z6zsW5e`rbBu0~xfCN05tpP$fnJb1#SAm}RnsU>Bv8g+@7v?Sks0fNJc;D5;P@GaB7 z?-ZL|VDG89JT=00A^8#ty_nIs&)A;-V;0wLfs&D}^jMAFUE-t5 z`6Fr^*hr0}#l82$M{W6iGYJ1@EUv$|u!fYU*PU~X=qwh^P;P-Os+>da!|QD?@{U%V z4QNS_Vmt*qg0hdZugNfN^k`>RUrCKylQkGwRj)}qXdIirMEQH8kA+9Y*ly3Y-pF~w z)_f(Ja|z!YO>VzxZ*GVZeXC#sz9EX{+zrh+$6}VeTZPxXSM1RoemWGU+jeC%-CjbI z=9UHWxlh61dI2TOQHceiJGEeoxdzSG^1?!PlQ5H;qJ$Kx{Q#={s4$w|zvnD$S_+#` zln`CD-yABCMiuNmg{F4}_C^IFOz$Xh8|*X0{dYL5Tefb|g+kE{89$kr>9;`QXk>-kEce2YSz7G+yuB*< z*Jq`%>+{n!>5p%0iqw(n$U|t)}1=lJZSnFiN@jgGRhI!1+QkSqg zYY$x*zJadyZ4>k0LKTd*s)L&g?yYainkI=`EN8VC&uWnrf?GV5MyB%%$v$9*L2^iF zbOC2BUy#;HdWX=c2`9c19t(J>n2*JQb+f20F%f1{*rA4>;_`m*G1A1KXdwx=G zahL24uW?l!38UB8NdI>t@hPT3Qvvxh+8I>?e1WC0@AP z*Vtk&Hq{(4)N?s+*FB373wIBFCRkRA(-Q&p({}w;8MFJ^hx@yKnwp=M zI~`J&k@Ie{@uZ7wqE>l;;U|PhM$|+AO(rm8>Uz&~_ZKFLk*Yz?-}?o1Ry7jZd%fn=*2-b@?qe_7z$4ez%;8 z?WHSzSIj!98K={xMb4I=ak2PmDx%iAr*GZewijCIu=sici?7CyS$*qLL2(xpLqoLD zv*1H<@iEZxuZ+UWGW$zPuqjN-&yKRwLDREPjud zW$Ndd%d!Cl7@9;W5g1f%SQJI#fIJ%lhb)PbN4W%~5|u|gT>EiFR2`YQFtayA)qX%K zdjlkvV-Z~H01(;doQkZ6v3HiL!1`$79EL%4f&-VW#6d89TmF0vz9v0LS;?KX-iV@A%~Sc7jol z9N#ELj&Bqr$2W?R;~VFLkvL zMgn$xBLO?U6R_hOx{t@{SMmSg_(u1M{L_Dkv7Z_@5039Yk7ow{A1lBWpSk2fUWHot zb2!V#B;w@Rx(D^S^@j9GRq0{cWSbNODJXD2fhr2x<|D}hNgE(YL4m`DWIRUbEANP| zi4TmpwL$(7yJ6Cbptt;^o*y}_i1cT-MUiPvV01rDy4@CCQx<4(bA!A#hhfx;9I(yN zMWG1tH1-~O%L^8vlN(jf#jweJA8+Ky~qn+)XXfMNI=!ugx?WpmUDX6uGINTa#MUdxCus!X%rvml+Y+ny%IWjDvaCA&S#^rCGwZz1!z!Qz1yn^+IGNS#6oqQQE<~|J zEx_w!eNbn39oT^!2&@9)s6ev^II?z1u*&Xcl|4ICmg^yDI6(>elbEA0i4VRE$s(=f@k0N#l9YDmSUh(z6M$S^5+j%Aos zL59ie*f6PyL`6|pr4W~IWEv**k%~hMsa9db zCc{>wCBwX7gw7x#bjI*LB(D4n)IgTludy3&!)tF~ORR`G(-J!a34vSsN@U*fm#SZo zCH7Nw*b+Mf3BlFEBmJCPx#tsLWW^0|2?JsU9LAvtRs*6Ty4&E@@EkLeko#EU2iTx2 z<)4q7$DkhT8f{fFmG-RAsC5rB3D_TI1_7S7p-I)IX+}t7rX%ZwX^B)+) z(^$%~OH>2u*XQPGWelea40Fu;B822x20fA>`G9~28A|K z>;i>{Ykt=J&KC&$QF{b|Km0NHLm_hp6M_*ipHg2SIK05!hsut{O8+GU1bGlyTEq#^A|-$paRa16ib=)Uieu1Y9_X15a(gJWXfJTtR6N65^MwlQ=48otFIq)m| z2{Qdu5@EY$UM7Z3KT%6jqF4+XkhF&9vC14;iTaL!O+SMeHllR^nSR_6)a2z&m`b;9&ru4K5Fz) zHSqYHW+n%AfRo2+^8~vvx~iuU^3Vl9r+`sfitZNej`Jv=2l{;sVx}B#pkL z5H_R|Lm~$o{*t~kVtz?WFsy_PeKQ?-@Z7S-(GxqA$NZA`v8s~^8>wFR8TwHu5%4(y z-_%k7>3l+p^ijb#!-9=SihhAR!B7+IKmia6PBaZIC+!ZK()RKm>TDfG z71cT(dA0#t1MpF8BQm=re9{qc0^pZ>1daDl9N46vsHDm4H%k;_nq|2&aI8J z4!fMqfV&zUXzcG!86N1FV`#gx?tV|i+iJnVCdZZaw4KvOj{L~@uFus?rPQEwFmiI- zuCK~Y%WCk>^svERM$?#-s)2*}X7{*l*6)tkn;j^Mo_kToL3|S^EI^?LimD5supY*f z4(-~M?2QdL+=4?}9NMF>Fm0~mi^cB|O5}H8{kmI{nLp%lqa{s~co%XJemg|QT<16b z;57Uc2EgoNE`p4=W~+>R`%*q9SjO9^RcP5HA}kc~>Xsxcaej#c{4TI1cL5Aql~A5j zCrg{whYxe+?jYS0iS3c@iV_lw)3Q7quS;m--?ZcVbp>PWRlm-&qU0K_r~3vua>u0> zIOpH1UA>n?Z#}H|K#nqjKmpVsOu*tgny=-dIbrbK#;mv-jRiMV%w z6ls4FQ|k@WJ(q@?ZQ0=*n7}!H)lF-C`~k)+8-JXQ+YT2dgfN|oFGA>&cb~2~^y!*q zpRS3)*T7&tt%^qOCol*axiblP{?8H0c?K?mM=mRYM()>m6FhQ{uSb*V@%0gPazDKP zKp75sbh!2hz>nO0d^nLm>z!cLlk&MKn&iFUjI>PNJ|Krj; zvL(mui(^wk^Vj5*_6WW;0%%A;5*NZx5^-KM&MW(zHw;c+oI>xF&uRofnc&byXcDbE`ctKuWHd2ETxAB&%>iZXbS z7ULM$Vn-43s20KrHmR}YB2w%-ASAKJjxEQNe0rh_{AALv<1ht>sV*3Ll$(1Nff
mhEG`e=+m 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 LVbSAW+?@Xbe3Dc6 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest/test0/transitVehicles.xml.gz deleted file mode 100644 index 3156247920716188746f17cfea00a1f98f67f97a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 640 zcmV-`0)PDGqCiB}Y?Gnn{u2IEXL;L1oUwD8oN)&h`)S zaer^GH-#~y98RHNf*A{IWIct>@YtB@C=Bbm4oYG)D+8%UVZk`)FgH1V@%Sa5frSKW=Gl$o=ZxMOK-zeXoEd`y@Y#($RYAU-zVhr>L=nz`Z=4lI>fh9+(RJIR>Nim>V&Alp9z~1o21dGqR>o4fT^YWJ>Ln`_?=-& z)B-8EtOrHGD6^t|I3v_Zg$nnziqs1m$Nn7|3{O6e4h}xO4!TiyCnY4iE1}Z~I?dMo zUzBh)_&7>9{Papl({v{##8G!wCA8Z?`&s+2N@!k-?(AjJSo`XpuIJ=nGf+wow#9_)b! ad+5P_UCXxYYuuVwp#ERAeq+uA4gdffHa9o` diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml deleted file mode 100644 index f45e23b0f3b..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/config.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml deleted file mode 100644 index 89d2e617067..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/trainNetwork.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - 999 - - - - - 2 - - - - - 999 - - - - - 5 - - - - - 999 - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml deleted file mode 100644 index 05db21897f8..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitSchedule.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml deleted file mode 100644 index 790b6b25dae..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest/test0/transitVehicles.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml deleted file mode 100644 index 8a9d31f4497..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest/testRunRailsim/config.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 5cc84877ad7735d207bfa211c50d6fab65da2917 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 18 Aug 2023 13:35:29 +0200 Subject: [PATCH 079/258] Adjust cross integration test - Map crossing with railsimResourceId in the network. - Adapt the transit schedule. --- .../integration/10_cross/trainNetwork.xml | 93 ++++++++++++------- .../integration/10_cross/transitSchedule.xml | 36 +++---- .../integration/10_cross/transitVehicles.xml | 7 +- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml index b535f2608d8..7e93463b353 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml @@ -8,77 +8,102 @@ - + + + - + - + - + - + + + - + + + - + - + - + + + - - - + - + + + + - + + + c1 + - + - 2 + c1 - + + + - + - + - + - 2 + c1 - + + + c1 + + + + + - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml index d1c6aa9a2fc..c9add0e102c 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml @@ -4,27 +4,29 @@ - + - + - + - + rail - - + + - - - + + + + + @@ -35,18 +37,20 @@ rail - - + + - - - - + + + + + + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml index c86eff16d18..3466080caaa 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml @@ -7,7 +7,6 @@ 5.0 serial 5.0 - 10.0 @@ -23,7 +22,7 @@ - - + + - \ No newline at end of file + From 614951621f7c74d307a18588e438ac2027df8a77 Mon Sep 17 00:00:00 2001 From: u234825 Date: Fri, 18 Aug 2023 15:29:50 +0200 Subject: [PATCH 080/258] make some parameters configurable via the config group; add option to update position events every x time steps --- .../railsim/config/RailsimConfigGroup.java | 10 +++ .../railsim/qsimengine/RailsimEngine.java | 62 +++++++++---------- .../integration/RailsimIntegrationTest.java | 2 +- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index 060deba4b0b..5b0c6dbea34 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -43,6 +43,14 @@ public class RailsimConfigGroup extends ReflectiveConfigGroup { @Parameter @Comment("Maximum Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link.") public double maxDecelerationGlobalDefault = 0.5; + + @Parameter + @Comment("Time interval in seconds a train has to wait until trying again to request a track reservation if the track was blocked by another train.") + public double pollInterval = 10; + + @Parameter + @Comment("Maximum time interval in seconds which is used to update the train position update events.") + public double trainPositionMaximumUpdateInterval = 10.; public RailsimConfigGroup() { super(GROUP_NAME); @@ -61,4 +69,6 @@ protected void checkConsistency(Config config) { } } } + + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 1ff27b9bdfa..9bdce5d7827 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -1,13 +1,20 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; -import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; -import ch.sbb.matsim.contrib.railsim.events.RailsimDetourEvent; -import ch.sbb.matsim.contrib.railsim.events.RailsimTrainLeavesLinkEvent; -import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.TrainDisposition; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.stream.Collectors; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.events.*; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.LinkEnterEvent; +import org.matsim.api.core.v01.events.LinkLeaveEvent; +import org.matsim.api.core.v01.events.PersonEntersVehicleEvent; +import org.matsim.api.core.v01.events.VehicleEntersTrafficEvent; import org.matsim.api.core.v01.network.Link; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.mobsim.framework.MobsimDriverAgent; @@ -16,8 +23,10 @@ import org.matsim.pt.transitSchedule.api.TransitStopFacility; import org.matsim.vehicles.VehicleType; -import java.util.*; -import java.util.stream.Collectors; +import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; +import ch.sbb.matsim.contrib.railsim.events.RailsimDetourEvent; +import ch.sbb.matsim.contrib.railsim.events.RailsimTrainLeavesLinkEvent; +import ch.sbb.matsim.contrib.railsim.qsimengine.disposition.TrainDisposition; /** * Engine to simulate train movement. @@ -26,11 +35,6 @@ final class RailsimEngine implements Steppable { private static final Logger log = LogManager.getLogger(RailsimEngine.class); - /** - * If trains need to wait, they will check every x seconds if they can proceed. - */ - private static final double POLL_INTERVAL = 10; - private final EventsManager eventsManager; private final RailsimConfigGroup config; @@ -67,6 +71,15 @@ public void doSimStep(double time) { update = updateQueue.peek(); } + + if (time % config.trainPositionMaximumUpdateInterval == 0.) { + // Update position events for all trains + for (TrainState train : activeTrains) { + if (train.timestamp < time) + updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); + } + } + } /** @@ -165,7 +178,7 @@ private void blockTrack(double time, UpdateEvent event) { decideTargetSpeed(event, state); - event.checkReservation = time + POLL_INTERVAL; + event.checkReservation = time + config.pollInterval; decideNextUpdate(event); } else { @@ -189,7 +202,7 @@ private void checkTrackReservation(double time, UpdateEvent event) { if (allBlocked) event.checkReservation = -1; else { - event.checkReservation = time + POLL_INTERVAL; + event.checkReservation = time + config.pollInterval; } // Train already waits at the end of previous link @@ -208,11 +221,11 @@ private void checkTrackReservation(double time, UpdateEvent event) { } else { - event.checkReservation = time + POLL_INTERVAL; + event.checkReservation = time + config.pollInterval; // If train is already standing still and waiting, there is no update needed. if (event.waitingForLink) { - event.plannedTime = time + POLL_INTERVAL; + event.plannedTime = time + config.pollInterval; } else { decideNextUpdate(event); } @@ -260,7 +273,7 @@ private void updateDeparture(double time, UpdateEvent event) { } else { // vehicle will wait and call departure again - event.plannedTime += POLL_INTERVAL; + event.plannedTime += config.pollInterval; } } @@ -388,7 +401,7 @@ private void enterLink(double time, UpdateEvent event) { if (!currentLink.isBlockedBy(state.driver)) { event.waitingForLink = true; event.type = UpdateEvent.Type.WAIT_FOR_RESERVATION; - event.plannedTime = time + POLL_INTERVAL; + event.plannedTime = time + config.pollInterval; return; } } @@ -720,19 +733,6 @@ else if (!resources.isBlockedBy(link, state.driver)) } - /** - * Debug helper function to create breakpoints. - */ - private static boolean debug(TrainState state) { - if (state.driver.getId().toString().equals("pt_Expresszug_GE_BE_train_3_train_Expresszug_GE_BE") && state.routeIdx > 550) - log.info("debug"); - - if (state.driver.getVehicle().getId().toString().equals("regio5")) - log.info("debug"); - - return true; - } - /** * Allowed speed for the train. */ diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index c910d5e08a8..380d4ac02e5 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -94,7 +94,7 @@ private EventsCollector runSimulation(File scenarioDir, Consumer f) { Config config = ConfigUtils.loadConfig(new File(scenarioDir, "config.xml").toString()); config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setDumpDataAtEnd(false); + config.controler().setDumpDataAtEnd(true); config.controler().setCreateGraphs(false); config.controler().setLastIteration(0); From 342271c1664d4a8f934d993f7c0bbf8444cc5205 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 18 Aug 2023 17:49:40 +0200 Subject: [PATCH 081/258] Documentation and cosmetics - Add current railsim attributes and examples to specification markdowns. - Rename railsimMaxAcceleration to railsimAcceleration. - Some typos, remove unused imports and comments. - Extract method for position update of all train. --- contribs/railsim/docs/events-specification.md | 2 +- .../railsim/docs/network-specification.md | 132 ++++++++++++++---- contribs/railsim/docs/train-specification.md | 18 +-- .../matsim/contrib/railsim/RailsimUtils.java | 5 +- .../contrib/railsim/RunRailsimExample.java | 1 - .../railsim/config/RailsimConfigGroup.java | 10 +- .../events/RailsimTrainStateEvent.java | 2 - .../railsim/qsimengine/RailsimEngine.java | 25 ++-- .../railsim/qsimengine/RailsimQSimModule.java | 2 - .../railsim/qsimengine/TrainState.java | 2 - .../railsim/qsimengine/UpdateEvent.java | 4 +- .../src/test/resources/trainVehicleTypes.xml | 8 +- .../integration/0_simple/transitVehicles.xml | 2 +- .../11_mesoStation/transitVehicles.xml | 15 +- .../12_mesoStation2/transitVehicles.xml | 15 +- .../7_trainFollowing/transitVehicles.xml | 9 +- .../8_microStation/transitVehicles.xml | 7 +- .../9_microStation2/transitVehicles.xml | 7 +- 18 files changed, 168 insertions(+), 98 deletions(-) diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index 44c6851a477..8a5cb670752 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -13,7 +13,7 @@ that could include information about the new state of the link (or even the trac Attributes: -- `state`: `free`, `reserved`, or `blocked` +- `state`: `free` or `blocked` - `vehicleId`: if `state=reserved|blocked`, the id of the vehicle blocking or reserving this link - `track`: a number (0-based or 1-based?) if the link has multiple tracks diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index 912afe9f047..e37730d4d6f 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -3,28 +3,39 @@ ## Introduction As trains interact differently with links than regular cars, the typical attributes like `capacity`, `lanes` and even -`freespeed` might not be suitable for describing rail infrastructure. +`freespeed` are not suitable for describing rail infrastructure. -railsim uses custom link and node attributes to describe the essential parts of the rail infrastructure. +railsim uses custom link attributes to describe the essential parts of the rail infrastructure. This document specifies these custom attributes. -railsim supports the microscopic modelling of tracks, where each link represents a single track, and -a mesoscopic level of modelling, where a link may represent multiple tracks. - ## Specification -We try to use the prefix `railsim` where it is appropriate. +We use the prefix `railsim` where it is appropriate. ### Link Attributes -#### grade +#### railsimGrade TODO #### railsimTrainCapacity -The number of trains that can be on this link at the same time. -If the attribute is not provided, a default of 1 is used. +The number of trains that can be on this link at the same time. If the attribute is not provided, a default of 1 is +used. +railsim supports the microscopic modelling of tracks, where each link represents a single track (`railsimCapacity` = 1 +or default), and a mesoscopic level of modelling, where a link may represent multiple tracks (`railsimCapacity` > 1). + +Example: + +```xml + + + + 3 + + + +``` #### railsimResourceId @@ -37,20 +48,42 @@ even on small roads by going very slow and near the edge, but trains cannot. This can also be used to model intersections as one resource, which will restrict crossing trains. -The train capacity will be derived as the minimum of all included links of this resource. +The train capacity will be derived as the minimum of all included links of this resource. Links that have no resource id will be handled as individual resource. -#### railsimMaxSpeed - -TODO - #### railsimMinimumTime The minimum time ("minimum train headway time") for the switch at the end of the link (toNode). If no link attribute is provided, a default of 0 is used. +#### railsimEntry + +Entry link of a station, triggers re-routing and serves as origin. + +#### railsimExit + +Exit link of a station, denotes a destination in re-routing. + +Example: + +```xml + + + + true + + + +``` + +#### railsimMaxSpeed + +TODO + #### railsimSpeed_ + vehicle type +TODO + The vehicle-specific freespeed on this link. Please note that the actual vehicle-type must be used as part of the attribute name, see example. @@ -71,27 +104,24 @@ Example: Currently none. -(ik, mu): We assume that blocked nodes are somehow identified by the currently blocked links? Otherwise, we need node -states to avoid collisions. Do we really want that? - -## Examples +## Microscopic Examples ### Single track with contraflow +Default value of `railsimCapacity` sets an own railsimResourceId for each track. + ```xml - 1 AB - 1 AB @@ -100,19 +130,73 @@ states to avoid collisions. Do we really want that? ### Two tracks, each with a single direction -TODO +Default value of `railsimResourceId` sets an own railsimResourceId for each track. + +```xml + + + + + + + +``` ### Three tracks, with contraflow in the middle track -(ik, mu): Default case, all tracks are open for both directions. For more complex track layout we would tend to use the -microscopic modelling approach. +```xml + + + + + + + AB + + + + + AB + + + + + +``` ### Two tracks that intersect each other If two tracks cross each other, e.g. like in the form of the letter `X` or a plus `+`, a train driving in one direction effectively also blocks the intersecting tracks, even if they only share a common node, but not a common link. -There should be no additional link- or node-attributes necessary. The simulation should block the node in the case of +There should be no additional link- or node-attributes necessary. The simulation should block the node in the case of `railsimCapacity = 1`, but not if the capacity is larger than 1. If the node is blocked, no other trains must be able to cross this node/intersection. +## Mesoscopic Examples + +### Section with a capacity of 2 + +TODO + +### Station with 5 platforms + +TODO + +## Fixed vs. Moving Block + +| | Microscopic Scale | Mesoscopic Scale | +|---------------|--------------------------------------|--------------------------------------| +| Moving Block | Model tracks consisting of short | Model tracks consisting of short | +| | links without resource id. | links of capacity greater than 1 | +| | | without resource id. | +| ------------- | ------------------------------------ | ------------------------------------ | +| Fixed Block | Model blocks of links with capacity | Not supported, as it adds no value; | +| | 1 and identical resource id. | simulation results will be nonsense | +| ------------- | ------------------------------------ | ------------------------------------ | diff --git a/contribs/railsim/docs/train-specification.md b/contribs/railsim/docs/train-specification.md index 7a2dac70d1b..63082005d16 100644 --- a/contribs/railsim/docs/train-specification.md +++ b/contribs/railsim/docs/train-specification.md @@ -12,19 +12,19 @@ type in MATSim. TODO, e.g. length and acceleration of a train. -#### maxAcceleration +#### railsimAcceleration The vehicle-specific acceleration. Unit: meters per square-seconds \[m/s²] -(ik, mu): Suggestion, maybe just `acceleration`? Do we always accelerate at maximum speed? +#### railsimDeceleration -#### maxDeceleration +TODO -The vehicle-specific maximum deceleration for emergency braking ("bremsweg"). Unit: meters per square-seconds \[m/s²] +The typical vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] -#### deceleration +#### railsimMaxDeceleration -The typical vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] +The vehicle-specific maximum deceleration for emergency braking. Unit: meters per square-seconds \[m/s²] ## Examples @@ -32,9 +32,9 @@ The typical vehicle-specific deceleration. Unit: meters per square-seconds \[m/s - 0.5 - 0.5 - 0.7 + 0.4 + 0.4 + 0.5 diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index ff5984ba141..5ed9ab6db94 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -25,8 +25,9 @@ public final class RailsimUtils { public static final String LINK_ATTRIBUTE_MAX_SPEED = "railsimMaxSpeed"; public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "railsimMinimumTime"; // vehicle + public static final String VEHICLE_ATTRIBUTE_ACCELERATION = "railsimAcceleration"; + public static final String VEHICLE_ATTRIBUTE_DECELERATION = "railsimDeceleration"; public static final String VEHICLE_ATTRIBUTE_MAX_DECELERATION = "railsimMaxDeceleration"; - public static final String VEHICLE_ATTRIBUTE_MAX_ACCELERATION = "railsimMaxAcceleration"; private RailsimUtils() { } @@ -77,7 +78,7 @@ public static double getTrainDeceleration(VehicleType vehicle, RailsimConfigGrou */ public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { double acceleration = railsimConfigGroup.accelerationGlobalDefault; - Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_ACCELERATION); + Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_ACCELERATION); return attr != null ? (double) attr : acceleration; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java index 0d954193140..de67902943f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java @@ -20,7 +20,6 @@ package ch.sbb.matsim.contrib.railsim; import ch.sbb.matsim.contrib.railsim.qsimengine.RailsimQSimModule; -import ch.sbb.matsim.routing.pt.raptor.SwissRailRaptorModule; import org.matsim.api.core.v01.Scenario; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index 5b0c6dbea34..f6ecfd318a5 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -37,17 +37,17 @@ public class RailsimConfigGroup extends ReflectiveConfigGroup { public String railNetworkModes = "rail"; @Parameter - @Comment("Global acceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_ACCELERATION + ");" + " used to compute the train velocity per link.") + @Comment("Global acceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_ACCELERATION + ");" + " used to compute the train velocity per link.") public double accelerationGlobalDefault = 0.5; @Parameter @Comment("Maximum Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link.") public double maxDecelerationGlobalDefault = 0.5; - + @Parameter @Comment("Time interval in seconds a train has to wait until trying again to request a track reservation if the track was blocked by another train.") public double pollInterval = 10; - + @Parameter @Comment("Maximum time interval in seconds which is used to update the train position update events.") public double trainPositionMaximumUpdateInterval = 10.; @@ -69,6 +69,6 @@ protected void checkConsistency(Config config) { } } } - - + + } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java index a373a45f7a5..172e43fd1ad 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -7,8 +7,6 @@ import org.matsim.api.core.v01.network.Link; import org.matsim.vehicles.Vehicle; -import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.Map; /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 9bdce5d7827..c1865437f2a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -71,15 +71,10 @@ public void doSimStep(double time) { update = updateQueue.peek(); } - + if (time % config.trainPositionMaximumUpdateInterval == 0.) { - // Update position events for all trains - for (TrainState train : activeTrains) { - if (train.timestamp < time) - updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); - } + updateAllPositions(time); } - } /** @@ -88,12 +83,10 @@ public void doSimStep(double time) { public void updateAllStates(double time) { // Process all waiting events first + // TODO: Consider potential duplication of position update events here, if time matches trainPositionMaximumUpdateInterval. doSimStep(time); - for (TrainState train : activeTrains) { - if (train.timestamp < time) - updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); - } + updateAllPositions(time); } /** @@ -125,10 +118,16 @@ public boolean handleDeparture(double now, MobsimDriverAgent agent, Id lin return true; } + private void updateAllPositions(double time) { + for (TrainState train : activeTrains) { + if (train.timestamp < time) + updateState(time, new UpdateEvent(train, UpdateEvent.Type.POSITION)); + } + } + private void createEvent(Event event) { // Because of the 1s update interval, events need to be rounded to the current simulation step event.setTime(Math.ceil(event.getTime())); -// System.out.println(event.getTime()); this.eventsManager.processEvent(event); } @@ -562,8 +561,6 @@ private void decideNextUpdate(UpdateEvent event) { TrainState state = event.state; RailLink currentLink = resources.getLink(state.headLink); -// assert debug(state); - // (1) max speed reached double accelDist = Double.POSITIVE_INFINITY; if (state.acceleration > 0 && FuzzyUtils.greaterThan(state.targetSpeed, state.speed)) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java index c3fe61f4939..ac17bfece61 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java @@ -26,9 +26,7 @@ import org.matsim.core.mobsim.qsim.AbstractQSimModule; import org.matsim.core.mobsim.qsim.components.QSimComponentsConfig; import org.matsim.core.mobsim.qsim.components.QSimComponentsConfigurator; -import org.matsim.core.mobsim.qsim.pt.DefaultTransitDriverAgentFactory; import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentFactory; -import org.matsim.core.mobsim.qsim.pt.TransitEngineModule; public class RailsimQSimModule extends AbstractQSimModule implements QSimComponentsConfigurator { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index 22665e23428..e12bbdbcbbb 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -4,12 +4,10 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; import org.matsim.core.mobsim.framework.MobsimDriverAgent; -import org.matsim.core.mobsim.qsim.pt.TransitDriverAgent; import org.matsim.pt.transitSchedule.api.TransitStopFacility; import javax.annotation.Nullable; import java.util.List; -import java.util.Set; /** * Stores the mutable current state of a train. diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index ecebe3d6fdc..82a547cd72d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -22,7 +22,7 @@ final class UpdateEvent implements Comparable { boolean waitingForLink; /** - * Stores a link that is should to be released. + * Stores a link that is to be released. */ final RailLink unblockLink; @@ -65,7 +65,7 @@ public int hashCode() { } /** - * This train currently waits for an reservation for blocked tracks. + * This train currently waits for a reservation for blocked tracks. */ boolean isAwaitingReservation() { return checkReservation >= 0; diff --git a/contribs/railsim/src/test/resources/trainVehicleTypes.xml b/contribs/railsim/src/test/resources/trainVehicleTypes.xml index 667f435d87f..2f0fdb605e7 100644 --- a/contribs/railsim/src/test/resources/trainVehicleTypes.xml +++ b/contribs/railsim/src/test/resources/trainVehicleTypes.xml @@ -4,7 +4,7 @@ - 0.7 + 0.7 0.7 @@ -16,7 +16,7 @@ - 0.7 + 0.7 0.7 @@ -27,7 +27,7 @@ - 0.5 + 0.5 0.5 @@ -38,7 +38,7 @@ - 0.2 + 0.2 0.2 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml index dce684b4165..0298a829e01 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml @@ -8,7 +8,7 @@ 5.0 serial 5.0 - 0.5 + 0.5 0.5 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml index 103d0251dad..1d1ea9bd4cf 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml @@ -7,7 +7,6 @@ 5.0 serial 5.0 - 1.0 @@ -23,11 +22,11 @@ - - - - - - + + + + + - \ No newline at end of file + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml index 103d0251dad..1d1ea9bd4cf 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml @@ -7,7 +7,6 @@ 5.0 serial 5.0 - 1.0 @@ -23,11 +22,11 @@ - - - - - - + + + + + - \ No newline at end of file + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml index c900e79b69c..87b29c02573 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml @@ -7,7 +7,6 @@ 5.0 serial 5.0 - 5.0 @@ -22,7 +21,7 @@ - + 5.0 @@ -44,7 +43,7 @@ - - + + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml index 2642a38d9e0..5f43b53905a 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml @@ -7,7 +7,6 @@ 5.0 serial 5.0 - 2.0 @@ -23,7 +22,7 @@ - - + + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml index 2642a38d9e0..5f43b53905a 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml @@ -7,7 +7,6 @@ 5.0 serial 5.0 - 2.0 @@ -23,7 +22,7 @@ - - + + - \ No newline at end of file + From e8d76b6da5eefeb8f186f582b0163685c5d4ea70 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 18 Aug 2023 17:55:38 +0200 Subject: [PATCH 082/258] Fix table formatting --- contribs/railsim/docs/network-specification.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index e37730d4d6f..92ab91e469f 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -191,12 +191,7 @@ TODO ## Fixed vs. Moving Block -| | Microscopic Scale | Mesoscopic Scale | -|---------------|--------------------------------------|--------------------------------------| -| Moving Block | Model tracks consisting of short | Model tracks consisting of short | -| | links without resource id. | links of capacity greater than 1 | -| | | without resource id. | -| ------------- | ------------------------------------ | ------------------------------------ | -| Fixed Block | Model blocks of links with capacity | Not supported, as it adds no value; | -| | 1 and identical resource id. | simulation results will be nonsense | -| ------------- | ------------------------------------ | ------------------------------------ | +| | Microscopic Scale | Mesoscopic Scale | +|--------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------| +| Moving Block | Model tracks consisting of short links without resource id. | Model tracks consisting of short links of capacity greater than 1 without resource id. | +| Fixed Block | Model blocks of links with capacity 1 and identical resource id. | Not supported, as it adds no value; simulation results will be nonsense. | From 48edea278d410ab21a42c45a254907f92c00372e Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Mon, 21 Aug 2023 14:30:14 +0200 Subject: [PATCH 083/258] Fix integration test, test3_twoSources --- .../integration/3_twoSources/trainNetwork.xml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml index 15df575ca5b..61ffb82569a 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml @@ -40,34 +40,34 @@ - + 999 - + 999 - + 1 - + 999 - + 999 - + 999 @@ -97,11 +97,13 @@ - 2 + bottleneck + 1 + bottleneck 1 From e84a0931aced45b886ea893a03b7e9a352c44d29 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Mon, 28 Aug 2023 15:22:21 +0200 Subject: [PATCH 084/258] Test case inputs for mesoscopic and microscopic network combination - Combined rail network with 5 stations. - Different transit vehicle types. - Schedule with contraflow. --- .../micro_meso_combination/config.xml | 39 +++ .../micro_meso_combination/trainNetwork.xml | 328 ++++++++++++++++++ .../transitSchedule.xml | 166 +++++++++ .../transitVehicles.xml | 54 +++ 4 files changed, 587 insertions(+) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml new file mode 100644 index 00000000000..da3761b5c4e --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml new file mode 100644 index 00000000000..1d1c36979a9 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml @@ -0,0 +1,328 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + a + 5 + true + + + + + a + 5 + true + + + + + + + ab1 + + + + + ab1 + + + + + + ab2_1 + + + + + ab2_1 + + + + + + ab2_2 + + + + + ab2_2 + + + + + + ab2_3 + + + + + ab2_3 + + + + + + + b + 3 + true + + + + + b + 3 + true + + + + + + + bc + 2 + true + + + + + bc + 2 + true + + + + + + + c1 + + + + + c1 + + + + + + c1_c2_1 + + + + + c1_c2_1 + + + + + + c2 + + + + + c2 + + + + + + c1_c2_2 + + + + + c1_c2_2 + + + + + + c2_c3_1 + + + + + c2_c3_1 + + + + + + c3 + + + + + c3 + + + + + + c2_c3_2 + + + + + c2_c3_2 + + + + + + + cd + 2 + true + + + + + cd + 2 + true + + + + + + + d + 5 + + + + + d + 5 + + + + + + + ce_1 + 2 + true + + + + + ce_1 + 2 + true + + + + + ce_2 + 2 + + + + + ce_2 + 2 + + + + + + + e + 5 + true + + + + + e + 5 + true + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml new file mode 100644 index 00000000000..7d8fe9dc25e --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml new file mode 100644 index 00000000000..74b80c41cfd --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml @@ -0,0 +1,54 @@ + + + + + + + 0.3 + 0.3 + + + + + + + + + + + 0.4 + 0.4 + + + + + + + + + + + 0.1 + 0.1 + + + + + + + + + + + + + + + + + + + + + + From f7e2905ab5ad1722c668e46ca18360138f820c39 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Mon, 28 Aug 2023 15:43:44 +0200 Subject: [PATCH 085/258] Run test case --- .../integration/RailsimIntegrationTest.java | 5 +++++ .../micro_meso_combination/transitSchedule.xml | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 380d4ac02e5..23840962265 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -411,4 +411,9 @@ public void test_station_rerouting_concurrent() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "station_rerouting"), filter); } + + @Test + public void test_micro_meso_combination() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "micro_meso_combination")); + } } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml index 7d8fe9dc25e..697bb317be1 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml @@ -41,8 +41,8 @@ - - + + @@ -66,8 +66,8 @@ - - + + @@ -92,7 +92,7 @@ - + @@ -115,7 +115,7 @@ - + @@ -138,7 +138,7 @@ - + @@ -159,7 +159,7 @@ - + From a6359d2924d89d3da3038d91aa53a9b3fb02f95a Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Mon, 28 Aug 2023 16:37:07 +0200 Subject: [PATCH 086/258] Adjustments to schedule have some illustrative examples - Contraflow in micro station. - Contraflow in micro route. --- .../micro_meso_combination/trainNetwork.xml | 16 ++++++++-------- .../micro_meso_combination/transitSchedule.xml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml index 1d1c36979a9..4a9bf96f32b 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml @@ -81,14 +81,14 @@ a - 5 + 12 true a - 5 + 12 true @@ -204,12 +204,12 @@ - + c1_c2_2 - + c1_c2_2 @@ -268,13 +268,13 @@ d - 5 + 6 d - 5 + 6 @@ -310,14 +310,14 @@ e - 5 + 6 true e - 5 + 6 true diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml index 697bb317be1..1f4d16bb781 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml @@ -41,8 +41,8 @@ - - + + @@ -66,8 +66,8 @@ - - + + @@ -92,7 +92,7 @@ - + @@ -114,8 +114,8 @@ - - + + @@ -141,7 +141,7 @@ - + rail From 988d635054f593111dce4bcc4e6443d46c6e7285 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Wed, 30 Aug 2023 15:01:02 +0200 Subject: [PATCH 087/258] Adjust integration test - Mico meso combination. - Train following. --- .../7_trainFollowing/transitSchedule.xml | 4 +-- .../7_trainFollowing/transitVehicles.xml | 17 ++++------ .../micro_meso_combination/config.xml | 4 +++ .../micro_meso_combination/trainNetwork.xml | 33 +++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml index 33a88a1cc16..673009a1ed0 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml @@ -40,8 +40,8 @@ - + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml index 87b29c02573..4f684ec0808 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml @@ -4,16 +4,15 @@ - 5.0 - serial - 5.0 + 0.4 + 0.4 - + @@ -24,17 +23,15 @@ - 5.0 - serial - 5.0 - 5.0 + 0.1 + 0.1 - + - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml index da3761b5c4e..179b9afd763 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml @@ -36,4 +36,8 @@ + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml index 4a9bf96f32b..0f47de2b222 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml @@ -16,11 +16,11 @@ n=5 n=3 n=3 n=5 Stations: - - A: Mesoscopic, 5 + - A: Mesoscopic, 12 - B: Mesoscopic, 3 - C: Microscopic, 3 - - D: Mesoscopic, 3 - - E: Mesoscopic, 5 + - D: Mesoscopic, 6 + - E: Mesoscopic, 6 Routes: - AB: Capacity 2, Microscopic @@ -28,22 +28,21 @@ - CD: Capacity 2, Mesoscopic - CE: Capacity 2, Mesoscopic - Transit Stop Facilities (and Areas, if set): + Transit stop facilities and areas: - Stop A_F (A) - Stop A_R (A) - - Stop B - - Stop C1 (C) - - Stop C2 (C) - - Stop C3 (C) - - Stop D - - Stop E - - Trains: - - Train1: AB - CE - JK - - Train2: AB - CE - JK - - Train3: AB - CE - JK - - Train4: AB - CE - LM - - Train5: AB - CE - LM + - Stop B_F (B) + - Stop B_R (B) + - Stop C1_F (C) + - Stop C1_R (C) + - Stop C2_F (C) + - Stop C2_R (C) + - Stop C3_F (C) + - Stop C3_R (C) + - Stop D_F (D) + - Stop D_R (D) + - Stop E_F (E) + - Stop E_R (E) --> From b159fb678ee7f68143540a642198a3593b746316 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Wed, 30 Aug 2023 15:55:08 +0200 Subject: [PATCH 088/258] Remove railsimMaxDeceleration - Update train-specification.md - Update network elements in the network-specification.md --- .../railsim/docs/network-specification.md | 51 ++++++++++++++----- contribs/railsim/docs/train-specification.md | 11 +--- .../matsim/contrib/railsim/RailsimUtils.java | 5 +- .../railsim/config/RailsimConfigGroup.java | 4 +- .../src/test/resources/trainVehicleTypes.xml | 8 +-- .../integration/0_simple/transitVehicles.xml | 2 +- .../7_trainFollowing/transitVehicles.xml | 4 +- .../transitVehicles.xml | 6 +-- 8 files changed, 53 insertions(+), 38 deletions(-) diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index 92ab91e469f..2069a1e0e0e 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -104,9 +104,26 @@ Example: Currently none. -## Microscopic Examples +## Microscopic -### Single track with contraflow +### Moving Block + +Model tracks consisting of short links. Each link, except for the opposite link, has a unique resource ID and a capacity +of 1. + +### Fixed block + +Model blocks of links with a capacity of 1 each and identical resource IDs. + +### Station + +Each platform link has a distinct resource ID, except for the opposite link, and a capacity of 1. Each platform link has +a transit stop facility, which belongs to the same stop area id. Ingoing and outgoing links of the station have entry +and exit attributes. + +### Examples + +#### Single track with contraflow Default value of `railsimCapacity` sets an own railsimResourceId for each track. @@ -128,7 +145,7 @@ Default value of `railsimCapacity` sets an own railsimResourceId for each track. ``` -### Two tracks, each with a single direction +#### Two tracks, each with a single direction Default value of `railsimResourceId` sets an own railsimResourceId for each track. @@ -144,7 +161,7 @@ Default value of `railsimResourceId` sets an own railsimResourceId for each trac ``` -### Three tracks, with contraflow in the middle track +#### Three tracks, with contraflow in the middle track ```xml @@ -170,7 +187,7 @@ Default value of `railsimResourceId` sets an own railsimResourceId for each trac ``` -### Two tracks that intersect each other +#### Two tracks that intersect each other If two tracks cross each other, e.g. like in the form of the letter `X` or a plus `+`, a train driving in one direction effectively also blocks the intersecting tracks, even if they only share a common node, but not a common link. @@ -179,19 +196,25 @@ There should be no additional link- or node-attributes necessary. The simulation `railsimCapacity = 1`, but not if the capacity is larger than 1. If the node is blocked, no other trains must be able to cross this node/intersection. -## Mesoscopic Examples +## Mesoscopic -### Section with a capacity of 2 +### Track -TODO +Model tracks consisting of links with capacities exceeding 1. Opposite links share the same resource ID. There is no +differentiation between moving block and fixed block. A mesoscopic link can only initiate or terminate at points where a +physical track change is feasible. + +### Station -### Station with 5 platforms +The station consists of one or several links, including the opposite links, with the same resource ID. The capacity is +larger than 1 and corresponds to the number of tracks. + +### Examples + +#### Section with a capacity of 2 TODO -## Fixed vs. Moving Block +#### Station with 5 platforms -| | Microscopic Scale | Mesoscopic Scale | -|--------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------| -| Moving Block | Model tracks consisting of short links without resource id. | Model tracks consisting of short links of capacity greater than 1 without resource id. | -| Fixed Block | Model blocks of links with capacity 1 and identical resource id. | Not supported, as it adds no value; simulation results will be nonsense. | +TODO diff --git a/contribs/railsim/docs/train-specification.md b/contribs/railsim/docs/train-specification.md index 63082005d16..96ba098a9ee 100644 --- a/contribs/railsim/docs/train-specification.md +++ b/contribs/railsim/docs/train-specification.md @@ -18,13 +18,7 @@ The vehicle-specific acceleration. Unit: meters per square-seconds \[m/s²] #### railsimDeceleration -TODO - -The typical vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] - -#### railsimMaxDeceleration - -The vehicle-specific maximum deceleration for emergency braking. Unit: meters per square-seconds \[m/s²] +The vehicle-specific acceleration. Unit: meters per square-seconds \[m/s²] ## Examples @@ -33,8 +27,7 @@ The vehicle-specific maximum deceleration for emergency braking. Unit: meters pe 0.4 - 0.4 - 0.5 + 0.5 diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 5ed9ab6db94..97fcfee2737 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -27,7 +27,6 @@ public final class RailsimUtils { // vehicle public static final String VEHICLE_ATTRIBUTE_ACCELERATION = "railsimAcceleration"; public static final String VEHICLE_ATTRIBUTE_DECELERATION = "railsimDeceleration"; - public static final String VEHICLE_ATTRIBUTE_MAX_DECELERATION = "railsimMaxDeceleration"; private RailsimUtils() { } @@ -68,8 +67,8 @@ public static void setMinimumTrainHeadwayTime(Link link, double time) { * @return the default deceleration time or the vehicle-specific value */ public static double getTrainDeceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { - double deceleration = railsimConfigGroup.maxDecelerationGlobalDefault; - Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_MAX_DECELERATION); + double deceleration = railsimConfigGroup.decelerationGlobalDefault; + Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_DECELERATION); return attr != null ? (double) attr : deceleration; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index f6ecfd318a5..f4f4f106e8f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -41,8 +41,8 @@ public class RailsimConfigGroup extends ReflectiveConfigGroup { public double accelerationGlobalDefault = 0.5; @Parameter - @Comment("Maximum Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_MAX_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link.") - public double maxDecelerationGlobalDefault = 0.5; + @Comment("Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link.") + public double decelerationGlobalDefault = 0.5; @Parameter @Comment("Time interval in seconds a train has to wait until trying again to request a track reservation if the track was blocked by another train.") diff --git a/contribs/railsim/src/test/resources/trainVehicleTypes.xml b/contribs/railsim/src/test/resources/trainVehicleTypes.xml index 2f0fdb605e7..9e70862fe68 100644 --- a/contribs/railsim/src/test/resources/trainVehicleTypes.xml +++ b/contribs/railsim/src/test/resources/trainVehicleTypes.xml @@ -5,7 +5,7 @@ 0.7 - 0.7 + 0.7 @@ -17,7 +17,7 @@ 0.7 - 0.7 + 0.7 @@ -28,7 +28,7 @@ 0.5 - 0.5 + 0.5 @@ -39,7 +39,7 @@ 0.2 - 0.2 + 0.2 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml index 0298a829e01..557c7cca2b9 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml @@ -9,7 +9,7 @@ serial 5.0 0.5 - 0.5 + 0.5 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml index 4f684ec0808..e27b94aa51c 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml @@ -5,7 +5,7 @@ 0.4 - 0.4 + 0.4 @@ -24,7 +24,7 @@ 0.1 - 0.1 + 0.1 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml index 74b80c41cfd..d8d123f2c23 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml @@ -5,7 +5,7 @@ 0.3 - 0.3 + 0.3 @@ -17,7 +17,7 @@ 0.4 - 0.4 + 0.4 @@ -29,7 +29,7 @@ 0.1 - 0.1 + 0.1 From 2af2749176e0b0ba55808dbc11f2c6c4e812ec13 Mon Sep 17 00:00:00 2001 From: u234825 Date: Thu, 31 Aug 2023 20:16:28 +0200 Subject: [PATCH 089/258] fix test9_microStation2 (crossing ingoing links as one resource, link length) --- .../9_microStation2/trainNetwork.xml | 120 +++++++++--------- .../9_microStation2/transitSchedule.xml | 4 +- .../9_microStation2/transitVehicles.xml | 5 +- 3 files changed, 65 insertions(+), 64 deletions(-) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml index 5f1fc638a6c..47b01961b47 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml @@ -127,291 +127,293 @@ - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 + r1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 + r1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml index 1feafed0f84..821ec1d0a81 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml @@ -22,8 +22,8 @@ rail - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml index 5f43b53905a..fab0c38780f 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml @@ -4,9 +4,8 @@ - 5.0 - serial - 5.0 + 0.2 + 0.2 From c87145bd653536dfc9fc6c91cbb4f78cbedbe8bf Mon Sep 17 00:00:00 2001 From: u234825 Date: Thu, 31 Aug 2023 20:50:20 +0200 Subject: [PATCH 090/258] adding another simplistic corridor test... --- .../integration/RailsimIntegrationTest.java | 19 +++++++ .../integration/15_simpleCorridor/config.xml | 52 +++++++++++++++++++ .../15_simpleCorridor/trainNetwork.xml | 51 ++++++++++++++++++ .../15_simpleCorridor/transitSchedule.xml | 30 +++++++++++ .../15_simpleCorridor/transitVehicles.xml | 26 ++++++++++ 5 files changed, 178 insertions(+) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitVehicles.xml diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 23840962265..f385ec47b61 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -9,7 +9,9 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.Scenario; import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.events.Event; import org.matsim.api.core.v01.network.Link; +import org.matsim.core.api.experimental.events.VehicleArrivesAtFacilityEvent; import org.matsim.core.config.Config; import org.matsim.core.config.ConfigUtils; import org.matsim.core.controler.AbstractModule; @@ -369,6 +371,23 @@ public void test13_Y() { public void test14_mesoStations() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "14_mesoStations")); } + + @Test + public void test15_simpleCorridor() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "15_simpleCorridor")); + + for (Event event : collector.getEvents()) { + if (event.getEventType().equals(VehicleArrivesAtFacilityEvent.EVENT_TYPE)) { + + VehicleArrivesAtFacilityEvent vehicleArrivesEvent = (VehicleArrivesAtFacilityEvent) event; + if (vehicleArrivesEvent.getVehicleId().toString().equals("train1") && + vehicleArrivesEvent.getFacilityId().toString().equals("stop_3-4")) { + + Assert.assertEquals("The arrival time of train1 at stop_3-4 has changed.", 29594., event.getTime(), MatsimTestUtils.EPSILON); + } + } + } + } @Test public void test_station_rerouting() { diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/config.xml new file mode 100644 index 00000000000..73d7265aa02 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/config.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml new file mode 100644 index 00000000000..d68e9f1c48f --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml @@ -0,0 +1,51 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitSchedule.xml new file mode 100644 index 00000000000..88106a724e8 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitSchedule.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitVehicles.xml new file mode 100644 index 00000000000..8646c6a67ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitVehicles.xml @@ -0,0 +1,26 @@ + + + + + + + 0.2 + 0.3 + + + + + + + + + + + + + + + + + + From 457d26099513079c5e1c56aab191842d7b47435d Mon Sep 17 00:00:00 2001 From: u234825 Date: Fri, 1 Sep 2023 16:41:25 +0200 Subject: [PATCH 091/258] adding a variant of test7 which gives us a runtime exception... needs to be investigated. --- .../integration/RailsimIntegrationTest.java | 7 + .../integration/7_trainFollowing2/config.xml | 39 ++++ .../7_trainFollowing2/trainNetwork.xml | 170 ++++++++++++++++++ .../7_trainFollowing2/transitSchedule.xml | 47 +++++ .../7_trainFollowing2/transitVehicles.xml | 46 +++++ 5 files changed, 309 insertions(+) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/config.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/trainNetwork.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitSchedule.xml create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitVehicles.xml diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index f385ec47b61..e63b67755e7 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -336,6 +336,13 @@ public void test6_threeTracksMicroscopic() { public void test7_trainFollowing() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "7_trainFollowing")); } + + // This test is similar to test7_trainFollowing but with varying speed levels along the corridor. + // TODO: Right now, there are some runtime exceptions which I don't understand. + @Test + public void test7_trainFollowing2() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "7_trainFollowing2")); + } @Test public void test8_microStation() { diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/config.xml new file mode 100644 index 00000000000..80d11a1d7ab --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/trainNetwork.xml new file mode 100644 index 00000000000..9e610736643 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/trainNetwork.xml @@ -0,0 +1,170 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitSchedule.xml new file mode 100644 index 00000000000..673009a1ed0 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitSchedule.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + rail + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitVehicles.xml new file mode 100644 index 00000000000..e27b94aa51c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitVehicles.xml @@ -0,0 +1,46 @@ + + + + + + + 0.4 + 0.4 + + + + + + + + + + + + + + + + + + 0.1 + 0.1 + + + + + + + + + + + + + + + + + + + From aeeffc67cded10a58a12df566bd6174dcd942aa4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 14:51:36 +0000 Subject: [PATCH 092/258] build(deps): bump io.grpc:grpc-all from 1.57.2 to 1.58.0 Bumps [io.grpc:grpc-all](https://github.com/grpc/grpc-java) from 1.57.2 to 1.58.0. - [Release notes](https://github.com/grpc/grpc-java/releases) - [Commits](https://github.com/grpc/grpc-java/compare/v1.57.2...v1.58.0) --- updated-dependencies: - dependency-name: io.grpc:grpc-all dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- contribs/hybridsim/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/hybridsim/pom.xml b/contribs/hybridsim/pom.xml index 55a9bcd52b2..ed4777814fb 100644 --- a/contribs/hybridsim/pom.xml +++ b/contribs/hybridsim/pom.xml @@ -11,7 +11,7 @@ 3.24.2 - 1.57.2 + 1.58.0 From 8853f1e73042ef62e9481c599933ec430196f5b0 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 8 Sep 2023 10:46:32 +0200 Subject: [PATCH 093/258] Apply link length correction - If the link attribute length does not match the Euclidean distance between the nodes, this leads to distortions of the train lengths in the visualization. - Sets the length attribute to the Euclidean distance for all links. --- .../integration/10_cross/trainNetwork.xml | 59 ++++---- .../11_mesoStation/trainNetwork.xml | 58 ++++---- .../12_mesoStation2/trainNetwork.xml | 62 ++++----- .../railsim/integration/13_Y/trainNetwork.xml | 56 ++++---- .../14_mesoStations/trainNetwork.xml | 125 ++++++++--------- .../15_simpleCorridor/trainNetwork.xml | 29 ++-- .../trainNetwork.xml | 58 ++++---- .../5_complexTwoSources/trainNetwork.xml | 78 +++++------ .../6_threeTracksMicroscopic/trainNetwork.xml | 128 ++++++++---------- .../8_microStation/trainNetwork.xml | 118 ++++++++-------- .../9_microStation2/trainNetwork.xml | 122 ++++++++--------- 11 files changed, 416 insertions(+), 477 deletions(-) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml index 7e93463b353..2f3d6815f1e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml @@ -27,81 +27,78 @@ | B5 V 13 - --> + --> + - + - + - + - + - + - + - + - - + - + - + - + - + - + - - - + + - + - + c1 - + c1 - + - + - - + - + - + c1 - + c1 - + - + - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml index e137ae8df83..e8cc6ba1e7e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml @@ -5,73 +5,67 @@ Atlantis - + - + - + - + - + - + - + - + - + - + - + - - - + - 2 + 999 - - - - - - - - - - + 999 - + + + 999 - - + 999 - + + + - 999 + 2 - - + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml index 48ffeea845c..f27451d8c21 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml @@ -5,75 +5,69 @@ Atlantis - + - + - + - + - + - + - + - + - + - + - + - - + - - + - 1 + 999 - - - - - - - - - - + 999 - + + + 999 - - + 999 - + + + - 999 + 1 - - + + + + - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml index ac32e44354f..0bfe206efc8 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml @@ -12,93 +12,83 @@ 5 =========> 6 ====================> 7 ===> 8 3 ===> 4 ===================== 4a ==> - --> + --> - - - + - + - + - + - + - + - + - + - + - + - + - + - - + 999 - - + 1 - + 1 - - + 999 - - + 1 - + 1 - - + 3 - - + 1 - - + 999 - - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml index 26256d2f907..2d853285b05 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml @@ -2,83 +2,76 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - 2 - - - - - - - - - 2 - - - - - - - - - 2 - + + + + 2 + - + + + 2 + - + + + 2 + - - - 2 - + + + 2 + - + + + 2 + - + - - - 2 - + - - + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml index d68e9f1c48f..52ae1533206 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml @@ -5,47 +5,46 @@ Atlantis - + - + - + - + - + - + - + - - + 5 - + 1 - - + + 1 - - + + 5 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml index 0cf9845e575..46f01336767 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml @@ -10,76 +10,70 @@ t1_A <===> t1_B <================> t2_A <===> t2_B <====================> t3_A <===> t3_B - --> + --> - - - + - + - + - + - + - + - + - + - - - - - - - - - - - + 5 - + 5 - - + + + + + - 1 t2_A-t2_B + 1 - + - 1 t2_A-t2_B + 1 - - + + + + + 5 - + 5 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml index 62c9de23de0..c68b481d038 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml @@ -2,10 +2,6 @@ - - Atlantis - - - - - + + Atlantis + + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - - + 1 - - + 1 - - + + + + + + + + + + + 1 - - + 1 - - + - - - - - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml index f868c244985..5721ac18c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml @@ -6,191 +6,183 @@ Atlantis + + - + - + - + - + - + - + - - + - + - + - + - + - + - + - - + - + - + - + - + - + - + - + - + - + - - + - - + + + 5 + + + 1 - + 1 - - + + + 1 + + + 5 - + - 1 + 5 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + - 5 + 1 - - + 5 - + 1 - + 1 - + 1 - + 1 - + 5 - - + 5 - - - 1 - - - + 1 - + 1 - - - 5 - - - - - - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml index 6222a2999f1..e28783df73d 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml @@ -127,291 +127,291 @@ - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml index 47b01961b47..f37f491ecef 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml @@ -127,293 +127,293 @@ - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + - 1 r1 + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + - + 1 - + 1 - + 1 - + 1 - + 1 - + - 1 r1 + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 From d86953c13d5322231d3f4beac9e427aa62f2ecc4 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 8 Sep 2023 10:47:17 +0200 Subject: [PATCH 094/258] Apply link length correction - If the link attribute length does not match the Euclidean distance between the nodes, this leads to distortions of the train lengths in the visualization. - Sets the length attribute to the Euclidean distance for all links. --- .../1_oppositeTraffic/trainNetwork.xml | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml index d0ee3e64435..46f01336767 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml @@ -10,64 +10,70 @@ t1_A <===> t1_B <================> t2_A <===> t2_B <====================> t3_A <===> t3_B - --> + --> - - - + - + - + - + - + - + - + - + - - - - - - + + + 5 + - + + + 5 + - - + - + - - + - 1 t2_A-t2_B + 1 - + - 1 t2_A-t2_B + 1 - - + + + - + + + 5 + + + + + 5 + - + From b43991488e0b4bbda969f2615d5221fb357f406a Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 8 Sep 2023 10:49:48 +0200 Subject: [PATCH 095/258] Adjust RailsimUtils.java - Solve TODO by adding setters. - Rename minimum headway time. - Rearrange methods. --- .../matsim/contrib/railsim/RailsimUtils.java | 103 +++++++++--------- .../railsim/config/RailsimConfigGroup.java | 1 - .../contrib/railsim/qsimengine/RailLink.java | 2 +- .../railsim/qsimengine/RailsimEngineTest.java | 2 +- 4 files changed, 52 insertions(+), 56 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 97fcfee2737..0bf46d7beed 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -1,10 +1,7 @@ package ch.sbb.matsim.contrib.railsim; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; -import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.vehicles.VehicleType; import java.math.BigDecimal; @@ -13,26 +10,21 @@ /** * Utility class for working with Railsim and its specific attributes. * - * @author Ihab Kaddoura + * @author ikaddoura * @author rakow + * @author munterfi */ public final class RailsimUtils { - // link - public static final String LINK_ATTRIBUTE_GRADE = "railsimGrade"; public static final String LINK_ATTRIBUTE_RESOURCE_ID = "railsimResourceId"; public static final String LINK_ATTRIBUTE_CAPACITY = "railsimTrainCapacity"; - public static final String LINK_ATTRIBUTE_MAX_SPEED = "railsimMaxSpeed"; public static final String LINK_ATTRIBUTE_MINIMUM_TIME = "railsimMinimumTime"; - // vehicle public static final String VEHICLE_ATTRIBUTE_ACCELERATION = "railsimAcceleration"; public static final String VEHICLE_ATTRIBUTE_DECELERATION = "railsimDeceleration"; private RailsimUtils() { } - // TODO: Setter methods - /** * Round number to precision commonly used in Railsim. */ @@ -48,10 +40,17 @@ public static int getTrainCapacity(Link link) { return attr != null ? (int) attr : 1; } + /** + * Sets the train capacity for the link. + */ + public static void setTrainCapacity(Link link, int capacity) { + link.getAttributes().putAttribute(LINK_ATTRIBUTE_CAPACITY, capacity); + } + /** * @return the minimum time for the switch at the end of the link (toNode); if no link attribute is provided the default is 0. */ - public static double getMinimumTrainHeadwayTime(Link link) { + public static double getMinimumHeadwayTime(Link link) { Object attr = link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME); return attr != null ? (double) attr : 0; } @@ -59,33 +58,22 @@ public static double getMinimumTrainHeadwayTime(Link link) { /** * Sets the minimum headway time after a link can be released. */ - public static void setMinimumTrainHeadwayTime(Link link, double time) { + public static void setMinimumHeadwayTime(Link link, double time) { link.getAttributes().putAttribute(LINK_ATTRIBUTE_MINIMUM_TIME, time); } /** - * @return the default deceleration time or the vehicle-specific value - */ - public static double getTrainDeceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { - double deceleration = railsimConfigGroup.decelerationGlobalDefault; - Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_DECELERATION); - return attr != null ? (double) attr : deceleration; - } - - /** - * @return the default acceleration time or the vehicle-specific value + * Resource id or null if there is none. */ - public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { - double acceleration = railsimConfigGroup.accelerationGlobalDefault; - Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_ACCELERATION); - return attr != null ? (double) attr : acceleration; + public static String getResourceId(Link link) { + return (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_RESOURCE_ID); } /** - * Resource id or null if there is none. + * Sets the resource id for the link. */ - public static String getResourceId(Link link) { - return (String) link.getAttributes().getAttribute(LINK_ATTRIBUTE_RESOURCE_ID); + public static void setResourceId(Link link, String resourceId) { + link.getAttributes().putAttribute(LINK_ATTRIBUTE_RESOURCE_ID, resourceId); } /** @@ -95,6 +83,12 @@ public static boolean isEntryLink(Link link) { return Boolean.TRUE.equals(link.getAttributes().getAttribute("railsimEntry")); } + /** + * Sets whether this link is an entry link applicable for re-routing. + */ + public static void setEntryLink(Link link, boolean isEntry) { + link.getAttributes().putAttribute("railsimEntry", isEntry); + } /** * Exit link used for re routing. @@ -104,39 +98,42 @@ public static boolean isExitLink(Link link) { } /** - * @return the vehicle-specific freespeed or 0 if there is no vehicle-specific freespeed provided in the link attributes + * Sets whether this link is an exit link used for re-routing. */ - public static double getLinkFreespeedForVehicleType(VehicleType type, Link link) { - Object attribute = link.getAttributes().getAttribute(type.getId().toString()); - if (attribute == null) { - return 0.; - } else { - return (double) attribute; - } + public static void setExitLink(Link link, boolean isExit) { + link.getAttributes().putAttribute("railsimExit", isExit); } /** - * @return the line-specific freespeed or 0 if there is no line-specific freespeed provided in the link attributes + * @return the default deceleration time or the vehicle-specific value. */ - public static double getLinkFreespeedForTransitLine(Id line, Link link) { - Object attribute = link.getAttributes().getAttribute(line.toString()); - if (attribute == null) { - return 0.; - } else { - return (double) attribute; - } + public static double getTrainDeceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { + double deceleration = railsimConfigGroup.decelerationGlobalDefault; + Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_DECELERATION); + return attr != null ? (double) attr : deceleration; + } + + /** + * Sets the deceleration time for the vehicle type. + */ + public static void setTrainDeceleration(VehicleType vehicle, double deceleration) { + vehicle.getAttributes().putAttribute(VEHICLE_ATTRIBUTE_DECELERATION, deceleration); + } + + /** + * @return the default acceleration time or the vehicle-specific value. + */ + public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { + double acceleration = railsimConfigGroup.accelerationGlobalDefault; + Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_ACCELERATION); + return attr != null ? (double) attr : acceleration; } /** - * @return the line- and route-specific freespeed or 0 if there is no line- and route-specific freespeed provided in the link attributes + * Sets the acceleration time for the vehicle type. */ - public static double getLinkFreespeedForTransitLineAndTransitRoute(Id line, Id route, Link link) { - Object attribute = link.getAttributes().getAttribute(line.toString() + "+++" + route.toString()); - if (attribute == null) { - return 0.; - } else { - return (double) attribute; - } + public static void setTrainAcceleration(VehicleType vehicle, double acceleration) { + vehicle.getAttributes().putAttribute(VEHICLE_ATTRIBUTE_ACCELERATION, acceleration); } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index f4f4f106e8f..e811e8f1bc8 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -70,5 +70,4 @@ protected void checkConsistency(Config config) { } } - } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index db762656fd5..e8bddeb0427 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -47,7 +47,7 @@ public RailLink(Link link) { blocked = new MobsimDriverAgent[state.length]; length = link.getLength(); freeSpeed = link.getFreespeed(); - minimumHeadwayTime = RailsimUtils.getMinimumTrainHeadwayTime(link); + minimumHeadwayTime = RailsimUtils.getMinimumHeadwayTime(link); String resourceId = RailsimUtils.getResourceId(link); resource = resourceId != null ? Id.create(resourceId, RailResource.class) : null; isEntryLink = RailsimUtils.isEntryLink(link); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 747230327d4..87a470445bf 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -101,7 +101,7 @@ public void congested() { @Test public void congested_with_headway() { - RailsimTestUtils.Holder test = getTestEngine("network0.xml", l -> RailsimUtils.setMinimumTrainHeadwayTime(l, 60)); + RailsimTestUtils.Holder test = getTestEngine("network0.xml", l -> RailsimUtils.setMinimumHeadwayTime(l, 60)); RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo", 0, "l1-2", "l5-6"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); From 6e0a96a209d579a9a630058740d91b48b03d9f01 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 8 Sep 2023 10:50:49 +0200 Subject: [PATCH 096/258] Update docs - Add description of railsim to README.md - Adjust the specifications to the current state of railsim. --- contribs/railsim/README.md | 29 +++-- contribs/railsim/docs/events-specification.md | 20 +-- .../railsim/docs/network-specification.md | 120 +++++++++--------- contribs/railsim/docs/train-specification.md | 23 ++-- 4 files changed, 104 insertions(+), 88 deletions(-) diff --git a/contribs/railsim/README.md b/contribs/railsim/README.md index f9ea2efef06..2344b5a17db 100644 --- a/contribs/railsim/README.md +++ b/contribs/railsim/README.md @@ -1,17 +1,30 @@ # railsim -*railsim* is a QSim-Engine for MATSim, specialized on the simulation of trains. +A large-scale hybrid micro- and mesoscopic simulation approach for railway operation. -Trains behave and interact with links differently than cars: While usually multiple cars can be on one link, -for safity reasons there should usually be only one train on a track per time. -And while a car is typically located on one link only, a long train may occupy space on multiple links. -To capture these differences and offer a realistic simulation of train traffic within MATSim, -the *railsim* contrib provides a custom QSim-Engine to simulate trains. +Trains behave and interact with links differently than cars: While usually multiple cars can be on one link, +for safety reasons there should be only one train on a track (segment or block) per time. +And while a car is typically located on one link only, a long train may occupy space on multiple links. +To capture these differences and offer a realistic simulation of train traffic within MATSim, +the *railsim* contrib provides a custom QSim-Engine to simulate trains: + +- Trains are spatially expanded along several links. Additional events indicate when the end of the train leaves a link. +- Trains accelerate and decelerate based on predefined vehicle attributes (along a single link or along several links). +- The infrastructure ahead of each train is blocked (reserved train path) depending on the braking distance which is computed based on the + vehicle-specific deceleration and the current speed. +- Capacity effects are modeled at the level of resources. A resource consists of one link or several links. +- Trains may deviate from the network route given in the schedule, e.g. to avoid a blocked track (dispatching, disposition). ## Configuration -TODO +See `config.RailsimConfigGroup.java` ## Enabling railsim in MATSim -TODO, see RunExample.java +See `RunRailsimExample.java` + +## Specifications + +- [network-specification](docs/network-specification.md) +- [train-specification](docs/train-specification.md) +- [events-specification](docs/events-specification.md) diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index 8a5cb670752..26e9bc0b10c 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -6,10 +6,10 @@ All the additional events use the prefix `railsim`. ## Event Types -### railsimLinkStateChangeEvent +### RailsimLinkStateChangeEvent -Instead of `trainPathEntersLinkEvent`, we could have a generic `railsimLinkStateChangeEvent` -that could include information about the new state of the link (or even the track of a multi-track link). +Instead of `TrainPathEntersLinkEvent`, we have a generic `RailsimLinkStateChangeEvent` that includes information about +the new state of the link (or even the track of a multi-track link). Attributes: @@ -17,21 +17,21 @@ Attributes: - `vehicleId`: if `state=reserved|blocked`, the id of the vehicle blocking or reserving this link - `track`: a number (0-based or 1-based?) if the link has multiple tracks -### railsimTrainLeavesLinkEvent +### RailsimTrainLeavesLinkEvent -Similar to the existing `trainLeavesLinkEvent`. +Similar to the existing `TrainLeavesLinkEvent`. One could argue that setting the link state to `free` would imply the same. I (mr) would still say it makes sense to have it separate, because depending on the implementation, a link could remain blocked for a longer time even if the train has already passed (e.g. minimum headway time). -There is **no** railsimTrainEntersLinkEvent. The regular `LinkEnterEvent` is used to provide the highest +There is **no** `RailsimTrainEntersLinkEvent`. The regular `LinkEnterEvent` is used to provide the highest compatibility with existing analysis and visualization tools. -### railsimTrainStateEvent +### RailsimTrainStateEvent -This event is emitted every time there is a position update for a train. +This event is emitted every time there is a position update for a train. This event contains detailed information about the trains position on a single link. -### railsimDetourEvent +### RailsimDetourEvent -This event is emitted when a train is re-routed and contains parts of the routes that have changed. \ No newline at end of file +This event is emitted when a train is re-routed and contains parts of the routes that have changed. diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index 2069a1e0e0e..7964e38bf3a 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -14,10 +14,6 @@ We use the prefix `railsim` where it is appropriate. ### Link Attributes -#### railsimGrade - -TODO - #### railsimTrainCapacity The number of trains that can be on this link at the same time. If the attribute is not provided, a default of 1 is @@ -30,9 +26,9 @@ Example: ```xml - - 3 - + + 3 + ``` @@ -69,17 +65,13 @@ Example: ```xml - - true - + + true + ``` -#### railsimMaxSpeed - -TODO - #### railsimSpeed_ + vehicle type TODO @@ -92,10 +84,10 @@ Example: ```xml - - 44.444 - 50.0 - + + 44.444 + 50.0 + ``` @@ -130,18 +122,18 @@ Default value of `railsimCapacity` sets an own railsimResourceId for each track. ```xml - - - AB - - - - - AB - - + + + AB + + + + + AB + + ``` @@ -152,12 +144,12 @@ Default value of `railsimResourceId` sets an own railsimResourceId for each trac ```xml - - - - + + + + ``` @@ -166,24 +158,24 @@ Default value of `railsimResourceId` sets an own railsimResourceId for each trac ```xml - - - - - AB - - - - - AB - - - - + + + + + AB + + + + + AB + + + + ``` @@ -198,7 +190,7 @@ to cross this node/intersection. ## Mesoscopic -### Track +### Route Model tracks consisting of links with capacities exceeding 1. Opposite links share the same resource ID. There is no differentiation between moving block and fixed block. A mesoscopic link can only initiate or terminate at points where a @@ -213,8 +205,18 @@ larger than 1 and corresponds to the number of tracks. #### Section with a capacity of 2 -TODO - -#### Station with 5 platforms +```xml -TODO + + + + 2 + + + + + 2 + + + +``` diff --git a/contribs/railsim/docs/train-specification.md b/contribs/railsim/docs/train-specification.md index 96ba098a9ee..1d4182a3d30 100644 --- a/contribs/railsim/docs/train-specification.md +++ b/contribs/railsim/docs/train-specification.md @@ -10,7 +10,8 @@ type in MATSim. ### TransitVehicle Attributes -TODO, e.g. length and acceleration of a train. +Default vehicle type attributes for length, maximum velocity and capacity. +Set network mode to rail. #### railsimAcceleration @@ -18,21 +19,21 @@ The vehicle-specific acceleration. Unit: meters per square-seconds \[m/s²] #### railsimDeceleration -The vehicle-specific acceleration. Unit: meters per square-seconds \[m/s²] +The vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] ## Examples ```xml - - 0.4 - 0.5 - - - - - - + + 0.4 + 0.5 + + + + + + ``` From bb43df26cd0fe8a800a4d7a6f55a4adc6f561a60 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 8 Sep 2023 15:03:40 +0200 Subject: [PATCH 097/258] Rename junction tests --- .../integration/RailsimIntegrationTest.java | 18 +++++++++--------- .../{10_cross => 10_junctionCross}/config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../{13_Y => 13_junctionY}/config.xml | 0 .../{13_Y => 13_junctionY}/trainNetwork.xml | 0 .../{13_Y => 13_junctionY}/transitSchedule.xml | 0 .../{13_Y => 13_junctionY}/transitVehicles.xml | 0 9 files changed, 9 insertions(+), 9 deletions(-) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_cross => 10_junctionCross}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_cross => 10_junctionCross}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_cross => 10_junctionCross}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_cross => 10_junctionCross}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_Y => 13_junctionY}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_Y => 13_junctionY}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_Y => 13_junctionY}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_Y => 13_junctionY}/transitVehicles.xml (100%) diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index e63b67755e7..ca45273262b 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -336,7 +336,7 @@ public void test6_threeTracksMicroscopic() { public void test7_trainFollowing() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "7_trainFollowing")); } - + // This test is similar to test7_trainFollowing but with varying speed levels along the corridor. // TODO: Right now, there are some runtime exceptions which I don't understand. @Test @@ -355,8 +355,8 @@ public void test9_microStation2() { } @Test - public void test10_cross() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "10_cross")); + public void test10_junctionCross() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "10_junctionCross")); } @Test @@ -370,26 +370,26 @@ public void test12_mesoStation2() { } @Test - public void test13_Y() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "13_Y")); + public void test13_junctionY() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "13_junctionY")); } @Test public void test14_mesoStations() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "14_mesoStations")); } - + @Test public void test15_simpleCorridor() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "15_simpleCorridor")); - + for (Event event : collector.getEvents()) { if (event.getEventType().equals(VehicleArrivesAtFacilityEvent.EVENT_TYPE)) { - + VehicleArrivesAtFacilityEvent vehicleArrivesEvent = (VehicleArrivesAtFacilityEvent) event; if (vehicleArrivesEvent.getVehicleId().toString().equals("train1") && vehicleArrivesEvent.getFacilityId().toString().equals("stop_3-4")) { - + Assert.assertEquals("The arrival time of train1 at stop_3-4 has changed.", 29594., event.getTime(), MatsimTestUtils.EPSILON); } } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_cross/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_Y/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitVehicles.xml From dbe4c0868f8ea95b71a83a3e634f8671191514dd Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 8 Sep 2023 17:17:52 +0200 Subject: [PATCH 098/258] Refactor tests for improved readability and maintainability - Renamed tests with descriptive names following naming conventions. - Isolated integration and unit test inputs to prevent unintended changes. - Introduced naming consistency: micro/mesoscopic cases, and uni-/bidirectional when applicable. --- .../integration/RailsimIntegrationTest.java | 104 ++++++----- .../railsim/qsimengine/RailsimCalcTest.java | 14 +- .../railsim/qsimengine/RailsimEngineTest.java | 38 ++-- .../railsim/integration/0_simple/config.xml | 37 ---- .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../{13_junctionY => mesoStations}/config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../microSimpleBiDirectionalTrack/config.xml | 49 +++++ .../trainNetwork.xml} | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 170 ++++++++++++++++++ .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../config.xml | 0 .../trainNetwork.xml.gz | Bin .../transitSchedule.xml.gz | Bin .../transitVehicles.xml.gz | Bin .../config.xml | 0 .../trainNetwork.xml | 0 .../transitSchedule.xml | 0 .../transitVehicles.xml | 0 .../{network1.xml => networkMesoUni.xml} | 0 .../railsim/qsimengine/networkMicroBi.xml | 78 ++++++++ .../networkMicroUni.xml} | 0 .../railsim/qsimengine/network_genf.xml.gz | Bin 160331 -> 0 bytes 88 files changed, 373 insertions(+), 117 deletions(-) delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/config.xml rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{11_mesoStation => mesoStationCapacityOne}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{12_mesoStation2 => mesoStationCapacityOne}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{11_mesoStation => mesoStationCapacityOne}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{11_mesoStation => mesoStationCapacityOne}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{12_mesoStation2 => mesoStationCapacityTwo}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{11_mesoStation => mesoStationCapacityTwo}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{12_mesoStation2 => mesoStationCapacityTwo}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{12_mesoStation2 => mesoStationCapacityTwo}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_junctionY => mesoStations}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{14_mesoStations => mesoStations}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{14_mesoStations => mesoStations}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{14_mesoStations => mesoStations}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{14_mesoStations => mesoTwoSources}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{3_twoSources => mesoTwoSources}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{3_twoSources => mesoTwoSources}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{3_twoSources => mesoTwoSources}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{1_oppositeTraffic => mesoTwoSourcesComplex}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{5_complexTwoSources => mesoTwoSourcesComplex}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{5_complexTwoSources => mesoTwoSourcesComplex}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{5_complexTwoSources => mesoTwoSourcesComplex}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{0_varyingCapacities => mesoUniDirectionalVaryingCapacities}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{0_varyingCapacities => mesoUniDirectionalVaryingCapacities}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{0_varyingCapacities => mesoUniDirectionalVaryingCapacities}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{0_varyingCapacities => mesoUniDirectionalVaryingCapacities}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_junctionCross => microJunctionCross}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_junctionCross => microJunctionCross}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_junctionCross => microJunctionCross}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{10_junctionCross => microJunctionCross}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{2_multipleOppositeTraffic => microJunctionY}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_junctionY => microJunctionY}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_junctionY => microJunctionY}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{13_junctionY => microJunctionY}/transitVehicles.xml (100%) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/{qsimengine/network0.xml => integration/microSimpleBiDirectionalTrack/trainNetwork.xml} (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{0_simple => microSimpleBiDirectionalTrack}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{0_simple => microSimpleBiDirectionalTrack}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{15_simpleCorridor => microSimpleUniDirectionalTrack}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{15_simpleCorridor => microSimpleUniDirectionalTrack}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{15_simpleCorridor => microSimpleUniDirectionalTrack}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{15_simpleCorridor => microSimpleUniDirectionalTrack}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{3_twoSources => microStationDifferentLink}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{9_microStation2 => microStationDifferentLink}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{9_microStation2 => microStationDifferentLink}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{9_microStation2 => microStationDifferentLink}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{5_complexTwoSources => microStationRerouting}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{station_rerouting => microStationRerouting}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{station_rerouting => microStationRerouting}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{station_rerouting => microStationRerouting}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{6_threeTracksMicroscopic => microStationSameLink}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{8_microStation => microStationSameLink}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{8_microStation => microStationSameLink}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{8_microStation => microStationSameLink}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{7_trainFollowing => microThreeUniDirectionalTracks}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{6_threeTracksMicroscopic => microThreeUniDirectionalTracks}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{6_threeTracksMicroscopic => microThreeUniDirectionalTracks}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{6_threeTracksMicroscopic => microThreeUniDirectionalTracks}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{7_trainFollowing2 => microTrackOppositeTraffic}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{1_oppositeTraffic => microTrackOppositeTraffic}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{1_oppositeTraffic => microTrackOppositeTraffic}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{1_oppositeTraffic => microTrackOppositeTraffic}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{8_microStation => microTrackOppositeTrafficMany}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{2_multipleOppositeTraffic => microTrackOppositeTrafficMany}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{2_multipleOppositeTraffic => microTrackOppositeTrafficMany}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{2_multipleOppositeTraffic => microTrackOppositeTrafficMany}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{9_microStation2 => microTrainFollowingConstantSpeed}/config.xml (100%) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{7_trainFollowing => microTrainFollowingConstantSpeed}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{7_trainFollowing => microTrainFollowingConstantSpeed}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{station_rerouting => microTrainFollowingVaryingSpeed}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{7_trainFollowing2 => microTrainFollowingVaryingSpeed}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{7_trainFollowing2 => microTrainFollowingVaryingSpeed}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{7_trainFollowing2 => microTrainFollowingVaryingSpeed}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{4_genf_bern => scenarioMesoGenfBern}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{4_genf_bern => scenarioMesoGenfBern}/trainNetwork.xml.gz (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{4_genf_bern => scenarioMesoGenfBern}/transitSchedule.xml.gz (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{4_genf_bern => scenarioMesoGenfBern}/transitVehicles.xml.gz (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{micro_meso_combination => scenarioMicroMesoCombination}/config.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{micro_meso_combination => scenarioMicroMesoCombination}/trainNetwork.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{micro_meso_combination => scenarioMicroMesoCombination}/transitSchedule.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/{micro_meso_combination => scenarioMicroMesoCombination}/transitVehicles.xml (100%) rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/{network1.xml => networkMesoUni.xml} (100%) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml rename contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/{integration/7_trainFollowing/trainNetwork.xml => qsimengine/networkMicroUni.xml} (100%) delete mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index ca45273262b..5c581dd453a 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -43,7 +43,7 @@ public class RailsimIntegrationTest { public MatsimTestUtils utils = new MatsimTestUtils(); @Test - public void scenario_kelheim() { + public void testScenarioKelheim() { URL base = ExamplesUtils.getTestScenarioURL("kelheim"); @@ -82,10 +82,8 @@ public void scenario_kelheim() { } @Test - public void test0_simple() { - - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "0_simple")); - + public void testMicroSimpleBiDirectionalTrack() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microSimpleBiDirectionalTrack")); } private EventsCollector runSimulation(File scenarioDir) { @@ -165,8 +163,8 @@ private List filterTrainEvents(EventsCollector collector } @Test - public void test0_varyingCapacities() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "0_varyingCapacities")); + public void testMesoUniDirectionalVaryingCapacities() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "mesoUniDirectionalVaryingCapacities")); // print events of train1 for debugging List train1events = filterTrainEvents(collector, "train1"); @@ -273,27 +271,33 @@ public void test0_varyingCapacities() { } @Test - public void test1_oppositeTraffic() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "1_oppositeTraffic")); + public void testMicroTrackOppositeTraffic() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microTrackOppositeTraffic")); + } + + @Test + public void testMicroTrackOppositeTrafficMany() { + // multiple trains, one slow train + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microTrackOppositeTrafficMany")); } @Test - public void test2_oppositeTraffic_multipleTrains_oneSlowTrain() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "2_multipleOppositeTraffic")); + public void testMesoTwoSources() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "mesoTwoSources")); } @Test - public void test3_twoSources() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "3_twoSources")); + public void testMesoTwoSourcesComplex() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "mesoTwoSourcesComplex")); } @Test - public void test4_genf_bern() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "4_genf_bern")); + public void testScenarioMesoGenfBernAllTrains() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "scenarioMesoGenfBern")); } @Test - public void test4_1_genf_bern() { + public void testScenarioMesoGenfBernOneTrain() { // Remove vehicles except the first one Consumer filter = scenario -> { @@ -318,70 +322,65 @@ public void test4_1_genf_bern() { remove.forEach(v -> scenario.getTransitVehicles().removeVehicle(v)); }; - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "4_genf_bern"), filter); + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "scenarioMesoGenfBern"), filter); } @Test - public void test5_complexTwoSources() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "5_complexTwoSources")); + public void testMicroThreeUniDirectionalTracks() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microThreeUniDirectionalTracks")); } @Test - public void test6_threeTracksMicroscopic() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "6_threeTracksMicroscopic")); + public void testMicroTrainFollowingConstantSpeed() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microTrainFollowingConstantSpeed")); } - @Test - public void test7_trainFollowing() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "7_trainFollowing")); - } - - // This test is similar to test7_trainFollowing but with varying speed levels along the corridor. + // This test is similar to testMicroTrainFollowingConstantSpeed but with varying speed levels along the corridor. // TODO: Right now, there are some runtime exceptions which I don't understand. @Test - public void test7_trainFollowing2() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "7_trainFollowing2")); + public void testMicroTrainFollowingVaryingSpeed() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microTrainFollowingVaryingSpeed")); } @Test - public void test8_microStation() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "8_microStation")); + public void testMicroStationSameLink() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microStationSameLink")); } @Test - public void test9_microStation2() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "9_microStation2")); + public void testMicroStationDifferentLink() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microStationDifferentLink")); } @Test - public void test10_junctionCross() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "10_junctionCross")); + public void testMicroJunctionCross() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microJunctionCross")); } @Test - public void test11_mesoStation() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "11_mesoStation")); + public void testMicroJunctionY() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microJunctionY")); } @Test - public void test12_mesoStation2() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "12_mesoStation2")); + public void testMesoStationCapacityOne() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "mesoStationCapacityOne")); } @Test - public void test13_junctionY() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "13_junctionY")); + public void testMesoStationCapacityTwo() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "mesoStationCapacityTwo")); } @Test - public void test14_mesoStations() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "14_mesoStations")); + public void testMesoStations() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "mesoStations")); } @Test - public void test15_simpleCorridor() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "15_simpleCorridor")); + public void testMicroSimpleUniDirectionalTrack() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microSimpleUniDirectionalTrack")); for (Event event : collector.getEvents()) { if (event.getEventType().equals(VehicleArrivesAtFacilityEvent.EVENT_TYPE)) { @@ -397,23 +396,20 @@ public void test15_simpleCorridor() { } @Test - public void test_station_rerouting() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "station_rerouting")); - + public void testMicroStationRerouting() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microStationRerouting")); collector.getEvents().forEach(System.out::println); - // Checks end times assertTrainState(30854, 0, 0, 0, 400, filterTrainEvents(collector, "train1")); // 1min later assertTrainState(30914, 0, 0, 0, 400, filterTrainEvents(collector, "train2")); - // These arrive closer together, because both waited assertTrainState(30974, 0, 0, 0, 400, filterTrainEvents(collector, "train3")); assertTrainState(30982, 0, 0, 0, 400, filterTrainEvents(collector, "train4")); } @Test - public void test_station_rerouting_concurrent() { + public void testMicroStationReroutingConcurrent() { Consumer filter = scenario -> { TransitScheduleFactory f = scenario.getTransitSchedule().getFactory(); @@ -435,11 +431,11 @@ public void test_station_rerouting_concurrent() { } }; - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "station_rerouting"), filter); + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microStationRerouting"), filter); } @Test - public void test_micro_meso_combination() { - EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "micro_meso_combination")); + public void testScenarioMicroMesoCombination() { + EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "scenarioMicroMesoCombination")); } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index 6fcd9decffc..797088d4110 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -8,7 +8,7 @@ public class RailsimCalcTest { @Test - public void calc() { + public void testCalcAndSolveTraveledDist() { assertThat(RailsimCalc.calcTraveledDist(5, 2, 0)) .isEqualTo(10); @@ -28,7 +28,7 @@ public void calc() { } @Test - public void negative() { + public void testCalcAndSolveTraveledDistNegative() { double d = RailsimCalc.calcTraveledDist(5, 5, -1); @@ -45,7 +45,7 @@ public void negative() { } @Test - public void maxSpeed() { + public void testMaxSpeed() { double dist = 1000; @@ -55,7 +55,7 @@ public void maxSpeed() { RailsimCalc.SpeedTarget res = RailsimCalc.calcTargetSpeed(dist, 0.5, 0.5, 5, 30, 0); - System.out.println(res); + // System.out.println(res); double timeDecel = (res.targetSpeed() - f) / 0.5; double distDecel = RailsimCalc.calcTraveledDist(res.targetSpeed(), timeDecel, -0.5); @@ -69,7 +69,7 @@ public void maxSpeed() { } @Test - public void decel() { + public void testCalcTargetDecel() { double d = RailsimCalc.calcTargetDecel(1000, 0,10); @@ -84,7 +84,7 @@ public void decel() { } @Test - public void targetSpeed() { + public void testCalcTargetSpeed() { RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(100, 0.5, 0.5, 0, 23, 0); @@ -100,7 +100,7 @@ public void targetSpeed() { } @Test - public void speedForStop() { + public void testCalcTargetSpeedForStop() { double v = RailsimCalc.calcTargetSpeedForStop(1000, 0.5, 0.5, 0); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 87a470445bf..861026fed34 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -58,9 +58,9 @@ private RailsimTestUtils.Holder getTestEngine(String network) { } @Test - public void simple() { + public void testSimple() { - RailsimTestUtils.Holder test = getTestEngine("network0.xml"); + RailsimTestUtils.Holder test = getTestEngine("networkMicroBi.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); test.doSimStepUntil(400); @@ -70,7 +70,7 @@ public void simple() { .hasTrainState("train", 144, 0, 44) .hasTrainState("train", 234, 2000, 0); - test = getTestEngine("network0.xml"); + test = getTestEngine("networkMicroBi.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "train", 0, "l1-2", "l5-6"); test.doStateUpdatesUntil(400, 1); @@ -83,9 +83,9 @@ public void simple() { } @Test - public void congested() { + public void testCongested() { - RailsimTestUtils.Holder test = getTestEngine("network0.xml"); + RailsimTestUtils.Holder test = getTestEngine("networkMicroBi.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo", 0, "l1-2", "l5-6"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); @@ -99,9 +99,9 @@ public void congested() { } @Test - public void congested_with_headway() { + public void testCongestedWithHeadway() { - RailsimTestUtils.Holder test = getTestEngine("network0.xml", l -> RailsimUtils.setMinimumHeadwayTime(l, 60)); + RailsimTestUtils.Holder test = getTestEngine("networkMicroBi.xml", l -> RailsimUtils.setMinimumHeadwayTime(l, 60)); RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo", 0, "l1-2", "l5-6"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 60, "l1-2", "l5-6"); @@ -116,9 +116,9 @@ public void congested_with_headway() { @Test - public void opposite() { + public void testOpposite() { - RailsimTestUtils.Holder test = getTestEngine("network0.xml"); + RailsimTestUtils.Holder test = getTestEngine("networkMicroBi.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l7-8"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l8-7", "l2-1"); @@ -132,7 +132,7 @@ public void opposite() { .hasTrainState("regio2", 358, 1000, 0); - test = getTestEngine("network0.xml"); + test = getTestEngine("networkMicroBi.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "l1-2", "l7-8"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "l8-7", "l2-1"); @@ -148,9 +148,9 @@ public void opposite() { } @Test - public void varyingSpeed_one() { + public void testVaryingSpeedOne() { - RailsimTestUtils.Holder test = getTestEngine("network1.xml"); + RailsimTestUtils.Holder test = getTestEngine("networkMesoUni.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 0, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); @@ -162,7 +162,7 @@ public void varyingSpeed_one() { .hasTrainState("regio", 7599, 0, 2.7777777) .hasTrainState("regio", 7674, 200, 0); - test = getTestEngine("network1.xml"); + test = getTestEngine("networkMesoUni.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio", 0, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); @@ -177,9 +177,9 @@ public void varyingSpeed_one() { } @Test - public void varyingSpeed_many() { + public void testVaryingSpeedMany() { - RailsimTestUtils.Holder test = getTestEngine("network1.xml"); + RailsimTestUtils.Holder test = getTestEngine("networkMesoUni.xml"); for (int i = 0; i < 10; i++) { RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio" + i, 60 * i, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); @@ -195,7 +195,7 @@ public void varyingSpeed_many() { // test.debug(collector, "varyingSpeed_many"); - test = getTestEngine("network1.xml"); + test = getTestEngine("networkMesoUni.xml"); for (int i = 0; i < 10; i++) { RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio" + i, 60 * i, "t1_IN-t1_OUT", "t3_IN-t3_OUT"); @@ -212,9 +212,9 @@ public void varyingSpeed_many() { } @Test - public void trainFollowing() { + public void testTrainFollowing() { - RailsimTestUtils.Holder test = getTestEngine("../integration/7_trainFollowing/trainNetwork.xml"); + RailsimTestUtils.Holder test = getTestEngine("networkMicroUni.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "1-2", "20-21"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "1-2", "20-21"); @@ -226,7 +226,7 @@ public void trainFollowing() { .hasTrainState("regio1", 1138, 1000, 0) .hasTrainState("regio2", 1517, 1000, 0); - test = getTestEngine("../integration/7_trainFollowing/trainNetwork.xml"); + test = getTestEngine("networkMicroUni.xml"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio1", 0, "1-2", "20-21"); RailsimTestUtils.createDeparture(test, TestVehicle.Regio, "regio2", 0, "1-2", "20-21"); diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/config.xml deleted file mode 100644 index 5725cce9371..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/config.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/11_mesoStation/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/12_mesoStation2/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/14_mesoStations/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_varyingCapacities/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/10_junctionCross/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/13_junctionY/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml new file mode 100644 index 00000000000..b110a860043 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network0.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/0_simple/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/15_simpleCorridor/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/3_twoSources/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/5_complexTwoSources/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/6_threeTracksMicroscopic/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/1_oppositeTraffic/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/8_microStation/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/2_multipleOppositeTraffic/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/9_microStation2/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml new file mode 100644 index 00000000000..31353bdad2f --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml @@ -0,0 +1,170 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/station_rerouting/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing2/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/trainNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/trainNetwork.xml.gz similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/trainNetwork.xml.gz rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/trainNetwork.xml.gz diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitSchedule.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/transitSchedule.xml.gz similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitSchedule.xml.gz rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/transitSchedule.xml.gz diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitVehicles.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/transitVehicles.xml.gz similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/4_genf_bern/transitVehicles.xml.gz rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/transitVehicles.xml.gz diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/config.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/config.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/config.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/trainNetwork.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/trainNetwork.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitSchedule.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitSchedule.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitSchedule.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitVehicles.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/micro_meso_combination/transitVehicles.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitVehicles.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMesoUni.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network1.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMesoUni.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml new file mode 100644 index 00000000000..ac6b478fe47 --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml @@ -0,0 +1,78 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + l45 + + + + + l45 + + + + + + + + + + + + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroUni.xml similarity index 100% rename from contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/7_trainFollowing/trainNetwork.xml rename to contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroUni.xml diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/network_genf.xml.gz deleted file mode 100644 index 62d71612cfee807b4c1f2071d84a8eb764f42df5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160331 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 Date: Fri, 8 Sep 2023 17:30:04 +0200 Subject: [PATCH 099/258] feat: avoid short wait times in drt (#2730) * feat: avoid short wait times in drt * rename getStartSlack to getStartSlackTime * Move Start.now to VehicleEntry.createTime --- .../charging/ChargingChangeoverActivity.java | 4 +- .../schedule/ShiftDrtActionCreator.java | 4 +- .../VehicleDataEntryFactoryImpl.java | 22 +++++++--- .../contrib/drt/optimizer/VehicleEntry.java | 11 ++++- .../InsertionDetourTimeCalculator.java | 35 +--------------- .../insertion/InsertionGenerator.java | 29 +++++++++++++- .../drt/passenger/DrtStopActivity.java | 13 +++--- .../DefaultRequestInsertionScheduler.java | 23 +++-------- .../drt/vrpagent/DrtActionCreator.java | 2 +- .../VehicleDataEntryFactoryImplTest.java | 30 +++++++++----- .../insertion/BestInsertionFinderTest.java | 2 +- .../DefaultUnplannedRequestInserterTest.java | 2 +- .../InsertionCostCalculatorTest.java | 4 +- .../InsertionDetourTimeCalculatorTest.java | 2 +- ...imeCalculatorWithVariableDurationTest.java | 2 +- .../insertion/InsertionGeneratorTest.java | 8 ++-- .../extensive/DetourPathDataCacheTest.java | 2 +- .../KNearestInsertionsAtEndFilterTest.java | 2 +- .../drt/run/examples/RunDrtExampleIT.java | 40 ++++++++++++++++++- 19 files changed, 144 insertions(+), 93 deletions(-) diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingChangeoverActivity.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingChangeoverActivity.java index 2c5d784e8da..f5771c79f09 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingChangeoverActivity.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingChangeoverActivity.java @@ -25,9 +25,9 @@ public ChargingChangeoverActivity(ChargingTask chargingTask, PassengerHandler pa DynAgent driver, StayTask task, Map, ? extends AcceptedDrtRequest> dropoffRequests, Map, ? extends AcceptedDrtRequest> pickupRequests) { - chargingDelegate = new FixedTimeChargingActivity(chargingTask, task.getEndTime()); - busStopDelegate = new DrtStopActivity(passengerHandler, driver, task, dropoffRequests, pickupRequests, ""); endTime = task.getEndTime(); + chargingDelegate = new FixedTimeChargingActivity(chargingTask, endTime); + busStopDelegate = new DrtStopActivity(passengerHandler, driver, () -> endTime, dropoffRequests, pickupRequests, ""); } @Override diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftDrtActionCreator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftDrtActionCreator.java index 42c7c18db1a..0f65e1208f0 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftDrtActionCreator.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftDrtActionCreator.java @@ -32,11 +32,11 @@ public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now Task task = vehicle.getSchedule().getCurrentTask(); if (task instanceof ShiftBreakTask) { DrtStopTask t = (DrtStopTask)task; - return new DrtStopActivity(passengerHandler, dynAgent, t, t.getDropoffRequests(), t.getPickupRequests(), + return new DrtStopActivity(passengerHandler, dynAgent, t::getEndTime, t.getDropoffRequests(), t.getPickupRequests(), DRT_SHIFT_BREAK_NAME); } else if (task instanceof ShiftChangeOverTask) { DrtStopTask t = (DrtStopTask) task; - return new DrtStopActivity(passengerHandler, dynAgent, t, t.getDropoffRequests(), t.getPickupRequests(), + return new DrtStopActivity(passengerHandler, dynAgent, t::getEndTime, t.getDropoffRequests(), t.getPickupRequests(), DRT_SHIFT_CHANGEOVER_NAME); } else if (task instanceof WaitForShiftStayTask) { return new IdleDynActivity(DRT_SHIFT_WAIT_FOR_SHIFT_NAME, task::getEndTime); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java index 1b5ec6a5d94..98b961e2204 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; +import org.matsim.contrib.drt.passenger.AcceptedDrtRequest; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.schedule.DrtStopTask; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; @@ -100,31 +101,40 @@ public VehicleEntry create(DvrpVehicle vehicle, double currentTime) { Waypoint.Stop s = stops[i] = new Waypoint.Stop(stopTasks.get(i), outgoingOccupancy); outgoingOccupancy -= s.getOccupancyChange(); } + + Waypoint.Stop startStop = startTask != null && STOP.isBaseTypeOf(startTask) + ? new Waypoint.Stop((DrtStopTask) startTask, 0) + : null; - var slackTimes = computeSlackTimes(vehicle, currentTime, stops); + var slackTimes = computeSlackTimes(vehicle, currentTime, stops, startStop); return new VehicleEntry(vehicle, new Waypoint.Start(startTask, start.link, start.time, outgoingOccupancy), - ImmutableList.copyOf(stops), slackTimes); + ImmutableList.copyOf(stops), slackTimes, currentTime); } public boolean isNotEligibleForRequestInsertion(DvrpVehicle vehicle, double currentTime) { return currentTime + lookAhead < vehicle.getServiceBeginTime() || currentTime >= vehicle.getServiceEndTime(); } - static double[] computeSlackTimes(DvrpVehicle vehicle, double now, Waypoint.Stop[] stops) { - double[] slackTimes = new double[stops.length + 1]; + static double[] computeSlackTimes(DvrpVehicle vehicle, double now, Waypoint.Stop[] stops, Waypoint.Stop start) { + double[] slackTimes = new double[stops.length + 2]; //vehicle double slackTime = calcVehicleSlackTime(vehicle, now); - slackTimes[stops.length] = slackTime; + slackTimes[stops.length + 1] = slackTime; //stops for (int i = stops.length - 1; i >= 0; i--) { var stop = stops[i]; slackTime = Math.min(stop.latestArrivalTime - stop.task.getBeginTime(), slackTime); slackTime = Math.min(stop.latestDepartureTime - stop.task.getEndTime(), slackTime); - slackTimes[i] = slackTime; + slackTimes[i + 1] = slackTime; } + + // start + slackTimes[0] = start == null ? slackTime : + Math.min(start.latestDepartureTime - start.task.getEndTime(), slackTime); + return slackTimes; } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java index b79c3523f33..df2bd47957b 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/VehicleEntry.java @@ -37,14 +37,16 @@ public interface EntryFactory { public final ImmutableList stops; public final Waypoint.End end; private final double[] slackTimes;// for all insertion points + public final double createTime; public VehicleEntry(DvrpVehicle vehicle, Waypoint.Start start, ImmutableList stops, - double[] slackTimes) { + double[] slackTimes, double createTime) { this.vehicle = vehicle; this.start = start; this.stops = stops; this.end = Waypoint.End.OPEN_END; this.slackTimes = slackTimes; + this.createTime = createTime; } protected VehicleEntry(VehicleEntry that) { @@ -53,6 +55,7 @@ protected VehicleEntry(VehicleEntry that) { this.stops = that.stops; this.end = that.end; this.slackTimes = that.slackTimes; + this.createTime = that.createTime; } public Waypoint getWaypoint(int index) { @@ -64,6 +67,10 @@ public boolean isAfterLastStop(int index) { } public double getSlackTime(int index) { - return slackTimes[index]; + return slackTimes[index + 1]; + } + + public double getStartSlackTime() { + return slackTimes[0]; } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java index 9ff3a41e82d..4e0006320cc 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculator.java @@ -152,39 +152,8 @@ private PickupTimeInfo calculatePickupIfSameLink(VehicleEntry vEntry, int pickup return new PickupTimeInfo(departureTime, additionalStopDuration); } else { // case 3: previous waypoint is an ongoing (started) stop - // insertion is the beginning of the planned stop (traditionally) - - /* - * TODO: Generally, DRT is allowed to insert pickups into ongoing stop tasks. - * However, currently, stop tasks have a fixed duration that is never changed. - * This means that a pickup is added to an ongoing task that will end, maybe in - * 20s. The task will then end regardless of the added pickup. The request will, - * hence, have a wait time of 20s although the configured stop duration may be - * 60s. We can even frequently have requests with zero wait time. - * - * To mitigate the problem, the following steps are necessary: - * - Impose here not the stopTask.beginTime as the time at which the request is inserted, - * but impose the current time ("now") as the point of insertion. - * - The underlying StopTimeCalculator should make proper use of this information and - * *extend* the stop task here in the insertion algorithm. - * - This should equally be done in RequestInsertionScheduler where the stop task must be - * extended according to the information given by StopTimeCalculator. - * - The DrtStopActvity should not be based on the endTime of the stop that is known at - * time of construction, but it should depend dynamically on the end time (basically, - * like a stay task). - * - Finally, all of this means that by adding a pickup to an ongoing stop and, potentially, - * shifting the end time of the task, we may shift already assigned pick-ups beyond their - * latestDepartureTime. Testing for this requires to extend the definition of slack times. - * Specifically, VehicleEntryFactoryImpl only generates the slack *after* the start of the - * schedule. In an empty schedule this is the slack from the initial stay to the end time - * of the vehicle service. Hence, we need to introduce a "start slack" that indicates the - * slack at the very beginning of the schedule (i.e., how far can I extend the start stop - * if there is any to maintain a valid timing). - */ - - double insertionTime = stopTask.getBeginTime(); - double departureTime = stopTimeCalculator.updateEndTimeForPickup(vEntry.vehicle, stopTask, - insertionTime, request); + // insertion is a soon as possible (now) + double departureTime = stopTimeCalculator.updateEndTimeForPickup(vEntry.vehicle, stopTask, vEntry.createTime, request); double additionalStopDuration = departureTime - stopTask.getEndTime(); return new PickupTimeInfo(departureTime, additionalStopDuration); } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java index 55d381daf01..1316ccde366 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java @@ -27,8 +27,10 @@ import org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.DetourTimeInfo; import org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.PickupDetourInfo; import org.matsim.contrib.drt.passenger.DrtRequest; -import org.matsim.contrib.drt.stops.PassengerStopDurationProvider; +import org.matsim.contrib.drt.schedule.DrtStopTask; +import org.matsim.contrib.drt.schedule.DrtTaskBaseType; import org.matsim.contrib.drt.stops.StopTimeCalculator; +import org.matsim.contrib.dvrp.schedule.Task; import com.google.common.base.MoreObjects; @@ -180,6 +182,11 @@ private void generateDropoffInsertions(DrtRequest request, VehicleEntry vEntry, toPickupDepartureTime + toPickupTT); //TODO stopDuration not included var pickupDetourInfo = detourTimeCalculator.calcPickupDetourInfo(vEntry, pickupInsertion, toPickupTT, fromPickupTT, true, request); + + if (i == 0 && !checkStartSlack(vEntry, request, pickupDetourInfo)) { + // Inserting at schedule start and extending an ongoing stop task further than allowed + return; + } int stopCount = vEntry.stops.size(); // i == j @@ -252,6 +259,26 @@ private Waypoint.Stop currentStop(VehicleEntry entry, int insertionIdx) { private Waypoint.Stop nextStop(VehicleEntry entry, int insertionIdx) { return entry.stops.get(insertionIdx); } + + private boolean checkStartSlack(VehicleEntry vEntry, DrtRequest request, PickupDetourInfo pickupDetourInfo) { + if (vEntry.start.task.isEmpty()) { + return true; + } + + Task startTask = vEntry.start.task.get(); + + if (!DrtTaskBaseType.STOP.isBaseTypeOf(startTask)) { + return true; + } + + DrtStopTask stopTask = (DrtStopTask) startTask; + + if (stopTask.getLink() != request.getFromLink()) { + return true; + } + + return vEntry.getStartSlackTime() >= pickupDetourInfo.departureTime - stopTask.getEndTime(); + } private InsertionWithDetourData createInsertionWithDetourData(DrtRequest request, VehicleEntry vehicleEntry, InsertionPoint pickupInsertion, double fromPickupTT, PickupDetourInfo pickupDetourInfo, int dropoffIdx) { diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java index 6d7a375efe7..e646c761df1 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java @@ -21,6 +21,7 @@ package org.matsim.contrib.drt.passenger; import java.util.Map; +import java.util.function.Supplier; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.population.Person; @@ -42,11 +43,11 @@ public class DrtStopActivity extends FirstLastSimStepDynActivity implements Pass private final DynAgent driver; private final Map, ? extends AcceptedDrtRequest> dropoffRequests; private final Map, ? extends AcceptedDrtRequest> pickupRequests; - private final double expectedEndTime; + private final Supplier endTime; private int passengersPickedUp = 0; - public DrtStopActivity(PassengerHandler passengerHandler, DynAgent driver, StayTask task, + public DrtStopActivity(PassengerHandler passengerHandler, DynAgent driver, Supplier endTime, Map, ? extends AcceptedDrtRequest> dropoffRequests, Map, ? extends AcceptedDrtRequest> pickupRequests, String activityType) { super(activityType); @@ -54,12 +55,12 @@ public DrtStopActivity(PassengerHandler passengerHandler, DynAgent driver, StayT this.driver = driver; this.dropoffRequests = dropoffRequests; this.pickupRequests = pickupRequests; - this.expectedEndTime = task.getEndTime(); + this.endTime = endTime; } @Override protected boolean isLastStep(double now) { - return passengersPickedUp == pickupRequests.size() && now >= expectedEndTime; + return passengersPickedUp == pickupRequests.size() && now >= endTime.get(); } @Override @@ -72,7 +73,7 @@ protected void beforeFirstStep(double now) { @Override protected void simStep(double now) { - if (now == expectedEndTime) { + if (now == endTime.get()) { for (var request : pickupRequests.values()) { if (passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now)) { passengersPickedUp++; @@ -83,7 +84,7 @@ protected void simStep(double now) { @Override public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now) { - if (now < expectedEndTime) { + if (now < endTime.get()) { return;// pick up only at the end of stop activity } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java index 9f184486755..5f40c9a80f6 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/DefaultRequestInsertionScheduler.java @@ -126,6 +126,7 @@ private void verifyTimes(String messageStart, double timeFromInsertionData, doub } private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetourData insertionWithDetourData) { + final double now = timer.getTimeOfDay(); var insertion = insertionWithDetourData.insertion; VehicleEntry vehicleEntry = insertion.vehicleEntry; Schedule schedule = vehicleEntry.vehicle.getSchedule(); @@ -164,7 +165,6 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour stayTask.setEndTime(stayTask.getBeginTime());// could get later removed with ScheduleTimingUpdater } else if (STAY.isBaseTypeOf(currentTask)) { stayTask = (DrtStayTask)currentTask; // ongoing stay task - double now = timer.getTimeOfDay(); if (stayTask.getEndTime() > now) { // stop stay task; a new stop/drive task can be inserted now stayTask.setEndTime(now); } @@ -179,15 +179,9 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour // add pickup request to stop task stopTask.addPickupRequest(request); - /* - * TODO: insertionTime should be set to "now" here to avoid adding pickups to - * ongoing tasks "for free" and generating requests with zero wait time. See - * InsertionDetourTimeCalculator.calculatePickupIfSameLink for more details. - */ - - double insertionTime = stopTask.getBeginTime(); + // potentially extend task stopTask.setEndTime(stopTimeCalculator.updateEndTimeForPickup(vehicleEntry.vehicle, stopTask, - insertionTime, request.getRequest())); + now, request.getRequest())); /// ADDED //// TODO this is copied, but has not been updated !!!!!!!!!!!!!!! @@ -285,6 +279,7 @@ private DrtStopTask insertPickup(AcceptedDrtRequest request, InsertionWithDetour private DrtStopTask insertDropoff(AcceptedDrtRequest request, InsertionWithDetourData insertionWithDetourData, DrtStopTask pickupTask) { + final double now = timer.getTimeOfDay(); var insertion = insertionWithDetourData.insertion; VehicleEntry vehicleEntry = insertion.vehicleEntry; Schedule schedule = vehicleEntry.vehicle.getSchedule(); @@ -303,15 +298,9 @@ private DrtStopTask insertDropoff(AcceptedDrtRequest request, InsertionWithDetou // add dropoff request to stop task, and extend the stop task (when incremental stop task duration is used) stopTask.addDropoffRequest(request); - /* - * TODO: insertionTime should be set to "now" here to avoid adding pickups to - * ongoing tasks "for free" and generating requests with zero wait time. See - * InsertionDetourTimeCalculator.calculatePickupIfSameLink for more details. - */ - - double insertionTime = stopTask.getBeginTime(); + // potentially extend task stopTask.setEndTime(stopTimeCalculator.updateEndTimeForDropoff(vehicleEntry.vehicle, stopTask, - insertionTime, request.getRequest())); + now, request.getRequest())); scheduleTimingUpdater.updateTimingsStartingFromTaskIdx(vehicleEntry.vehicle, stopTask.getTaskIdx() + 1, stopTask.getEndTime()); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java index a1ced347d8a..1d57080e521 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/vrpagent/DrtActionCreator.java @@ -63,7 +63,7 @@ public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now case STOP: DrtStopTask t = (DrtStopTask)task; - return new DrtStopActivity(passengerHandler, dynAgent, t, t.getDropoffRequests(), t.getPickupRequests(), + return new DrtStopActivity(passengerHandler, dynAgent, t::getEndTime, t.getDropoffRequests(), t.getPickupRequests(), DRT_STOP_NAME); case STAY: diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java index 45fae689ef2..79bde9d12c8 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/VehicleDataEntryFactoryImplTest.java @@ -49,34 +49,46 @@ public class VehicleDataEntryFactoryImplTest { @Test public void computeSlackTimes_withStops() { //final stay task not started - vehicle slack time is 50 - assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop0, stop1 })).containsExactly(20, 30, 50); + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop0, stop1 }, null)).containsExactly(20, 20, 30, 50); //final stay task not started - vehicle slack time is 25 and limits the slack times at stop1 - assertThat(computeSlackTimes(vehicle(500, 475), 100, new Stop[] { stop0, stop1 })).containsExactly(20, 25, 25); + assertThat(computeSlackTimes(vehicle(500, 475), 100, new Stop[] { stop0, stop1 }, null)).containsExactly(20, 20, 25, 25); //final stay task not started - vehicle slack time is 10 and limits the slack times at all stops - assertThat(computeSlackTimes(vehicle(500, 490), 100, new Stop[] { stop0, stop1 })).containsExactly(10, 10, 10); + assertThat(computeSlackTimes(vehicle(500, 490), 100, new Stop[] { stop0, stop1 }, null)).containsExactly(10, 10, 10, 10); } @Test public void computeSlackTimes_withoutStops() { //final stay task not started yet - vehicle slack time is 10 - assertThat(computeSlackTimes(vehicle(500, 490), 485, new Stop[] {})).containsExactly(10); + assertThat(computeSlackTimes(vehicle(500, 490), 485, new Stop[] {}, null)).containsExactly(10, 10); //final stay task just started - vehicle slack time is 10 - assertThat(computeSlackTimes(vehicle(500, 490), 490, new Stop[] {})).containsExactly(10); + assertThat(computeSlackTimes(vehicle(500, 490), 490, new Stop[] {}, null)).containsExactly(10, 10); //final stay task half completed - vehicle slack time is 5 - assertThat(computeSlackTimes(vehicle(500, 490), 495, new Stop[] {})).containsExactly(5); + assertThat(computeSlackTimes(vehicle(500, 490), 495, new Stop[] {}, null)).containsExactly(5, 5); //final stay task just completed - vehicle slack time is 0 - assertThat(computeSlackTimes(vehicle(500, 490), 500, new Stop[] {})).containsExactly(0); + assertThat(computeSlackTimes(vehicle(500, 490), 500, new Stop[] {}, null)).containsExactly(0, 0); //final stay task started, but delayed - vehicle slack time is 0 - assertThat(computeSlackTimes(vehicle(500, 510), 510, new Stop[] {})).containsExactly(0); + assertThat(computeSlackTimes(vehicle(500, 510), 510, new Stop[] {}, null)).containsExactly(0, 0); //final stay task planned after vehicle end time - vehicle slack time is 0s - assertThat(computeSlackTimes(vehicle(500, 510), 300, new Stop[] {})).containsExactly(0); + assertThat(computeSlackTimes(vehicle(500, 510), 300, new Stop[] {}, null)).containsExactly(0, 0); + } + + @Test + public void computeSlackTimes_withStart() { + //start without stop + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] {}, stop0)).containsExactly(30, 50); + + //start without stop + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] {}, stop1)).containsExactly(30, 50); + + //start with stop + assertThat(computeSlackTimes(vehicle(500, 450), 100, new Stop[] { stop1 }, stop0)).containsExactly(30, 30, 50); } private Stop stop(double beginTime, double latestArrivalTime, double endTime, double latestDepartureTime) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java index cc7fdac6b04..14b8c359ec5 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/BestInsertionFinderTest.java @@ -132,7 +132,7 @@ private void whenInsertionThenCost(InsertionWithDetourData insertion, double cos private InsertionWithDetourData insertion(String vehicleId, int pickupIdx, int dropoffIdx) { var vehicle = mock(DvrpVehicle.class); when(vehicle.getId()).thenReturn(Id.create(vehicleId, DvrpVehicle.class)); - var vehicleEntry = new VehicleEntry(vehicle, null, null, null); + var vehicleEntry = new VehicleEntry(vehicle, null, null, null, 0); var pickupInsertion = new InsertionGenerator.InsertionPoint(pickupIdx, null, null, null); var dropoffInsertion = new InsertionGenerator.InsertionPoint(dropoffIdx, null, null, null); diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java index afb8b62cc58..dcdc662f335 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java @@ -197,7 +197,7 @@ public void acceptedRequest() { var unplannedRequests = requests(request1); double now = 15; - var vehicle1Entry = new VehicleEntry(vehicle1, null, null, null); + var vehicle1Entry = new VehicleEntry(vehicle1, null, null, null, 0); var createEntryCounter = new MutableInt(); VehicleEntry.EntryFactory entryFactory = (vehicle, currentTime) -> { //make sure the right arguments are passed diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java index 22af2a9cf15..f9c25dd60cf 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java @@ -42,7 +42,7 @@ public class InsertionCostCalculatorTest { @Test public void testCalculate() { - VehicleEntry entry = entry(new double[] { 20, 50 }); + VehicleEntry entry = entry(new double[] { 20, 20, 50 }); var insertion = insertion(entry, 0, 1); //feasible solution @@ -71,7 +71,7 @@ private void assertCalculate(Insertion insertion, DetourTimeInfo detourTimeInfo, } private VehicleEntry entry(double[] slackTimes) { - return new VehicleEntry(null, null, null, slackTimes); + return new VehicleEntry(null, null, null, slackTimes, 0); } private Link link(String id) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java index 010e531b996..16e708701fd 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorTest.java @@ -234,7 +234,7 @@ private Waypoint.Stop stop(double beginTime, Link link) { } private VehicleEntry entry(Waypoint.Start start, Waypoint.Stop... stops) { - return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null); + return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null, 0); } private InsertionDetourData detourData(double toPickupTT, double fromPickupTT, double toDropoffTT, diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java index 37f95db5c83..8f9b037a7dc 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionDetourTimeCalculatorWithVariableDurationTest.java @@ -274,7 +274,7 @@ private Waypoint.Stop stop(double beginTime, Link link) { } private VehicleEntry entry(Waypoint.Start start, Waypoint.Stop... stops) { - return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null); + return new VehicleEntry(null, start, ImmutableList.copyOf(stops), null, 0); } private InsertionWithDetourData insertion(VehicleEntry entry, int pickupIdx, int dropoffIdx, diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java index 22da5b22295..4509e5f90ad 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java @@ -207,10 +207,10 @@ public void startEmpty_twoStops_notFullBetweenStops_tightSlackTimes() { Waypoint.Stop stop0 = stop(start.time + TIME_REPLACED_DRIVE, link("stop0"), 1);//pick up 1 pax Waypoint.Stop stop1 = stop(stop0.getDepartureTime() + TIME_REPLACED_DRIVE, link("stop1"), 0);//drop off 1 pax - double[] slackTimes = { 0, // impossible insertions: 00, 01, 02 (pickup at 0 is not possible) + double[] slackTimes = { 0, 0, // impossible insertions: 00, 01, 02 (pickup at 0 is not possible) 500, // additional impossible insertions: 11 (too long total detour); however 12 is possible 1000 }; // 22 is possible - VehicleEntry entry = new VehicleEntry(vehicle, start, ImmutableList.of(stop0, stop1), slackTimes); + VehicleEntry entry = new VehicleEntry(vehicle, start, ImmutableList.of(stop0, stop1), slackTimes, 0); var insertions = new ArrayList(); {//12 @@ -399,8 +399,8 @@ private Waypoint.Stop stop(double beginTime, Link link, int outgoingOccupancy) { } private VehicleEntry entry(Waypoint.Start start, Waypoint.Stop... stops) { - var slackTimes = new double[stops.length + 1]; + var slackTimes = new double[stops.length + 2]; Arrays.fill(slackTimes, Double.POSITIVE_INFINITY); - return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), slackTimes); + return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), slackTimes, 0); } } diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java index 37ac3a13a1d..283f69b0cf3 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/DetourPathDataCacheTest.java @@ -132,7 +132,7 @@ private Link link(String id) { private VehicleEntry entry(Link startLink, Link... stopLinks) { return new VehicleEntry(null, new Waypoint.Start(null, startLink, 0, 0), - Arrays.stream(stopLinks).map(this::stop).collect(ImmutableList.toImmutableList()), null); + Arrays.stream(stopLinks).map(this::stop).collect(ImmutableList.toImmutableList()), null, 0); } private Waypoint.Stop stop(Link link) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java index 8711a88a795..e971c5a312e 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/extensive/KNearestInsertionsAtEndFilterTest.java @@ -124,7 +124,7 @@ private Waypoint.Stop stop(double endTime) { private VehicleEntry vehicleEntry(String id, Waypoint.Start start, Waypoint.Stop... stops) { var vehicle = mock(DvrpVehicle.class); when(vehicle.getId()).thenReturn(Id.create(id, DvrpVehicle.class)); - return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), null); + return new VehicleEntry(vehicle, start, ImmutableList.copyOf(stops), null, 0); } private List filterOneInsertionAtEnd(InsertionWithDetourData... insertions) { diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java index 0c18bbb7fe4..3d28a85cffd 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/run/examples/RunDrtExampleIT.java @@ -38,6 +38,7 @@ import org.matsim.contrib.drt.optimizer.insertion.selective.SelectiveInsertionSearchParams; import org.matsim.contrib.drt.run.DrtControlerCreator; import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup; +import org.matsim.contrib.drt.stops.CorrectedStopTimeCalculator; import org.matsim.contrib.drt.stops.CumulativeStopTimeCalculator; import org.matsim.contrib.drt.stops.MinimumStopDurationAdapter; import org.matsim.contrib.drt.stops.StaticPassengerStopDurationProvider; @@ -212,6 +213,41 @@ public void testRunDrtStopbasedExample() { verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats); } + + @Test + public void testRunDrtStopbasedExampleWithFlexibleStopDuration() { + Id.resetCaches(); + URL configUrl = IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("mielec"), + "mielec_stop_based_drt_config.xml"); + Config config = ConfigUtils.loadConfig(configUrl, new MultiModeDrtConfigGroup(), new DvrpConfigGroup(), + new OTFVisConfigGroup()); + + config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + + Controler controller = DrtControlerCreator.createControler(config, false); + + // This snippet adds the correction against wait times smaller than the defined stopDuration + controller.addOverridingModule(new AbstractDvrpModeModule("drt") { + @Override + public void install() { + StopTimeCalculator stopTimeCalculator = new CorrectedStopTimeCalculator(60.0); + bindModal(StopTimeCalculator.class).toInstance(stopTimeCalculator); + } + }); + + controller.run(); + + var expectedStats = Stats.newBuilder() + .rejectionRate(0.05) + .rejections(17) + .waitAverage(261.88) + .inVehicleTravelTimeMean(376.04) + .totalTravelTimeMean(637.93) + .build(); + + verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats); + } @Test public void testRunServiceAreabasedExampleWithSpeedUp() { @@ -263,9 +299,9 @@ public void install() { var expectedStats = Stats.newBuilder() .rejectionRate(0.04) .rejections(16) - .waitAverage(278.11) + .waitAverage(278.92) .inVehicleTravelTimeMean(384.6) - .totalTravelTimeMean(662.71) + .totalTravelTimeMean(663.52) .build(); verifyDrtCustomerStatsCloseToExpectedStats(utils.getOutputDirectory(), expectedStats); From 27e3253448f9be39ed55081ff09c9f3a526a878f Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 8 Sep 2023 17:41:39 +0200 Subject: [PATCH 100/258] Rearrange RailsimIntegrationTest.java --- .../integration/RailsimIntegrationTest.java | 243 +++++++++--------- 1 file changed, 120 insertions(+), 123 deletions(-) diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 5c581dd453a..39fc7d4bb65 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -42,126 +42,11 @@ public class RailsimIntegrationTest { @Rule public MatsimTestUtils utils = new MatsimTestUtils(); - @Test - public void testScenarioKelheim() { - - URL base = ExamplesUtils.getTestScenarioURL("kelheim"); - - Config config = ConfigUtils.loadConfig(IOUtils.extendUrl(base, "config.xml")); - config.controler().setLastIteration(0); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setCreateGraphs(false); - config.controler().setDumpDataAtEnd(false); - - Scenario scenario = ScenarioUtils.loadScenario(config); - - // Convert all pt to rail - for (Link link : scenario.getNetwork().getLinks().values()) { - if (link.getAllowedModes().contains(TransportMode.car)) - link.setAllowedModes(Set.of(TransportMode.car, "ride", "freight")); - - if (link.getAllowedModes().contains(TransportMode.pt)) { - link.setFreespeed(50); - link.setAllowedModes(Set.of("rail")); - } - } - - // Maximum velocity must be configured - for (VehicleType type : scenario.getTransitVehicles().getVehicleTypes().values()) { - type.setMaximumVelocity(30); - type.setLength(100); - } - - SnzActivities.addScoringParams(config); - - Controler controler = new Controler(scenario); - controler.addOverridingModule(new RailsimModule()); - controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); - - controler.run(); - } - @Test public void testMicroSimpleBiDirectionalTrack() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microSimpleBiDirectionalTrack")); } - private EventsCollector runSimulation(File scenarioDir) { - return runSimulation(scenarioDir, null); - } - - private EventsCollector runSimulation(File scenarioDir, Consumer f) { - Config config = ConfigUtils.loadConfig(new File(scenarioDir, "config.xml").toString()); - - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setDumpDataAtEnd(true); - config.controler().setCreateGraphs(false); - config.controler().setLastIteration(0); - - Scenario scenario = ScenarioUtils.loadScenario(config); - - if (f != null) - f.accept(scenario); - - Controler controler = new Controler(scenario); - controler.addOverridingModule(new RailsimModule()); - controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); - - EventsCollector collector = new EventsCollector(); - controler.addOverridingModule(new AbstractModule() { - @Override - public void install() { - this.addEventHandlerBinding().toInstance(collector); - } - }); - - controler.run(); - - return collector; - } - - private double timeToAccelerate(double v0, double v, double a) { - return (v - v0) / a; - } - - private double distanceTravelled(double v0, double a, double t) { - return 0.5 * a * t * t + v0 * t; - } - - private double timeForDistance(double d, double v) { - return d / v; - } - - private void assertTrainState(double time, double speed, double targetSpeed, double acceleration, double headPosition, - List events) { - - RailsimTrainStateEvent prev = null; - for (RailsimTrainStateEvent event : events) { - - if (event.getTime() > Math.ceil(time)) { - Assert.fail(String.format("No matching event found for time %f, speed %f pos %f, Closest event is%s", time, speed, headPosition, prev)); - } - - // If all assertions are true, returns successfully - try { - Assert.assertEquals(Math.ceil(time), event.getTime(), 1e-7); - Assert.assertEquals(speed, event.getSpeed(), 1e-5); - Assert.assertEquals(targetSpeed, event.getTargetSpeed(), 1e-7); - Assert.assertEquals(acceleration, event.getAcceleration(), 1e-5); - Assert.assertEquals(headPosition, event.getHeadPosition(), 1e-5); - return; - } catch (AssertionError e) { - // Check further events in loop - } - - prev = event; - } - } - - private List filterTrainEvents(EventsCollector collector, String train) { - return collector.getEvents().stream().filter(event -> event instanceof RailsimTrainStateEvent).map(event -> (RailsimTrainStateEvent) event).filter(event -> event.getVehicleId().toString().equals(train)).toList(); - } - @Test public void testMesoUniDirectionalVaryingCapacities() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "mesoUniDirectionalVaryingCapacities")); @@ -302,9 +187,8 @@ public void testScenarioMesoGenfBernOneTrain() { // Remove vehicles except the first one Consumer filter = scenario -> { - Set> remove = scenario.getTransitVehicles().getVehicles().values().stream().filter( - v -> !v.getId().toString().equals("Bummelzug_GE_BE_train_0") - ).map(Vehicle::getId).collect(Collectors.toSet()); + Set> remove = scenario.getTransitVehicles().getVehicles().values().stream() + .filter(v -> !v.getId().toString().equals("Bummelzug_GE_BE_train_0")).map(Vehicle::getId).collect(Collectors.toSet()); for (TransitLine line : scenario.getTransitSchedule().getTransitLines().values()) { @@ -313,8 +197,7 @@ public void testScenarioMesoGenfBernOneTrain() { Collection values = new ArrayList<>(route.getDepartures().values()); for (Departure departure : values) { - if (remove.contains(departure.getVehicleId())) - route.removeDeparture(departure); + if (remove.contains(departure.getVehicleId())) route.removeDeparture(departure); } } } @@ -386,8 +269,8 @@ public void testMicroSimpleUniDirectionalTrack() { if (event.getEventType().equals(VehicleArrivesAtFacilityEvent.EVENT_TYPE)) { VehicleArrivesAtFacilityEvent vehicleArrivesEvent = (VehicleArrivesAtFacilityEvent) event; - if (vehicleArrivesEvent.getVehicleId().toString().equals("train1") && - vehicleArrivesEvent.getFacilityId().toString().equals("stop_3-4")) { + if (vehicleArrivesEvent.getVehicleId().toString().equals("train1") && vehicleArrivesEvent.getFacilityId().toString() + .equals("stop_3-4")) { Assert.assertEquals("The arrival time of train1 at stop_3-4 has changed.", 29594., event.getTime(), MatsimTestUtils.EPSILON); } @@ -410,7 +293,7 @@ public void testMicroStationRerouting() { @Test public void testMicroStationReroutingConcurrent() { - Consumer filter = scenario -> { + Consumer filter = scenario -> { TransitScheduleFactory f = scenario.getTransitSchedule().getFactory(); @@ -434,8 +317,122 @@ public void testMicroStationReroutingConcurrent() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microStationRerouting"), filter); } + @Test + public void testScenarioKelheim() { + + URL base = ExamplesUtils.getTestScenarioURL("kelheim"); + + Config config = ConfigUtils.loadConfig(IOUtils.extendUrl(base, "config.xml")); + config.controler().setLastIteration(0); + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setCreateGraphs(false); + config.controler().setDumpDataAtEnd(false); + + Scenario scenario = ScenarioUtils.loadScenario(config); + + // Convert all pt to rail + for (Link link : scenario.getNetwork().getLinks().values()) { + if (link.getAllowedModes().contains(TransportMode.car)) link.setAllowedModes(Set.of(TransportMode.car, "ride", "freight")); + + if (link.getAllowedModes().contains(TransportMode.pt)) { + link.setFreespeed(50); + link.setAllowedModes(Set.of("rail")); + } + } + + // Maximum velocity must be configured + for (VehicleType type : scenario.getTransitVehicles().getVehicleTypes().values()) { + type.setMaximumVelocity(30); + type.setLength(100); + } + + SnzActivities.addScoringParams(config); + + Controler controler = new Controler(scenario); + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); + + controler.run(); + } + @Test public void testScenarioMicroMesoCombination() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "scenarioMicroMesoCombination")); } + + private EventsCollector runSimulation(File scenarioDir) { + return runSimulation(scenarioDir, null); + } + + private EventsCollector runSimulation(File scenarioDir, Consumer f) { + Config config = ConfigUtils.loadConfig(new File(scenarioDir, "config.xml").toString()); + + config.controler().setOutputDirectory(utils.getOutputDirectory()); + config.controler().setDumpDataAtEnd(true); + config.controler().setCreateGraphs(false); + config.controler().setLastIteration(0); + + Scenario scenario = ScenarioUtils.loadScenario(config); + + if (f != null) f.accept(scenario); + + Controler controler = new Controler(scenario); + controler.addOverridingModule(new RailsimModule()); + controler.configureQSimComponents(components -> new RailsimQSimModule().configure(components)); + + EventsCollector collector = new EventsCollector(); + controler.addOverridingModule(new AbstractModule() { + @Override + public void install() { + this.addEventHandlerBinding().toInstance(collector); + } + }); + + controler.run(); + + return collector; + } + + private double timeToAccelerate(double v0, double v, double a) { + return (v - v0) / a; + } + + private double distanceTravelled(double v0, double a, double t) { + return 0.5 * a * t * t + v0 * t; + } + + private double timeForDistance(double d, double v) { + return d / v; + } + + private void assertTrainState(double time, double speed, double targetSpeed, double acceleration, double headPosition, List events) { + + RailsimTrainStateEvent prev = null; + for (RailsimTrainStateEvent event : events) { + + if (event.getTime() > Math.ceil(time)) { + Assert.fail( + String.format("No matching event found for time %f, speed %f pos %f, Closest event is%s", time, speed, headPosition, prev)); + } + + // If all assertions are true, returns successfully + try { + Assert.assertEquals(Math.ceil(time), event.getTime(), 1e-7); + Assert.assertEquals(speed, event.getSpeed(), 1e-5); + Assert.assertEquals(targetSpeed, event.getTargetSpeed(), 1e-7); + Assert.assertEquals(acceleration, event.getAcceleration(), 1e-5); + Assert.assertEquals(headPosition, event.getHeadPosition(), 1e-5); + return; + } catch (AssertionError e) { + // Check further events in loop + } + + prev = event; + } + } + + private List filterTrainEvents(EventsCollector collector, String train) { + return collector.getEvents().stream().filter(event -> event instanceof RailsimTrainStateEvent).map(event -> (RailsimTrainStateEvent) event) + .filter(event -> event.getVehicleId().toString().equals(train)).toList(); + } } From 8eb86115547b1aaee4d15ba67e498236758a41fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 16:13:44 +0000 Subject: [PATCH 101/258] build(deps): bump com.google.protobuf:protobuf-java Bumps [com.google.protobuf:protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.24.2 to 3.24.3. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.24.2...v3.24.3) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- contribs/hybridsim/pom.xml | 2 +- contribs/protobuf/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/hybridsim/pom.xml b/contribs/hybridsim/pom.xml index ed4777814fb..d340e18bbb6 100644 --- a/contribs/hybridsim/pom.xml +++ b/contribs/hybridsim/pom.xml @@ -10,7 +10,7 @@ hybridsim - 3.24.2 + 3.24.3 1.58.0 diff --git a/contribs/protobuf/pom.xml b/contribs/protobuf/pom.xml index b75f9d59200..7c8509d4e15 100644 --- a/contribs/protobuf/pom.xml +++ b/contribs/protobuf/pom.xml @@ -11,7 +11,7 @@ protobuf - 3.24.2 + 3.24.3 From 6b25389a8a7901fd935c86837803e822b5f8a162 Mon Sep 17 00:00:00 2001 From: marecabo <23156476+marecabo@users.noreply.github.com> Date: Sun, 10 Sep 2023 12:26:23 +0200 Subject: [PATCH 102/258] Add support for LocalTime, LocalDate and LocalDateTime to ReflectiveConfigGroup parameters --- .../core/config/ReflectiveConfigGroup.java | 14 +++++++++++-- .../config/ReflectiveConfigGroupTest.java | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/matsim/src/main/java/org/matsim/core/config/ReflectiveConfigGroup.java b/matsim/src/main/java/org/matsim/core/config/ReflectiveConfigGroup.java index 8181560dd17..1301c6daeb1 100644 --- a/matsim/src/main/java/org/matsim/core/config/ReflectiveConfigGroup.java +++ b/matsim/src/main/java/org/matsim/core/config/ReflectiveConfigGroup.java @@ -32,6 +32,9 @@ import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -234,10 +237,11 @@ private static void checkParamFieldValidity(Field field) { private static final Set> ALLOWED_PARAMETER_TYPES = Set.of(String.class, Float.class, Double.class, Integer.class, Long.class, Boolean.class, Character.class, Byte.class, Short.class, Float.TYPE, Double.TYPE, - Integer.TYPE, Long.TYPE, Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE); + Integer.TYPE, Long.TYPE, Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, LocalTime.class, + LocalDate.class, LocalDateTime.class); private static final String HINT = " Valid types are String, primitive types and their wrapper classes," - + " enumerations, List and Set." + + " enumerations, List and Set, LocalTime, LocalDate, LocalDateTime" + " Other types are fine as parameters, but you will need to implement conversion strategies" + " in corresponding StringGetters andStringSetters."; @@ -342,6 +346,12 @@ private Object fromString(String value, Class type, @Nullable Field paramFiel return null; } else if (type.equals(String.class)) { return value; + } else if (type.equals(LocalTime.class)) { + return LocalTime.parse(value); + } else if (type.equals(LocalDate.class)) { + return LocalDate.parse(value); + } else if (type.equals(LocalDateTime.class)) { + return LocalDateTime.parse(value); } else if (type.equals(Float.class) || type.equals(Float.TYPE)) { return Float.parseFloat(value); } else if (type.equals(Double.class) || type.equals(Double.TYPE)) { diff --git a/matsim/src/test/java/org/matsim/core/config/ReflectiveConfigGroupTest.java b/matsim/src/test/java/org/matsim/core/config/ReflectiveConfigGroupTest.java index fcc54693e19..a662f53d5ad 100644 --- a/matsim/src/test/java/org/matsim/core/config/ReflectiveConfigGroupTest.java +++ b/matsim/src/test/java/org/matsim/core/config/ReflectiveConfigGroupTest.java @@ -22,6 +22,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -61,6 +64,9 @@ public void testDumpAndRead() { dumpedModule.charField = 'z'; dumpedModule.byteField = 78; dumpedModule.booleanField = true; + dumpedModule.localTimeField = LocalTime.of(23, 59, 59); + dumpedModule.localDateField = LocalDate.of(2022, 12, 31); + dumpedModule.localDateTimeField = LocalDateTime.of(2022, 12, 31, 23, 59, 59); dumpedModule.enumListField = List.of(MyEnum.VALUE1, MyEnum.VALUE2); dumpedModule.enumSetField = Set.of(MyEnum.VALUE2); dumpedModule.setField = ImmutableSet.of("a", "b", "c"); @@ -154,6 +160,9 @@ public void testComments() { expectedComments.put("charField", "char"); expectedComments.put("byteField", "byte"); expectedComments.put("booleanField", "boolean"); + expectedComments.put("localTimeField", "local time"); + expectedComments.put("localDateField", "local date"); + expectedComments.put("localDateTimeField", "local datetime"); expectedComments.put("enumField", "Possible values: VALUE1,VALUE2"); expectedComments.put("enumListField", "list of enum"); expectedComments.put("enumSetField", "set of enum"); @@ -439,6 +448,18 @@ private static class MyModule extends ReflectiveConfigGroup { @Parameter private boolean booleanField; + @Comment("local time") + @Parameter + private LocalTime localTimeField; + + @Comment("local date") + @Parameter + private LocalDate localDateField; + + @Comment("local datetime") + @Parameter + private LocalDateTime localDateTimeField; + @Comment("set") @Parameter private Set setField; From 0c08774f0fd48981eefae81afb27352d8db12ba8 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Mon, 11 Sep 2023 13:53:05 +0200 Subject: [PATCH 103/258] Code cleanup - Remove unused maven dependencies. - Remove commented code and solved TODOs. - Rename config group parameters to shorter names. - Solve visibility scope issues by setting RailsimCalc to package-private. - Update specifications to current state. --- contribs/railsim/README.md | 7 +- contribs/railsim/docs/deadlock-avoidance.md | 2 +- contribs/railsim/docs/events-specification.md | 9 +- .../railsim/docs/network-specification.md | 122 ++++----- contribs/railsim/docs/train-specification.md | 22 +- contribs/railsim/pom.xml | 12 - .../matsim/contrib/railsim/RailsimUtils.java | 4 +- .../railsim/config/RailsimConfigGroup.java | 16 +- .../RailsimTrainStateEventMapper.java | 10 +- .../events/RailsimTrainStateEvent.java | 20 +- .../railsim/qsimengine/FuzzyUtils.java | 3 - .../contrib/railsim/qsimengine/RailLink.java | 6 +- .../qsimengine/RailResourceManager.java | 3 +- .../railsim/qsimengine/RailsimCalc.java | 8 +- .../qsimengine/RailsimDriverAgentFactory.java | 3 +- .../railsim/qsimengine/RailsimEngine.java | 12 +- .../railsim/qsimengine/RailsimQSimEngine.java | 31 +-- .../contrib/railsim/qsimengine/TrainInfo.java | 3 - .../railsim/qsimengine/UpdateEvent.java | 1 + .../disposition/TrainDisposition.java | 2 - .../integration/RailsimIntegrationTest.java | 1 - .../railsim/qsimengine/RailsimCalcTest.java | 3 +- .../railsim/qsimengine/RailsimEngineTest.java | 19 +- .../railsim/qsimengine/RailsimTestUtils.java | 20 +- .../mesoStationCapacityOne/config.xml | 34 +-- .../transitSchedule.xml | 8 +- .../transitVehicles.xml | 2 +- .../mesoStationCapacityTwo/config.xml | 34 +-- .../mesoStationCapacityTwo/trainNetwork.xml | 44 ++-- .../transitSchedule.xml | 8 +- .../transitVehicles.xml | 2 +- .../integration/mesoStations/config.xml | 34 +-- .../integration/mesoStations/trainNetwork.xml | 52 ++-- .../mesoStations/transitSchedule.xml | 10 +- .../mesoStations/transitVehicles.xml | 6 +- .../integration/mesoTwoSources/config.xml | 34 +-- .../mesoTwoSources/trainNetwork.xml | 51 ++-- .../mesoTwoSources/transitVehicles.xml | 2 +- .../mesoTwoSourcesComplex/config.xml | 34 +-- .../mesoTwoSourcesComplex/transitVehicles.xml | 20 +- .../config.xml | 36 +-- .../trainNetwork.xml | 28 +-- .../transitSchedule.xml | 12 +- .../transitVehicles.xml | 2 +- .../integration/microJunctionCross/config.xml | 34 +-- .../microJunctionCross/transitVehicles.xml | 2 +- .../integration/microJunctionY/config.xml | 34 +-- .../microJunctionY/transitSchedule.xml | 6 +- .../microJunctionY/transitVehicles.xml | 10 +- .../microSimpleBiDirectionalTrack/config.xml | 44 ++-- .../trainNetwork.xml | 6 +- .../transitVehicles.xml | 2 +- .../microSimpleUniDirectionalTrack/config.xml | 48 ++-- .../transitVehicles.xml | 2 +- .../microStationDifferentLink/config.xml | 34 +-- .../trainNetwork.xml | 238 +++++++++--------- .../transitSchedule.xml | 8 +- .../transitVehicles.xml | 2 +- .../microStationRerouting/config.xml | 34 +-- .../microStationRerouting/trainNetwork.xml | 196 +++++++-------- .../microStationRerouting/transitSchedule.xml | 12 +- .../microStationRerouting/transitVehicles.xml | 2 +- .../microStationSameLink/config.xml | 34 +-- .../microStationSameLink/trainNetwork.xml | 238 +++++++++--------- .../microStationSameLink/transitSchedule.xml | 10 +- .../microStationSameLink/transitVehicles.xml | 2 +- .../microThreeUniDirectionalTracks/config.xml | 34 +-- .../transitVehicles.xml | 8 +- .../microTrackOppositeTraffic/config.xml | 34 +-- .../transitVehicles.xml | 2 +- .../microTrackOppositeTrafficMany/config.xml | 34 +-- .../transitVehicles.xml | 22 +- .../config.xml | 34 +-- .../trainNetwork.xml | 89 ++++--- .../transitVehicles.xml | 2 +- .../config.xml | 34 +-- .../trainNetwork.xml | 86 +++---- .../transitVehicles.xml | 2 +- .../scenarioMesoGenfBern/config.xml | 32 +-- .../scenarioMicroMesoCombination/config.xml | 4 +- .../transitVehicles.xml | 2 +- .../railsim/qsimengine/networkMesoUni.xml | 29 +-- .../railsim/qsimengine/networkMicroBi.xml | 8 +- .../railsim/qsimengine/networkMicroUni.xml | 95 ++++--- 84 files changed, 1135 insertions(+), 1206 deletions(-) diff --git a/contribs/railsim/README.md b/contribs/railsim/README.md index 2344b5a17db..baec9278bb1 100644 --- a/contribs/railsim/README.md +++ b/contribs/railsim/README.md @@ -10,10 +10,11 @@ the *railsim* contrib provides a custom QSim-Engine to simulate trains: - Trains are spatially expanded along several links. Additional events indicate when the end of the train leaves a link. - Trains accelerate and decelerate based on predefined vehicle attributes (along a single link or along several links). -- The infrastructure ahead of each train is blocked (reserved train path) depending on the braking distance which is computed based on the - vehicle-specific deceleration and the current speed. +- The infrastructure ahead of each train is blocked (reserved train path) depending on the braking distance which is + computed based on the vehicle-specific deceleration and the current speed. - Capacity effects are modeled at the level of resources. A resource consists of one link or several links. -- Trains may deviate from the network route given in the schedule, e.g. to avoid a blocked track (dispatching, disposition). +- Trains may deviate from the network route given in the schedule, e.g. to avoid a blocked track (dispatching, + disposition). ## Configuration diff --git a/contribs/railsim/docs/deadlock-avoidance.md b/contribs/railsim/docs/deadlock-avoidance.md index c72fe03b4b0..5b8a2229823 100644 --- a/contribs/railsim/docs/deadlock-avoidance.md +++ b/contribs/railsim/docs/deadlock-avoidance.md @@ -1,4 +1,4 @@ -# Deadlock avoidance +# Deadlock-Avoidance Deadlocks can occur on two scales in the mesoscopic approach of railsim: diff --git a/contribs/railsim/docs/events-specification.md b/contribs/railsim/docs/events-specification.md index 26e9bc0b10c..e960461b9fe 100644 --- a/contribs/railsim/docs/events-specification.md +++ b/contribs/railsim/docs/events-specification.md @@ -8,8 +8,8 @@ All the additional events use the prefix `railsim`. ### RailsimLinkStateChangeEvent -Instead of `TrainPathEntersLinkEvent`, we have a generic `RailsimLinkStateChangeEvent` that includes information about -the new state of the link (or even the track of a multi-track link). +The generic `RailsimLinkStateChangeEvent` includes information about the new state of a link (or even the track of a +multi-track link). Attributes: @@ -19,7 +19,6 @@ Attributes: ### RailsimTrainLeavesLinkEvent -Similar to the existing `TrainLeavesLinkEvent`. One could argue that setting the link state to `free` would imply the same. I (mr) would still say it makes sense to have it separate, because depending on the implementation, a link could remain blocked for a longer time even if the train has already passed (e.g. minimum headway time). @@ -29,8 +28,8 @@ compatibility with existing analysis and visualization tools. ### RailsimTrainStateEvent -This event is emitted every time there is a position update for a train. -This event contains detailed information about the trains position on a single link. +This event is emitted every time there is a position update for a train and contains detailed information about the +trains position on a single link. ### RailsimDetourEvent diff --git a/contribs/railsim/docs/network-specification.md b/contribs/railsim/docs/network-specification.md index 7964e38bf3a..956de2e3da3 100644 --- a/contribs/railsim/docs/network-specification.md +++ b/contribs/railsim/docs/network-specification.md @@ -1,13 +1,15 @@ -# Network-Specification for railsim +# Network-Specification ## Introduction -As trains interact differently with links than regular cars, the typical attributes like `capacity`, `lanes` and even -`freespeed` are not suitable for describing rail infrastructure. +As trains interact differently with links than regular cars, the typical attributes like `capacity` and `lanes` are not +suitable for describing rail infrastructure. railsim uses custom link attributes to describe the essential parts of the rail infrastructure. This document specifies these custom attributes. +For the maximum allowed speed on a link, the normal `freespeed` attribute is used. + ## Specification We use the prefix `railsim` where it is appropriate. @@ -26,9 +28,9 @@ Example: ```xml - - 3 - + + 3 + ``` @@ -65,9 +67,9 @@ Example: ```xml - - true - + + true + ``` @@ -84,10 +86,10 @@ Example: ```xml - - 44.444 - 50.0 - + + 44.444 + 50.0 + ``` @@ -122,18 +124,18 @@ Default value of `railsimCapacity` sets an own railsimResourceId for each track. ```xml - - - AB - - - - - AB - - + + + AB + + + + + AB + + ``` @@ -144,12 +146,12 @@ Default value of `railsimResourceId` sets an own railsimResourceId for each trac ```xml - - - - + + + + ``` @@ -158,24 +160,24 @@ Default value of `railsimResourceId` sets an own railsimResourceId for each trac ```xml - - - - - AB - - - - - AB - - - - + + + + + AB + + + + + AB + + + + ``` @@ -208,15 +210,17 @@ larger than 1 and corresponds to the number of tracks. ```xml - - - 2 - - - - - 2 - - + + + 2 + + + + + 2 + + ``` diff --git a/contribs/railsim/docs/train-specification.md b/contribs/railsim/docs/train-specification.md index 1d4182a3d30..41fcec36403 100644 --- a/contribs/railsim/docs/train-specification.md +++ b/contribs/railsim/docs/train-specification.md @@ -1,4 +1,4 @@ -# Train-Specification for railsim +# Train-Specification ## Introduction @@ -6,6 +6,8 @@ railsim supports the simulation of specific, train-related behavior, e.g. accele train. In order to simulate this detailed behavior, additional attributes must be specified per train, i.e. per vehicle type in MATSim. +This document specifies these custom attributes. + ## Specification ### TransitVehicle Attributes @@ -26,14 +28,14 @@ The vehicle-specific deceleration. Unit: meters per square-seconds \[m/s²] ```xml - - 0.4 - 0.5 - - - - - - + + 0.4 + 0.5 + + + + + + ``` diff --git a/contribs/railsim/pom.xml b/contribs/railsim/pom.xml index 3f184d39bb0..345b4fd0cea 100644 --- a/contribs/railsim/pom.xml +++ b/contribs/railsim/pom.xml @@ -11,18 +11,6 @@ ch.sbb.matsim.contrib railsim - - org.matsim.contrib - otfvis - 16.0-SNAPSHOT - compile - - - org.matsim.contrib - signals - 16.0-SNAPSHOT - compile - org.assertj diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 0bf46d7beed..34f329cd139 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -108,7 +108,7 @@ public static void setExitLink(Link link, boolean isExit) { * @return the default deceleration time or the vehicle-specific value. */ public static double getTrainDeceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { - double deceleration = railsimConfigGroup.decelerationGlobalDefault; + double deceleration = railsimConfigGroup.decelerationDefault; Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_DECELERATION); return attr != null ? (double) attr : deceleration; } @@ -124,7 +124,7 @@ public static void setTrainDeceleration(VehicleType vehicle, double deceleration * @return the default acceleration time or the vehicle-specific value. */ public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { - double acceleration = railsimConfigGroup.accelerationGlobalDefault; + double acceleration = railsimConfigGroup.accelerationDefault; Object attr = vehicle.getAttributes().getAttribute(VEHICLE_ATTRIBUTE_ACCELERATION); return attr != null ? (double) attr : acceleration; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index e811e8f1bc8..baf3953bff3 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -33,16 +33,16 @@ public class RailsimConfigGroup extends ReflectiveConfigGroup { public static final String GROUP_NAME = "railsim"; @Parameter - @Comment("Comma separated set of modes that are handled by the rail simulation. Defaults to 'rail'.") - public String railNetworkModes = "rail"; + @Comment("Comma separated set of modes that are handled by the railsim qsim engine in the simulation. Defaults to 'rail'.") + public String networkModes = "rail"; @Parameter @Comment("Global acceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_ACCELERATION + ");" + " used to compute the train velocity per link.") - public double accelerationGlobalDefault = 0.5; + public double accelerationDefault = 0.5; @Parameter @Comment("Global deceleration in meters per second^2 which is used if there is no value provided in the vehicle attributes (" + RailsimUtils.VEHICLE_ATTRIBUTE_DECELERATION + ");" + " used to compute the reserved train path and the train velocity per link.") - public double decelerationGlobalDefault = 0.5; + public double decelerationDefault = 0.5; @Parameter @Comment("Time interval in seconds a train has to wait until trying again to request a track reservation if the track was blocked by another train.") @@ -50,20 +50,20 @@ public class RailsimConfigGroup extends ReflectiveConfigGroup { @Parameter @Comment("Maximum time interval in seconds which is used to update the train position update events.") - public double trainPositionMaximumUpdateInterval = 10.; + public double updateInterval = 10.; public RailsimConfigGroup() { super(GROUP_NAME); } - public Set getRailNetworkModes() { - return Set.of(railNetworkModes.split(",")); + public Set getNetworkModes() { + return Set.of(networkModes.split(",")); } @Override protected void checkConsistency(Config config) { super.checkConsistency(config); - for (String mode : getRailNetworkModes()) { + for (String mode : getNetworkModes()) { if (config.qsim().getMainModes().contains(mode)) { throw new IllegalArgumentException(String.format("Railsim mode '%s' must not be a network mode in qsim.", mode)); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java index 4ca57bea622..e362d2ed17c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java @@ -34,13 +34,13 @@ public RailsimTrainStateEvent apply(GenericEvent event) { event.getTime(), Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_EXACT_TIME)), asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_VEHICLE), Vehicle.class), - asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEADLINK), Link.class), - Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEADPOSITION)), - asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TAILLINK), Link.class), - Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TAILPOSITION)), + asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEAD_LINK), Link.class), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_HEAD_POSITION)), + asId(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TAIL_LINK), Link.class), + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TAIL_POSITION)), Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_SPEED)), Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_ACCELERATION)), - Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TARGETSPEED)) + Double.parseDouble(attributes.get(RailsimTrainStateEvent.ATTRIBUTE_TARGET_SPEED)) ); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java index 172e43fd1ad..2e480bdf845 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -17,13 +17,13 @@ public class RailsimTrainStateEvent extends Event implements HasVehicleId { public static final String EVENT_TYPE = "railsimTrainStateEvent"; public static final String ATTRIBUTE_EXACT_TIME = "exactTime"; - public static final String ATTRIBUTE_HEADLINK = "headLink"; - public static final String ATTRIBUTE_HEADPOSITION = "headPosition"; - public static final String ATTRIBUTE_TAILLINK = "tailLink"; - public static final String ATTRIBUTE_TAILPOSITION = "tailPosition"; + public static final String ATTRIBUTE_HEAD_LINK = "headLink"; + public static final String ATTRIBUTE_HEAD_POSITION = "headPosition"; + public static final String ATTRIBUTE_TAIL_LINK = "tailLink"; + public static final String ATTRIBUTE_TAIL_POSITION = "tailPosition"; public static final String ATTRIBUTE_SPEED = "speed"; public static final String ATTRIBUTE_ACCELERATION = "acceleration"; - public static final String ATTRIBUTE_TARGETSPEED = "targetSpeed"; + public static final String ATTRIBUTE_TARGET_SPEED = "targetSpeed"; /** * Exact time with resolution of 0.001s. @@ -101,13 +101,13 @@ public Map getAttributes() { Map attr = super.getAttributes(); attr.put(ATTRIBUTE_EXACT_TIME, String.valueOf(exactTime)); attr.put(ATTRIBUTE_VEHICLE, this.vehicleId.toString()); - attr.put(ATTRIBUTE_HEADLINK, String.valueOf(headLink)); - attr.put(ATTRIBUTE_HEADPOSITION, Double.toString(headPosition)); - attr.put(ATTRIBUTE_TAILLINK, String.valueOf(tailLink)); - attr.put(ATTRIBUTE_TAILPOSITION, Double.toString(tailPosition)); + attr.put(ATTRIBUTE_HEAD_LINK, String.valueOf(headLink)); + attr.put(ATTRIBUTE_HEAD_POSITION, Double.toString(headPosition)); + attr.put(ATTRIBUTE_TAIL_LINK, String.valueOf(tailLink)); + attr.put(ATTRIBUTE_TAIL_POSITION, Double.toString(tailPosition)); attr.put(ATTRIBUTE_SPEED, Double.toString(speed)); attr.put(ATTRIBUTE_ACCELERATION, Double.toString(acceleration)); - attr.put(ATTRIBUTE_TARGETSPEED, Double.toString(targetSpeed)); + attr.put(ATTRIBUTE_TARGET_SPEED, Double.toString(targetSpeed)); return attr; } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java index d9ab3bc5029..5ba5f9485b5 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java @@ -10,7 +10,6 @@ final class FuzzyUtils { private FuzzyUtils() { } - /** * Returns true if two doubles are approximately equal. */ @@ -18,7 +17,6 @@ public static boolean equals(double a, double b) { return a == b || Math.abs(a - b) < EPSILON; } - /** * Returns true if the first double is approximately greater than the second. */ @@ -33,7 +31,6 @@ public static boolean greaterThan(double a, double b) { return a - b > EPSILON; } - /** * Returns true if the first double is approximately less than the second. */ diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index e8bddeb0427..f71b38e3d5f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -35,7 +35,7 @@ public final class RailLink implements HasLinkId { final double minimumHeadwayTime; /** - * Id of the resource this link belongs to. + * ID of the resource this link belongs to. */ @Nullable final Id resource; @@ -75,10 +75,6 @@ public int getNumberOfTracks() { * Returns the allowed freespeed, depending on the context, which is given via driver. */ public double getAllowedFreespeed(MobsimDriverAgent driver) { - - // TODO: additional context information such as transit line is stored in the driver - // TODO: speed depending on vehicle type - return Math.min(freeSpeed, driver.getVehicle().getVehicle().getType().getMaximumVelocity()); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java index 08ab37036d4..01e0b685532 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -29,7 +29,6 @@ public final class RailResourceManager { */ private final Map, RailLink> links; - private final Map, RailResource> resources; @Inject @@ -44,7 +43,7 @@ public RailResourceManager(EventsManager eventsManager, RailsimConfigGroup confi this.eventsManager = eventsManager; this.links = new IdMap<>(Link.class, network.getLinks().size()); - Set modes = config.getRailNetworkModes(); + Set modes = config.getNetworkModes(); for (Map.Entry, ? extends Link> e : network.getLinks().entrySet()) { if (e.getValue().getAllowedModes().stream().anyMatch(modes::contains)) this.links.put(e.getKey(), new RailLink(e.getValue())); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 36a9806fa60..1e4bd53ed7a 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -6,7 +6,7 @@ /** * Utility class holding static calculation methods related to state (updates). */ -public class RailsimCalc { +class RailsimCalc { private RailsimCalc() { } @@ -150,7 +150,7 @@ static double calcTargetSpeedForStop(double dist, double acceleration, double de * @param currentLink the link where the train head is on * @return travel distance after which reservations should be updated. */ - public static double nextLinkReservation(TrainState state, RailLink currentLink) { + static double nextLinkReservation(TrainState state, RailLink currentLink) { // on way to pt stop, no need to reserve anymore if (state.isStop(currentLink.getLinkId()) && FuzzyUtils.lessThan(state.headPosition, currentLink.length)) @@ -194,7 +194,7 @@ public static double nextLinkReservation(TrainState state, RailLink currentLink) /** * Links that need to be blocked or otherwise stop needs to be initiated. */ - public static List calcLinksToBlock(TrainState state, RailLink currentLink) { + static List calcLinksToBlock(TrainState state, RailLink currentLink) { List result = new ArrayList<>(); @@ -231,7 +231,7 @@ public static List calcLinksToBlock(TrainState state, RailLink current * * @param upcoming the upcoming links the train tried to block. */ - public static boolean considerReRouting(List upcoming, RailLink currentLink) { + static boolean considerReRouting(List upcoming, RailLink currentLink) { return currentLink.isEntryLink() || upcoming.stream().anyMatch(RailLink::isEntryLink); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java index b8b29d25614..7278d0fd4ec 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java @@ -27,12 +27,11 @@ public class RailsimDriverAgentFactory implements TransitDriverAgentFactory { private final Set modes; - private final Map, List> stopAreas; @Inject public RailsimDriverAgentFactory(Config config, Scenario scenario) { - this.modes = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class).getRailNetworkModes(); + this.modes = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class).getNetworkModes(); this.stopAreas = scenario.getTransitSchedule().getFacilities().values().stream() .filter(t -> t.getStopAreaId() != null) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index c1865437f2a..477ac4d8c1c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -34,14 +34,10 @@ final class RailsimEngine implements Steppable { private static final Logger log = LogManager.getLogger(RailsimEngine.class); - private final EventsManager eventsManager; private final RailsimConfigGroup config; - private final List activeTrains = new ArrayList<>(); - private final Queue updateQueue = new PriorityQueue<>(); - private final RailResourceManager resources; private final TrainDisposition disposition; @@ -72,7 +68,7 @@ public void doSimStep(double time) { update = updateQueue.peek(); } - if (time % config.trainPositionMaximumUpdateInterval == 0.) { + if (time % config.updateInterval == 0.) { updateAllPositions(time); } } @@ -83,7 +79,7 @@ public void doSimStep(double time) { public void updateAllStates(double time) { // Process all waiting events first - // TODO: Consider potential duplication of position update events here, if time matches trainPositionMaximumUpdateInterval. + // TODO: Consider potential duplication of position update events here, if time matches updateInterval. doSimStep(time); updateAllPositions(time); @@ -405,7 +401,6 @@ private void enterLink(double time, UpdateEvent event) { } } - // On route departure the head link is null createEvent(new LinkLeaveEvent(time, state.driver.getVehicle().getId(), state.headLink)); @@ -458,7 +453,6 @@ private void leaveLink(double time, UpdateEvent event) { unblockTrack(time, state, tailLink); else updateQueue.add(new UpdateEvent(state, tailLink, time)); - } /** @@ -521,7 +515,6 @@ private void updatePosition(double time, UpdateEvent event) { state.targetDecelDist -= dist; } - // When trains are put into the network their tail may be longer than the current link // this assertion may not hold depending on the network, should possibly be removed assert state.routeIdx <= 2 || FuzzyUtils.greaterEqualThan(state.tailPosition, 0) : "Illegal state update. Tail position should not be negative"; @@ -743,7 +736,6 @@ private double retrieveAllowedMaxSpeed(TrainState state) { if (link.getLinkId().equals(state.tailLink)) { break; } - } return maxSpeed; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java index 5ddb6dca27c..88ee0b22cbc 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimEngine.java @@ -58,13 +58,12 @@ public class RailsimQSimEngine implements DepartureHandler, MobsimEngine { private RailsimEngine engine; @Inject - public RailsimQSimEngine(QSim qsim, RailResourceManager res, TrainDisposition disposition, - TransitStopAgentTracker agentTracker) { + public RailsimQSimEngine(QSim qsim, RailResourceManager res, TrainDisposition disposition, TransitStopAgentTracker agentTracker) { this.qsim = qsim; this.config = ConfigUtils.addOrGetModule(qsim.getScenario().getConfig(), RailsimConfigGroup.class); this.res = res; this.disposition = disposition; - this.modes = config.getRailNetworkModes(); + this.modes = config.getNetworkModes(); this.agentTracker = agentTracker; } @@ -91,8 +90,7 @@ public void doSimStep(double time) { @Override public boolean handleDeparture(double now, MobsimAgent agent, Id linkId) { - if (!modes.contains(agent.getMode())) - return false; + if (!modes.contains(agent.getMode())) return false; NetsimLink link = qsim.getNetsimNetwork().getNetsimLink(linkId); @@ -122,27 +120,4 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id linkId) { return engine.handleDeparture(now, driver, linkId, networkRoute); } - - /* - For visualization purposes, the following output should be produced: - - CSV containing time-dependent link-attributes depicting the track-state (needs discussion what we output when a link contains multiple tracks) - - optionally include information which trains (vehicleId) blocked or reserved a track? - - CSV containing time-dependent vehicle-attributes: e.g. current acceleration - - CSV containing XYT data to show head and tail of train, maybe even multiple points (e.g. every 25m) to show length of train? - - how often? every 1min, every 5min? --> config? - - additional attributes? e.g. current speed and acceleration? - - linkEnter/linkLeave-Events for the front of the train - instead of XYT (see above), we could think about if we could create linkEnter/linkLeave-Events for each wagon (estimated every 25m of the train), - but this might not look good as Via still interpolates the position independently. - - We will have to think about deadlock prevention at some stage. - Maybe design some test networks for that, e.g. a single track with a loop on one end; if too many trains try to get into the loop, they can't get out. - This is where the `reserved` track-state might come into play. - - - TODO: - 9. Re routing - 10. Deadlock Prevention - - */ } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java index 18af61e4f6d..b07a55e6a60 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java @@ -1,6 +1,5 @@ package ch.sbb.matsim.contrib.railsim.qsimengine; - import ch.sbb.matsim.contrib.railsim.RailsimUtils; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; import org.matsim.api.core.v01.Id; @@ -19,7 +18,6 @@ record TrainInfo( ) { public TrainInfo(VehicleType vehicle, RailsimConfigGroup config) { - // TODO: this( vehicle.getId(), vehicle.getLength(), @@ -39,6 +37,5 @@ public void checkConsistency() { if (!Double.isFinite(deceleration) || deceleration <= 0) throw new IllegalArgumentException("Train of type " + id + " does not have a finite and positive deceleration."); - } } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 82a547cd72d..b13172fde8f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -81,6 +81,7 @@ enum Type { POSITION, ENTER_LINK, LEAVE_LINK, + // TODO: Never used, should we delete this event type? RELEASE_TRACK, BLOCK_TRACK, WAIT_FOR_RESERVATION, diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java index fc175fbb890..2fa1d2e8178 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java @@ -12,7 +12,6 @@ */ public interface TrainDisposition { - /** * Method invoked when a train is departing. */ @@ -43,5 +42,4 @@ default List requestRoute(double time, RailsimTransitDriverAgent drive */ void unblockRailLink(double time, MobsimDriverAgent driver, RailLink link); - } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 39fc7d4bb65..4b468ea4476 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -93,7 +93,6 @@ public void testMesoUniDirectionalVaryingCapacities() { assertTrainState(currentTime, linkSpeed, linkSpeed, 0, accDistance1 + cruiseDistance2 + accDistance3, train1events); // train can cruise with link speed until it needs to decelerate for next station - double decTime5 = timeToAccelerate(linkSpeed, stationSpeed, -acceleration); double decDistance5 = distanceTravelled(linkSpeed, -acceleration, decTime5); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index 797088d4110..f0da3224272 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -55,8 +55,6 @@ public void testMaxSpeed() { RailsimCalc.SpeedTarget res = RailsimCalc.calcTargetSpeed(dist, 0.5, 0.5, 5, 30, 0); - // System.out.println(res); - double timeDecel = (res.targetSpeed() - f) / 0.5; double distDecel = RailsimCalc.calcTraveledDist(res.targetSpeed(), timeDecel, -0.5); @@ -94,6 +92,7 @@ public void testCalcTargetSpeed() { target = RailsimCalc.calcTargetSpeed(200, 0.5, 0.5, 13, 13, 0); + // TODO: Failing test, fix or set correct value? assertThat(target.decelDist()) .isCloseTo(169, Offset.offset(0.0001)); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index 861026fed34..cf9756c8966 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -27,13 +27,12 @@ public class RailsimEngineTest { private RailsimTestUtils.EventCollector collector; @Before - public void setUp() throws Exception { + public void setUp() { eventsManager = EventsUtils.createEventsManager(); collector = new RailsimTestUtils.EventCollector(); eventsManager.addHandler(collector); eventsManager.initProcessing(); - } private RailsimTestUtils.Holder getTestEngine(String network, @Nullable Consumer f) { @@ -125,8 +124,6 @@ public void testOpposite() { test.doSimStepUntil(600); -// test.debug(collector, "opposite"); - RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 293, 600, 0) .hasTrainState("regio2", 358, 1000, 0); @@ -139,8 +136,6 @@ public void testOpposite() { test.doStateUpdatesUntil(600, 1); -// test.debug(collector, "opposite_detailed"); - RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 293, 600, 0) .hasTrainState("regio2", 358, 1000, 0); @@ -156,8 +151,6 @@ public void testVaryingSpeedOne() { test.doSimStepUntil(10000); -// test.debug(collector, "varyingSpeed"); - RailsimTestUtils.assertThat(collector) .hasTrainState("regio", 7599, 0, 2.7777777) .hasTrainState("regio", 7674, 200, 0); @@ -172,8 +165,6 @@ public void testVaryingSpeedOne() { .hasTrainState("regio", 7599, 0, 2.7777777) .hasTrainState("regio", 7674, 200, 0); -// test.debug(collector, "varyingSpeed_detailed"); - } @Test @@ -193,8 +184,6 @@ public void testVaryingSpeedMany() { .hasTrainState("regio1", 7734, 200, 0) .hasTrainState("regio9", 23107, 200, 0); -// test.debug(collector, "varyingSpeed_many"); - test = getTestEngine("networkMesoUni.xml"); for (int i = 0; i < 10; i++) { @@ -220,8 +209,6 @@ public void testTrainFollowing() { test.doSimStepUntil(5000); -// test.debugFiles(collector, "trainFollowing"); - RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 1138, 1000, 0) .hasTrainState("regio2", 1517, 1000, 0); @@ -232,13 +219,9 @@ public void testTrainFollowing() { test.doStateUpdatesUntil(5000, 1); -// test.debugFiles(collector, "trainFollowing_detailed"); - RailsimTestUtils.assertThat(collector) .hasTrainState("regio1", 1138, 1000, 0) .hasTrainState("regio2", 1517, 1000, 0); - - } } diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java index 9334c62982a..1b4aa3c7de6 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java @@ -17,18 +17,24 @@ import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; -import org.matsim.vehicles.*; +import org.matsim.vehicles.MatsimVehicleReader; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; +import org.matsim.vehicles.VehicleUtils; +import org.matsim.vehicles.Vehicles; import org.mockito.Answers; import org.mockito.Mockito; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; /** * Helper class for test cases. */ public class RailsimTestUtils { - static Map vehicles = new EnumMap<>(TestVehicle.class); static { @@ -47,10 +53,11 @@ public class RailsimTestUtils { /** * Create a departure within the engine. Route will be determined automatically. */ - public static void createDeparture(Holder test, TestVehicle type, String veh, double time, String from, String to) { + public static void createDeparture(Holder test, TestVehicle type, String veh, double time, String from, String to) { DijkstraFactory f = new DijkstraFactory(); - LeastCostPathCalculator lcp = f.createPathCalculator(test.network(), new OnlyTimeDependentTravelDisutility(new FreeSpeedTravelTime()), new FreeSpeedTravelTime()); + LeastCostPathCalculator lcp = f.createPathCalculator(test.network(), new OnlyTimeDependentTravelDisutility(new FreeSpeedTravelTime()), + new FreeSpeedTravelTime()); Link fromLink = test.network.getLinks().get(Id.createLinkId(from)); Link toLink = test.network.getLinks().get(Id.createLinkId(to)); @@ -72,7 +79,6 @@ public static void createDeparture(Holder test, TestVehicle type, String veh, d Mockito.when(mobVeh.getId()).thenReturn(vehicleId); Mockito.when(driver.getVehicle()).thenReturn(mobVeh); - test.engine.handleDeparture(time, driver, route.getStartLinkId(), route); } @@ -143,7 +149,7 @@ public void debugFiles(EventCollector collector, String out) { /** * Helper method for event assertions. */ - public static EventsAssert assertThat(EventCollector events) { + static EventsAssert assertThat(EventCollector events) { return new EventsAssert(events.events, EventsAssert.class); } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitSchedule.xml index 5f1443a7b3a..4926a4cecb6 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitSchedule.xml @@ -6,10 +6,10 @@ - - + + - + @@ -73,4 +73,4 @@ - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitVehicles.xml index 1d1ea9bd4cf..de84fed864e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityOne/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/trainNetwork.xml index e8cc6ba1e7e..3d91ff4b191 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/trainNetwork.xml @@ -5,69 +5,69 @@ Atlantis - + - + - + - + - + - + - + - + - + - + - + - + - + 999 - + 999 - + - + 999 - + 999 - + - + 2 - + - + - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitSchedule.xml index 5f1443a7b3a..4926a4cecb6 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitSchedule.xml @@ -6,10 +6,10 @@ - - + + - + @@ -73,4 +73,4 @@ - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitVehicles.xml index 1d1ea9bd4cf..de84fed864e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStationCapacityTwo/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/trainNetwork.xml index 2d853285b05..c278432ef21 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/trainNetwork.xml @@ -2,77 +2,77 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + 2 - + 2 - + 2 - + 2 - + 2 - + - + - + - + - + - + - + - + - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitSchedule.xml index 17d4fe0d429..cac9c1a1ebf 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitSchedule.xml @@ -4,15 +4,15 @@ - + - + - + - + @@ -43,4 +43,4 @@ - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitVehicles.xml index de64a5a9958..1002d65f560 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoStations/transitVehicles.xml @@ -1,6 +1,6 @@ - + @@ -23,6 +23,6 @@ - + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/trainNetwork.xml index 61ffb82569a..49ed9222e98 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/trainNetwork.xml @@ -15,111 +15,110 @@ --> - - + - + - + - + - + - + - + - + - + - + - + 999 - + 999 - + 1 - + 999 - + 999 - + 999 - + 999 - + 999 - + 999 - + 999 - + bottleneck 1 - + bottleneck 1 - + 999 - + 999 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitVehicles.xml index f9585b6060c..a74a653efb2 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSources/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitVehicles.xml index a5f1849ff76..76d6e266237 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoTwoSourcesComplex/transitVehicles.xml @@ -1,6 +1,6 @@ - + @@ -22,15 +22,15 @@ - - - - - - - + + + + + + + - + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/config.xml index 922013d2df8..fd5d50bb1b6 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/config.xml @@ -3,38 +3,38 @@ - - + + - - - + + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/trainNetwork.xml index ff528739548..db1e86a0c4f 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/trainNetwork.xml @@ -5,53 +5,53 @@ Atlantis - + - + - + - + - + - + - + - + - + 999 - + 2 - + 999 - + 5 - + 999 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitSchedule.xml index be9b4aedcc4..3db3029cd5d 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitSchedule.xml @@ -33,13 +33,13 @@ - - + + - - - + + + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitVehicles.xml index 678eb942a96..262b655b7e3 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/mesoUniDirectionalVaryingCapacities/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/config.xml index 5b7aa89fbeb..b6491cf5f34 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/config.xml @@ -3,36 +3,36 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitVehicles.xml index 3466080caaa..8a32c984f1a 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionCross/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitSchedule.xml index fd97728b28d..8642858618f 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitSchedule.xml @@ -8,10 +8,10 @@ - + - + @@ -53,4 +53,4 @@ - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitVehicles.xml index a2056088fd3..5c4aa7acf39 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microJunctionY/transitVehicles.xml @@ -1,6 +1,6 @@ - + @@ -21,7 +21,7 @@ - + 5.0 @@ -42,7 +42,7 @@ - - + + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml index b110a860043..53f168e5c17 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/config.xml @@ -2,48 +2,48 @@ - + - + - + - + - + - + - - + + - - + + - - + + - - + + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/trainNetwork.xml index ac6b478fe47..193f08d66a3 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/trainNetwork.xml @@ -12,7 +12,7 @@ --> - + @@ -25,7 +25,7 @@ - + - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitVehicles.xml index 557c7cca2b9..20ebec4e74b 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleBiDirectionalTrack/transitVehicles.xml @@ -1,6 +1,6 @@ - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/config.xml index 73d7265aa02..be63f574384 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/config.xml @@ -1,52 +1,52 @@ - - + + - + - + - + - + - + - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitVehicles.xml index 8646c6a67ab..00506a3afee 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microSimpleUniDirectionalTrack/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/trainNetwork.xml index f37f491ecef..aed5d10a9f7 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/trainNetwork.xml @@ -5,421 +5,421 @@ Atlantis - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + r1 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + - + 1 - + 1 - + 1 - + 1 - + 1 - + r1 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitSchedule.xml index 821ec1d0a81..113ee046b2c 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitSchedule.xml @@ -10,10 +10,10 @@ - + - + @@ -66,7 +66,7 @@ - + rail @@ -101,4 +101,4 @@ - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitVehicles.xml index fab0c38780f..f942a748536 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationDifferentLink/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/trainNetwork.xml index 835e19b2945..1ebe818b2c5 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/trainNetwork.xml @@ -6,103 +6,103 @@ Atlantis - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - 2 - true - - - - - - - - - - - - - - 2 - true - - - - - 2 - - - - - - 2 - true - - - - - 2 - - - - + + + + + + + + + + + + + + + + + + + + + + + 2 + + + + + 2 + true + + + + + + + + + + + + + + 2 + true + + + + + 2 + + + + + + 2 + true + + + + + 2 + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitSchedule.xml index 396197eee57..5e5f412f9fb 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitSchedule.xml @@ -4,12 +4,12 @@ - - - - - - + + + + + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitVehicles.xml index 9ac5b25f004..e0e618e9b8e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationRerouting/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/trainNetwork.xml index e28783df73d..3119cbdaf75 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/trainNetwork.xml @@ -5,419 +5,419 @@ Atlantis - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitSchedule.xml index 9cc7ea08496..44cb586edf2 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitSchedule.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitSchedule.xml @@ -10,10 +10,10 @@ - + - + @@ -41,7 +41,7 @@ - + @@ -67,7 +67,7 @@ - + rail @@ -102,4 +102,4 @@ - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitVehicles.xml index 5f43b53905a..5bdf728353f 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microStationSameLink/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitVehicles.xml index 05c114f2309..7e98d72953d 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microThreeUniDirectionalTracks/transitVehicles.xml @@ -1,6 +1,6 @@ - + @@ -22,7 +22,7 @@ - - + + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitVehicles.xml index f3f7106bd31..7d91bf38fee 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTraffic/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitVehicles.xml index af93d6d5830..b359674db22 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrackOppositeTrafficMany/transitVehicles.xml @@ -1,6 +1,6 @@ - + @@ -20,7 +20,7 @@ - + 5.0 @@ -40,15 +40,15 @@ - - - - - - - + + + + + + + - + - \ No newline at end of file + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml index 31353bdad2f..840d9311577 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/trainNetwork.xml @@ -10,161 +10,160 @@ 1 ==> 2 ====> 3 ======> 4 =========== ... ====================> 19 ===> 20 ==> 21 - --> - + --> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 5 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 5 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitVehicles.xml index e27b94aa51c..893a13e980f 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingConstantSpeed/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/config.xml index 80d11a1d7ab..da3761b5c4e 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/config.xml @@ -3,37 +3,37 @@ - - + + - - + + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/trainNetwork.xml index 9e610736643..9094a12079c 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/trainNetwork.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/trainNetwork.xml @@ -14,157 +14,157 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 5 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 5 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitVehicles.xml index e27b94aa51c..893a13e980f 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/microTrainFollowingVaryingSpeed/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/config.xml index b67f48f3942..c7ac9dffc64 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMesoGenfBern/config.xml @@ -3,36 +3,36 @@ - - + + - + - - + + - - + + - + - - - - - - + + + + + + - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/config.xml index 179b9afd763..245af478051 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/config.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/config.xml @@ -36,8 +36,8 @@ - - + + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitVehicles.xml index d8d123f2c23..c6faae55120 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitVehicles.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/integration/scenarioMicroMesoCombination/transitVehicles.xml @@ -1,6 +1,6 @@ - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMesoUni.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMesoUni.xml index ff528739548..e1296d25fea 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMesoUni.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMesoUni.xml @@ -5,53 +5,54 @@ Atlantis - + + - + - + - + - + - + - + - + - + 999 - + 2 - + 999 - + 5 - + 999 - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml index ac6b478fe47..4596bfb98cd 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroBi.xml @@ -10,9 +10,9 @@ n1 <======> n2 <=======> n3 <==> n4 <==> n5 <=======> n6 - --> + --> - + @@ -25,7 +25,7 @@ - + - + diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroUni.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroUni.xml index e58d928f24a..d7d57e30f32 100644 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroUni.xml +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroUni.xml @@ -10,169 +10,164 @@ 1 ==> 2 ====> 3 ======> 4 =========== ... ====================> 19 ===> 20 ==> 21 + --> - --> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 5 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 5 - - - - - - + From ebb0c39c4285b4baa6aa467bc59e33438f05feff Mon Sep 17 00:00:00 2001 From: MattiasIngelstrom Date: Mon, 11 Sep 2023 14:02:17 +0200 Subject: [PATCH 104/258] Added comments to clarify how changes affect ev dependant contribs --- .../ChargerPowerTimeProfileCalculator.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java index 3ae9a239aa7..78e7f6987ef 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/stats/ChargerPowerTimeProfileCalculator.java @@ -22,12 +22,18 @@ public class ChargerPowerTimeProfileCalculator implements ChargingStartEventHandler, ChargingEndEventHandler { private final Map, double[]> chargerProfiles = new HashMap<>(); - private final Map, Double> chargingStartTime = new HashMap<>(); - private final Map, Double> chargingStartEnergy = new HashMap<>(); + private final Map, Double> chargingStartTimeMap = new HashMap<>(); + private final Map, Double> chargingStartEnergyMap = new HashMap<>(); private final TimeDiscretizer timeDiscretizer; private final double qsimEndTime; + /** + * Calculation of average power output for each charging station for each charging event. Charging stations without any charging events will not + * be present in the output file. Implementation does only work when the Qsim end time is defined in the config, i.e., will not work for + * extensions drt and taxi or others depending on the ev contrib without a defined Qsim end time. + * @author mattiasingelstrom + */ @Inject public ChargerPowerTimeProfileCalculator(Config config) { int chargeTimeStep = ConfigUtils.addOrGetModule(config, EvConfigGroup.class).chargeTimeStep; @@ -42,24 +48,27 @@ public Map, double[]> getChargerProfiles() { @Override public void handleEvent(ChargingStartEvent event) { - chargingStartTime.put(event.getVehicleId(), event.getTime()); - chargingStartEnergy.put(event.getVehicleId(), EvUnits.J_to_kWh(event.getCharge())); + chargingStartTimeMap.put(event.getVehicleId(), event.getTime()); + chargingStartEnergyMap.put(event.getVehicleId(), EvUnits.J_to_kWh(event.getCharge())); } @Override + public void handleEvent(ChargingEndEvent event) { - double chargingTimeIn_h = (event.getTime() - chargingStartTime.get(event.getVehicleId())) / 3600.0; - double averagePowerIn_kW = (EvUnits.J_to_kWh(event.getCharge()) - chargingStartEnergy.get(event.getVehicleId())) / chargingTimeIn_h; - increment(averagePowerIn_kW, event.getChargerId(), chargingStartTime.get(event.getVehicleId()), event.getTime()); + double chargingTimeIn_h = (event.getTime() - chargingStartTimeMap.get(event.getVehicleId())) / 3600.0; + double averagePowerIn_kW = (EvUnits.J_to_kWh(event.getCharge()) - chargingStartEnergyMap.get(event.getVehicleId())) / chargingTimeIn_h; + increment(averagePowerIn_kW, event.getChargerId(), chargingStartTimeMap.get(event.getVehicleId()), event.getTime()); } - private void increment(double averagePower, Id chargerId, double beginTime, double endTime) { - if (beginTime == endTime && beginTime >= qsimEndTime || qsimEndTime == 0) { + private void increment(double averagePower, Id chargerId, double chargingStartTime, double chargingEndTime) { + + //If Qsim end time is undefined in config, qsimEndTime will be 0.0 and will therefore not proceed in calculating the power curves. + if (chargingStartTime == chargingEndTime || chargingStartTime >= qsimEndTime || qsimEndTime == 0.0) { return; } - endTime = Math.min(endTime, qsimEndTime); + chargingEndTime = Math.min(chargingEndTime, qsimEndTime); - int fromIdx = timeDiscretizer.getIdx(beginTime); - int toIdx = timeDiscretizer.getIdx(endTime); + int fromIdx = timeDiscretizer.getIdx(chargingStartTime); + int toIdx = timeDiscretizer.getIdx(chargingEndTime); for (int i = fromIdx; i < toIdx; i++) { double[] chargingVector = chargerProfiles.computeIfAbsent(chargerId, c -> new double[timeDiscretizer.getIntervalCount()]); From a1a4ab65a70e1d0df09340060d3685e554d41dfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 14:43:33 +0000 Subject: [PATCH 105/258] build(deps): bump org.apache.maven.plugins:maven-enforcer-plugin Bumps [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) from 3.4.0 to 3.4.1. - [Release notes](https://github.com/apache/maven-enforcer/releases) - [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.4.0...enforcer-3.4.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-enforcer-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5a1f03471cf..3dd8df8326b 100644 --- a/pom.xml +++ b/pom.xml @@ -325,7 +325,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.4.0 + 3.4.1 enforce From dd8901746a45911ab5c3599201d4f487a487694c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:40:39 +0000 Subject: [PATCH 106/258] build(deps): bump org.apache.commons:commons-compress Bumps org.apache.commons:commons-compress from 1.23.0 to 1.24.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-compress dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3dd8df8326b..a73733c938e 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ org.apache.commons commons-compress - 1.23.0 + 1.24.0 commons-logging From dc7cc9921f3b70e1b35457846c74549bebc0018f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:09:37 +0000 Subject: [PATCH 107/258] build(deps): bump one.util:streamex from 0.8.1 to 0.8.2 Bumps [one.util:streamex](https://github.com/amaembo/streamex) from 0.8.1 to 0.8.2. - [Release notes](https://github.com/amaembo/streamex/releases) - [Commits](https://github.com/amaembo/streamex/compare/streamex-0.8.1...streamex-0.8.2) --- updated-dependencies: - dependency-name: one.util:streamex dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- contribs/ev/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/ev/pom.xml b/contribs/ev/pom.xml index 91a218be8b1..ba04bbce066 100644 --- a/contribs/ev/pom.xml +++ b/contribs/ev/pom.xml @@ -33,7 +33,7 @@ one.util streamex - 0.8.1 + 0.8.2 compile From d8f5e8042672953667eeabefc0eef085e200c331 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 13 Sep 2023 11:05:25 +0200 Subject: [PATCH 108/258] update failing calc test --- .../railsim/qsimengine/RailsimCalcTest.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index f0da3224272..7e078722dd5 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -69,12 +69,12 @@ public void testMaxSpeed() { @Test public void testCalcTargetDecel() { - double d = RailsimCalc.calcTargetDecel(1000, 0,10); + double d = RailsimCalc.calcTargetDecel(1000, 0, 10); assertThat(RailsimCalc.calcTraveledDist(10, -10 / d, d)) .isCloseTo(1000, Offset.offset(0.001)); - d = RailsimCalc.calcTargetDecel(1000, 5,10); + d = RailsimCalc.calcTargetDecel(1000, 5, 10); assertThat(RailsimCalc.calcTraveledDist(10, -5 / d, d)) .isCloseTo(1000, Offset.offset(0.001)); @@ -86,15 +86,31 @@ public void testCalcTargetSpeed() { RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(100, 0.5, 0.5, 0, 23, 0); + + double t = RailsimCalc.solveTraveledDist(0, 50, 0.5); + + // Train can not reach target speed and accelerates until 50m assertThat(target.decelDist()) .isCloseTo(50, Offset.offset(0.0001)); + assertThat(RailsimCalc.calcTraveledDist(target.targetSpeed(), t, -0.5)) + .isCloseTo(50, Offset.offset(0.0001)); + target = RailsimCalc.calcTargetSpeed(200, 0.5, 0.5, 13, 13, 0); - // TODO: Failing test, fix or set correct value? + assertThat(target.targetSpeed()) + .isCloseTo(13, Offset.offset(0.0001)); + + // assume travelling at max speed for 31m assertThat(target.decelDist()) - .isCloseTo(169, Offset.offset(0.0001)); + .isCloseTo(31, Offset.offset(0.0001)); + + t = RailsimCalc.solveTraveledDist(13, 200 - 31, -0.5); + + // speed is 0 after decelerating rest of the distance + assertThat(13 + t * -0.5) + .isCloseTo(0, Offset.offset(0.001)); } From a99017271415e1b2a4b49fd8e53d134525271034 Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 13 Sep 2023 11:21:08 +0200 Subject: [PATCH 109/258] resolved TODOs --- .../contrib/railsim/qsimengine/RailResource.java | 14 ++++++-------- .../contrib/railsim/qsimengine/UpdateEvent.java | 2 -- .../railsim/qsimengine/router/TrainRouter.java | 3 +-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java index 03e52c839fd..e3353c57bfa 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java @@ -18,21 +18,19 @@ public class RailResource { final List links; /** - * Maximum number of reservations. + * Agents holding this resource exclusively. */ - int capacity; + final Set reservations; /** - * Agents holding this resource exclusively. + * Maximum number of reservations. */ - Set reservations; + int capacity; public RailResource(List links) { - capacity = links.stream().mapToInt(RailLink::getNumberOfTracks).min().orElseThrow(); this.links = links; - - // TODO: this is not necessarily needed and can be computed implicitly - reservations = new HashSet<>(); + this.reservations = new HashSet<>(); + this.capacity = links.stream().mapToInt(RailLink::getNumberOfTracks).min().orElseThrow(); } /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index b13172fde8f..2cf73bbf9c1 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -81,8 +81,6 @@ enum Type { POSITION, ENTER_LINK, LEAVE_LINK, - // TODO: Never used, should we delete this event type? - RELEASE_TRACK, BLOCK_TRACK, WAIT_FOR_RESERVATION, SPEED_CHANGE, diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java index ab1811b927e..0d4ef7a0b3b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java @@ -34,8 +34,7 @@ public TrainRouter(Network network, RailResourceManager resources) { this.network = network; this.resources = resources; - // TODO: filter rail network - + // uses the full network, which should not be slower than filtered network as long as dijkstra is used this.lpc = new FastDijkstraFactory(false).createPathCalculator(network, new DisUtility(), new FreeSpeedTravelTime()); } From bea1774d10b75755b1513741c0ae812e34c3d91c Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 13 Sep 2023 13:58:04 +0200 Subject: [PATCH 110/258] add test and fix for it --- .../railsim/qsimengine/RailsimEngine.java | 3 +- .../railsim/qsimengine/RailsimEngineTest.java | 28 +++ .../qsimengine/networkMicroVaryingSpeed.xml | 170 ++++++++++++++++++ 3 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroVaryingSpeed.xml diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 477ac4d8c1c..4d8ae094afa 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -677,7 +677,8 @@ else if (!resources.isBlockedBy(link, state.driver)) // Only need to consider if speed is lower than the allowed speed if (!FuzzyUtils.equals(dist, 0) && allowed <= minAllowed) { - RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(dist, state.train.acceleration(), state.train.deceleration(), state.speed, state.allowedMaxSpeed, allowed); + RailsimCalc.SpeedTarget target = RailsimCalc.calcTargetSpeed(dist, state.train.acceleration(), state.train.deceleration(), + state.speed, state.allowedMaxSpeed, Math.min(state.allowedMaxSpeed, allowed)); assert FuzzyUtils.greaterEqualThan(target.decelDist(), 0) : "Decel dist must be greater than 0, or stopping is not possible"; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index cf9756c8966..aeb0bde1a3f 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -224,4 +224,32 @@ public void testTrainFollowing() { .hasTrainState("regio2", 1517, 1000, 0); } + @Test + public void testMicroTrainFollowingVaryingSpeed() { + + RailsimTestUtils.Holder test = getTestEngine("networkMicroVaryingSpeed.xml"); + + RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo1", 0, "1-2", "20-21"); + RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo2", 15, "1-2", "20-21"); + + test.doSimStepUntil(3000); +// test.debugFiles(collector, "microVarying"); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("cargo1", 1278, 1000, 0) + .hasTrainState("cargo2", 2033, 1000, 0); + + // Same test with state updates + test = getTestEngine("networkMicroVaryingSpeed.xml"); + RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo1", 0, "1-2", "20-21"); + RailsimTestUtils.createDeparture(test, TestVehicle.Cargo, "cargo2", 15, "1-2", "20-21"); + test.doStateUpdatesUntil(3000, 1); +// test.debugFiles(collector, "microVarying_detailed"); + + RailsimTestUtils.assertThat(collector) + .hasTrainState("cargo1", 1278, 1000, 0) + .hasTrainState("cargo2", 2033, 1000, 0); + + + } } diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroVaryingSpeed.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroVaryingSpeed.xml new file mode 100644 index 00000000000..9094a12079c --- /dev/null +++ b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/qsimengine/networkMicroVaryingSpeed.xml @@ -0,0 +1,170 @@ + + + + + + Atlantis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 5 + + + + + + + From 30eb78c76d2afe546cff807abaedf7ca6fb6140b Mon Sep 17 00:00:00 2001 From: Christian Rakow Date: Wed, 13 Sep 2023 14:40:17 +0200 Subject: [PATCH 111/258] fix checkstyle --- .../sbb/matsim/contrib/railsim/RailsimUtils.java | 8 ++++---- .../matsim/contrib/railsim/RunRailsimExample.java | 5 ++++- .../railsim/analysis/PostProcessAnalysis.java | 8 +++++++- .../contrib/railsim/analysis/RailsimCsvWriter.java | 14 +++++++++++++- .../analysis/linkstates/RailLinkStateAnalysis.java | 5 ++++- .../RailsimLinkStateControlerListener.java | 4 +++- .../RailsimTrainStateControlerListener.java | 3 +++ .../analysis/trainstates/TrainStateAnalysis.java | 5 ++++- .../contrib/railsim/config/RailsimConfigGroup.java | 2 +- .../RailsimLinkStateChangeEventHandler.java | 7 +++++++ .../RailsimTrainStateEventHandler.java | 7 +++++++ .../RailsimLinkStateChangeEventMapper.java | 3 +++ .../eventmappers/RailsimTrainStateEventMapper.java | 3 +++ .../events/RailsimLinkStateChangeEvent.java | 2 +- .../events/RailsimTrainLeavesLinkEvent.java | 2 +- .../railsim/events/RailsimTrainStateEvent.java | 2 +- .../contrib/railsim/qsimengine/RailsimCalc.java | 2 +- .../contrib/railsim/qsimengine/RailsimEngine.java | 4 ++-- .../railsim/qsimengine/RailsimQSimModule.java | 3 +++ .../contrib/railsim/qsimengine/TrackState.java | 2 +- .../contrib/railsim/qsimengine/TrainInfo.java | 2 +- .../contrib/railsim/qsimengine/TrainState.java | 2 +- .../contrib/railsim/qsimengine/UpdateEvent.java | 4 ++-- 23 files changed, 77 insertions(+), 22 deletions(-) diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 34f329cd139..66599468fda 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -33,7 +33,7 @@ public static double round(double d) { } /** - * @return the train capacity for this link, if no link attribute is provided the default is 1. + * Return the train capacity for this link, if no link attribute is provided the default is 1. */ public static int getTrainCapacity(Link link) { Object attr = link.getAttributes().getAttribute(LINK_ATTRIBUTE_CAPACITY); @@ -48,7 +48,7 @@ public static void setTrainCapacity(Link link, int capacity) { } /** - * @return the minimum time for the switch at the end of the link (toNode); if no link attribute is provided the default is 0. + * Return the minimum time for the switch at the end of the link (toNode); if no link attribute is provided the default is 0. */ public static double getMinimumHeadwayTime(Link link) { Object attr = link.getAttributes().getAttribute(LINK_ATTRIBUTE_MINIMUM_TIME); @@ -105,7 +105,7 @@ public static void setExitLink(Link link, boolean isExit) { } /** - * @return the default deceleration time or the vehicle-specific value. + * Return the default deceleration time or the vehicle-specific value. */ public static double getTrainDeceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { double deceleration = railsimConfigGroup.decelerationDefault; @@ -121,7 +121,7 @@ public static void setTrainDeceleration(VehicleType vehicle, double deceleration } /** - * @return the default acceleration time or the vehicle-specific value. + * Return the default acceleration time or the vehicle-specific value. */ public static double getTrainAcceleration(VehicleType vehicle, RailsimConfigGroup railsimConfigGroup) { double acceleration = railsimConfigGroup.accelerationDefault; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java index de67902943f..2724b7889b6 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RunRailsimExample.java @@ -30,7 +30,10 @@ /** * Example script that shows how to use railsim included in this contrib. */ -public class RunRailsimExample { +public final class RunRailsimExample { + + private RunRailsimExample() { + } public static void main(String[] args) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java index 3b779af9f8c..aed580e018f 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/PostProcessAnalysis.java @@ -33,7 +33,13 @@ import org.matsim.core.network.io.MatsimNetworkReader; import org.matsim.core.scenario.ScenarioUtils; -public class PostProcessAnalysis { +/** + * Class to generate rail sim csv files from event file. + */ +public final class PostProcessAnalysis { + + private PostProcessAnalysis() { + } public static void main(String[] args) { String eventsFilename; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java index 8ec5450d074..433c7a45569 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java @@ -14,8 +14,17 @@ import java.io.UncheckedIOException; import java.util.List; -public class RailsimCsvWriter { +/** + * Helper class to write railsim related csv files. + */ +public final class RailsimCsvWriter { + private RailsimCsvWriter() { + } + + /** + * Write {@link RailsimLinkStateChangeEvent} to a csv file. + */ public static void writeLinkStatesCsv(List events, String filename) throws UncheckedIOException { String[] header = {"link", "time", "state", "vehicle", "track"}; @@ -34,6 +43,9 @@ public static void writeLinkStatesCsv(List events, } + /** + * Write {@link RailsimTrainStateEvent} to a csv file. + */ public static void writeTrainStatesCsv(List events, Network network, String filename) throws UncheckedIOException { String[] header = {"vehicle", "time", "acceleration", "speed", "targetSpeed", "headLink", "headPosition", "headX", "headY", "tailLink", "tailPosition", "tailX", "tailY"}; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java index 519c3142c40..5f204dc9387 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailLinkStateAnalysis.java @@ -25,7 +25,10 @@ import java.util.ArrayList; import java.util.List; -public class RailLinkStateAnalysis implements RailsimLinkStateChangeEventHandler { +/** + * Handler for {@link RailsimLinkStateChangeEvent}. + */ +public final class RailLinkStateAnalysis implements RailsimLinkStateChangeEventHandler { final List events = new ArrayList<>(1000); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java index 4e16b70b114..fe37076bfcd 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/linkstates/RailsimLinkStateControlerListener.java @@ -29,7 +29,9 @@ import org.matsim.core.controler.listener.IterationEndsListener; import org.matsim.core.controler.listener.IterationStartsListener; - +/** + * Controler to automatically write rail sim analysis files. + */ public final class RailsimLinkStateControlerListener implements IterationEndsListener, IterationStartsListener { private RailLinkStateAnalysis analysis; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java index 9804242a1fc..49934ca9495 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/RailsimTrainStateControlerListener.java @@ -30,6 +30,9 @@ import org.matsim.core.controler.listener.IterationStartsListener; +/** + * Controler to write {@link ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent} csv files. + */ public final class RailsimTrainStateControlerListener implements IterationEndsListener, IterationStartsListener { private TrainStateAnalysis analysis; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java index 77aaa0780b3..fcc7531ce43 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/trainstates/TrainStateAnalysis.java @@ -25,7 +25,10 @@ import java.util.ArrayList; import java.util.List; -public class TrainStateAnalysis implements RailsimTrainStateEventHandler { +/** + * Handler collecting all {@link RailsimTrainStateEvent}s. + */ +public final class TrainStateAnalysis implements RailsimTrainStateEventHandler { final List events = new ArrayList<>(1000); diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java index baf3953bff3..9599b04c915 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/config/RailsimConfigGroup.java @@ -28,7 +28,7 @@ /** * Config of the Railsim contrib. */ -public class RailsimConfigGroup extends ReflectiveConfigGroup { +public final class RailsimConfigGroup extends ReflectiveConfigGroup { public static final String GROUP_NAME = "railsim"; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java index de61a24758a..7fd4ef9cfa4 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimLinkStateChangeEventHandler.java @@ -22,6 +22,13 @@ import ch.sbb.matsim.contrib.railsim.events.RailsimLinkStateChangeEvent; import org.matsim.core.events.handler.EventHandler; +/** + * Handler for {@link RailsimLinkStateChangeEvent}. + */ public interface RailsimLinkStateChangeEventHandler extends EventHandler { + + /** + * Process given event. + */ void handleEvent(RailsimLinkStateChangeEvent event); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java index f775b70ba87..5f4ae3179e6 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventhandlers/RailsimTrainStateEventHandler.java @@ -22,6 +22,13 @@ import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; import org.matsim.core.events.handler.EventHandler; +/** + * Event handler for {@link RailsimTrainStateEvent}. + */ public interface RailsimTrainStateEventHandler extends EventHandler { + + /** + * Handle given event. + */ void handleEvent(RailsimTrainStateEvent event); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java index 7739f80cede..97488e97e1d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimLinkStateChangeEventMapper.java @@ -27,6 +27,9 @@ import org.matsim.core.events.MatsimEventsReader; import org.matsim.vehicles.Vehicle; +/** + * Converter for railsim events. + */ public class RailsimLinkStateChangeEventMapper implements MatsimEventsReader.CustomEventMapper { @Override public RailsimLinkStateChangeEvent apply(GenericEvent event) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java index e362d2ed17c..1c118f15856 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/eventmappers/RailsimTrainStateEventMapper.java @@ -26,6 +26,9 @@ import org.matsim.core.events.MatsimEventsReader; import org.matsim.vehicles.Vehicle; +/** + * Convert for {@link RailsimTrainStateEvent}. + */ public class RailsimTrainStateEventMapper implements MatsimEventsReader.CustomEventMapper { @Override public RailsimTrainStateEvent apply(GenericEvent event) { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java index 47114bfaf7b..317d07ea73e 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java @@ -13,7 +13,7 @@ /** * Event thrown when the {@link ch.sbb.matsim.contrib.railsim.qsimengine.TrackState} of a {@link Link} changes. */ -public class RailsimLinkStateChangeEvent extends Event implements HasLinkId, HasVehicleId { +public final class RailsimLinkStateChangeEvent extends Event implements HasLinkId, HasVehicleId { public static final String EVENT_TYPE = "railsimLinkStateChangeEvent"; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java index 0d8d55c8d76..dc9e0ce0bfd 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java @@ -19,7 +19,7 @@ public class RailsimTrainLeavesLinkEvent extends Event implements HasLinkId, Has private final Id linkId; private final Id vehicleId; - public RailsimTrainLeavesLinkEvent(double time,Id vehicleId, Id linkId) { + public RailsimTrainLeavesLinkEvent(double time, Id vehicleId, Id linkId) { super(time); this.vehicleId = vehicleId; this.linkId = linkId; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java index 2e480bdf845..05425d5489c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -12,7 +12,7 @@ /** * Event for currents train position. */ -public class RailsimTrainStateEvent extends Event implements HasVehicleId { +public final class RailsimTrainStateEvent extends Event implements HasVehicleId { public static final String EVENT_TYPE = "railsimTrainStateEvent"; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 1e4bd53ed7a..8ae7d12ccda 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -6,7 +6,7 @@ /** * Utility class holding static calculation methods related to state (updates). */ -class RailsimCalc { +final class RailsimCalc { private RailsimCalc() { } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 4d8ae094afa..64354293285 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -41,7 +41,7 @@ final class RailsimEngine implements Steppable { private final RailResourceManager resources; private final TrainDisposition disposition; - public RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, RailResourceManager resources, TrainDisposition disposition) { + RailsimEngine(EventsManager eventsManager, RailsimConfigGroup config, RailResourceManager resources, TrainDisposition disposition) { this.eventsManager = eventsManager; this.config = config; this.resources = resources; @@ -142,7 +142,7 @@ private void updateState(double time, UpdateEvent event) { case BLOCK_TRACK -> blockTrack(time, event); case WAIT_FOR_RESERVATION -> checkTrackReservation(time, event); case UNBLOCK_LINK -> { - unblockTrack(time, event.state, event.unblockLink); + unblockTrack(time, event.state, event.unblockLink); // event will be removed event.type = UpdateEvent.Type.IDLE; } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java index ac17bfece61..77e4e04133b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimQSimModule.java @@ -28,6 +28,9 @@ import org.matsim.core.mobsim.qsim.components.QSimComponentsConfigurator; import org.matsim.core.mobsim.qsim.pt.TransitDriverAgentFactory; +/** + * Module to install railsim functionality. + */ public class RailsimQSimModule extends AbstractQSimModule implements QSimComponentsConfigurator { public static final String COMPONENT_NAME = "Railsim"; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java index c02fb537a29..635082d1593 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java @@ -9,5 +9,5 @@ public enum TrackState { /** * Blocked tracks that are exclusively available for trains. */ - BLOCKED, + BLOCKED } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java index b07a55e6a60..38ee43dcce9 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java @@ -17,7 +17,7 @@ record TrainInfo( double maxDeceleration ) { - public TrainInfo(VehicleType vehicle, RailsimConfigGroup config) { + TrainInfo(VehicleType vehicle, RailsimConfigGroup config) { this( vehicle.getId(), vehicle.getLength(), diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index e12bbdbcbbb..d1fb639ec9c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -94,7 +94,7 @@ final class TrainState { double speed; /** - * Current Acceleration, (or deceleration if negative) + * Current Acceleration, (or deceleration if negative). */ double acceleration; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 2cf73bbf9c1..1df31a75eba 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -26,7 +26,7 @@ final class UpdateEvent implements Comparable { */ final RailLink unblockLink; - public UpdateEvent(TrainState state, Type type) { + UpdateEvent(TrainState state, Type type) { this.state = state; this.plannedTime = state.timestamp; this.type = type; @@ -34,7 +34,7 @@ public UpdateEvent(TrainState state, Type type) { this.unblockLink = null; } - public UpdateEvent(TrainState state, RailLink unblockLink, double time) { + UpdateEvent(TrainState state, RailLink unblockLink, double time) { this.state = state; this.unblockLink = unblockLink; this.plannedTime = time + unblockLink.minimumHeadwayTime; From bf455e5dc57b160c3901caeb0648c3fbbcc32f11 Mon Sep 17 00:00:00 2001 From: rakow Date: Wed, 13 Sep 2023 20:03:29 +0200 Subject: [PATCH 112/258] round randomly generated coordinates (#2765) --- .../AdjustActivityToLinkDistances.java | 1 + .../population/ExtractHomeCoordinates.java | 51 +++++++++++-------- .../population/ResolveGridCoordinates.java | 5 +- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/contribs/application/src/main/java/org/matsim/application/prepare/population/AdjustActivityToLinkDistances.java b/contribs/application/src/main/java/org/matsim/application/prepare/population/AdjustActivityToLinkDistances.java index 33bb953f305..d0daecccda7 100644 --- a/contribs/application/src/main/java/org/matsim/application/prepare/population/AdjustActivityToLinkDistances.java +++ b/contribs/application/src/main/java/org/matsim/application/prepare/population/AdjustActivityToLinkDistances.java @@ -134,6 +134,7 @@ public Integer call() throws Exception { v = new Coord(coord.getX() - y * m, coord.getY() + x * m); } + v = CoordUtils.round(v); mapping.put(act.getCoord(), v); } diff --git a/contribs/application/src/main/java/org/matsim/application/prepare/population/ExtractHomeCoordinates.java b/contribs/application/src/main/java/org/matsim/application/prepare/population/ExtractHomeCoordinates.java index e8f343ffa1f..9ceef42f3e4 100644 --- a/contribs/application/src/main/java/org/matsim/application/prepare/population/ExtractHomeCoordinates.java +++ b/contribs/application/src/main/java/org/matsim/application/prepare/population/ExtractHomeCoordinates.java @@ -8,6 +8,7 @@ import org.matsim.application.MATSimAppCommand; import org.matsim.application.options.CsvOptions; import org.matsim.core.population.PopulationUtils; +import org.matsim.core.utils.geometry.CoordUtils; import picocli.CommandLine; import java.nio.file.Path; @@ -15,10 +16,10 @@ import java.util.Map; @CommandLine.Command( - name = "extract-home-coordinates", - description = "Extract the home coordinates of a person" + name = "extract-home-coordinates", + description = "Extract the home coordinates of a person" ) -public class ExtractHomeCoordinates implements MATSimAppCommand { +public final class ExtractHomeCoordinates implements MATSimAppCommand { private static final Logger log = LogManager.getLogger(ExtractHomeCoordinates.class); @@ -34,6 +35,29 @@ public class ExtractHomeCoordinates implements MATSimAppCommand { @CommandLine.Mixin private CsvOptions options = new CsvOptions(); + /** + * Set and return home coordinate of this person. Can be null if no home activity is known. + */ + public static Coord setHomeCoordinate(Person person) { + for (Plan plan : person.getPlans()) { + for (PlanElement planElement : plan.getPlanElements()) { + if (planElement instanceof Activity) { + String actType = ((Activity) planElement).getType(); + if (actType.startsWith("home")) { + Coord homeCoord = CoordUtils.round(((Activity) planElement).getCoord()); + + person.getAttributes().putAttribute("home_x", homeCoord.getX()); + person.getAttributes().putAttribute("home_y", homeCoord.getY()); + + return homeCoord; + } + } + } + } + + return null; + } + @Override public Integer call() throws Exception { @@ -42,24 +66,9 @@ public Integer call() throws Exception { Map coords = new LinkedHashMap<>(); for (Person person : population.getPersons().values()) { - outer: - for (Plan plan : person.getPlans()) { - for (PlanElement planElement : plan.getPlanElements()) { - if (planElement instanceof Activity) { - String actType = ((Activity) planElement).getType(); - if (actType.startsWith("home")) { - Coord homeCoord = ((Activity) planElement).getCoord(); - coords.put(person, homeCoord); - - person.getAttributes().putAttribute("home_x", homeCoord.getX()); - person.getAttributes().putAttribute("home_y", homeCoord.getY()); - - break outer; - } - } - } - - } + Coord coord = setHomeCoordinate(person); + if (coord != null) + coords.put(person, coord); } if (csv != null) { diff --git a/contribs/application/src/main/java/org/matsim/application/prepare/population/ResolveGridCoordinates.java b/contribs/application/src/main/java/org/matsim/application/prepare/population/ResolveGridCoordinates.java index 7c223d08484..c2cacd46992 100644 --- a/contribs/application/src/main/java/org/matsim/application/prepare/population/ResolveGridCoordinates.java +++ b/contribs/application/src/main/java/org/matsim/application/prepare/population/ResolveGridCoordinates.java @@ -14,6 +14,7 @@ import org.matsim.application.options.ShpOptions; import org.matsim.core.network.NetworkUtils; import org.matsim.core.population.PopulationUtils; +import org.matsim.core.utils.geometry.CoordUtils; import org.matsim.core.utils.geometry.geotools.MGC; import picocli.CommandLine; @@ -93,7 +94,7 @@ public Integer call() throws Exception { Coord newCoord = mapping.getOrDefault(coord, null); if (newCoord == null) { - newCoord = landuse.select(crs.getInputCRS(), + newCoord = CoordUtils.round(landuse.select(crs.getInputCRS(), () -> { double x = rnd.nextDouble(-gridResolution / 2, gridResolution / 2); double y = rnd.nextDouble(-gridResolution / 2, gridResolution / 2); @@ -103,7 +104,7 @@ public Integer call() throws Exception { else return new Coord(coord.getX() + x, coord.getY() + y); } - ); + )); mapping.put(coord, newCoord); } From 7ed4e31c7b5701041b3a0d0d3489d1fd9f5a0d68 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Wed, 13 Sep 2023 16:32:15 +0200 Subject: [PATCH 113/258] moved flush from writePerson to endPlans --- .../core/population/io/AbstractPopulationWriterHandler.java | 1 - .../core/population/io/PopulationWriterHandlerImplV0.java | 1 + .../core/population/io/PopulationWriterHandlerImplV4.java | 1 + .../core/population/io/PopulationWriterHandlerImplV5.java | 2 +- .../core/population/io/PopulationWriterHandlerImplV6.java | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/matsim/src/main/java/org/matsim/core/population/io/AbstractPopulationWriterHandler.java b/matsim/src/main/java/org/matsim/core/population/io/AbstractPopulationWriterHandler.java index 9208db8e875..95a99b66e50 100644 --- a/matsim/src/main/java/org/matsim/core/population/io/AbstractPopulationWriterHandler.java +++ b/matsim/src/main/java/org/matsim/core/population/io/AbstractPopulationWriterHandler.java @@ -77,7 +77,6 @@ else if (pe instanceof Leg) { } this.endPerson(writer); this.writeSeparator(writer); - writer.flush(); } public abstract void startPerson(final Person person, final BufferedWriter out) throws IOException; diff --git a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV0.java b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV0.java index 0009ba521e6..057684fb2f6 100644 --- a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV0.java +++ b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV0.java @@ -75,6 +75,7 @@ public void startPlans(final Population plans, final BufferedWriter out) throws @Override public void endPlans(final BufferedWriter out) throws IOException { out.write("\n"); + out.flush(); } ////////////////////////////////////////////////////////////////////// diff --git a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV4.java b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV4.java index 493ec680d06..c66dd9f57cc 100644 --- a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV4.java +++ b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV4.java @@ -82,6 +82,7 @@ public void startPlans(final Population plans, final BufferedWriter out) throws @Override public void endPlans(final BufferedWriter out) throws IOException { out.write("\n"); + out.flush(); } ////////////////////////////////////////////////////////////////////// diff --git a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV5.java b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV5.java index 7d1c42fe0b9..5ca46309074 100644 --- a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV5.java +++ b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV5.java @@ -99,12 +99,12 @@ else if (pe instanceof Leg) { } PopulationWriterHandlerImplV5.endPerson(out); this.writeSeparator(out); - out.flush(); } @Override public void endPlans(final BufferedWriter out) throws IOException { out.write("\n"); + out.flush(); } private static void startPerson(final Person person, final BufferedWriter out) throws IOException { diff --git a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV6.java b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV6.java index 67ff8fada0a..7d23d6c976a 100644 --- a/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV6.java +++ b/matsim/src/main/java/org/matsim/core/population/io/PopulationWriterHandlerImplV6.java @@ -119,12 +119,12 @@ else if (pe instanceof Leg) { } PopulationWriterHandlerImplV6.endPerson(out); this.writeSeparator(out); - out.flush(); } @Override public void endPlans(final BufferedWriter out) throws IOException { out.write("\n"); + out.flush(); } private void startPerson(final Person person, final BufferedWriter out) throws IOException { From 9dcff9ca1941dd73a20b1cae235cac8ae81f67bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:34:13 +0000 Subject: [PATCH 114/258] build(deps-dev): bump net.bytebuddy:byte-buddy from 1.14.7 to 1.14.8 Bumps [net.bytebuddy:byte-buddy](https://github.com/raphw/byte-buddy) from 1.14.7 to 1.14.8. - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.7...byte-buddy-1.14.8) --- updated-dependencies: - dependency-name: net.bytebuddy:byte-buddy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a73733c938e..ab7183acfd3 100644 --- a/pom.xml +++ b/pom.xml @@ -308,7 +308,7 @@ net.bytebuddy byte-buddy - 1.14.7 + 1.14.8 test From 2a54b08065564bf02d7e45de69b87aa103f7ee47 Mon Sep 17 00:00:00 2001 From: nixlaos <104372543+nixlaos@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:23:30 +0200 Subject: [PATCH 115/258] Freight: Adding missing converters for freight events (#2769) * added converters for missing freight events * added test for reading in carrier events * Revert "added test for reading in carrier events" This reverts commit 2ef28130513920e0c6a673c810d2a09f3f12764b. --- .../freight/events/CarrierEventsReaders.java | 7 ++++++- .../freight/events/CarrierServiceEndEvent.java | 15 +++++++++++++++ .../freight/events/CarrierServiceStartEvent.java | 16 ++++++++++++++++ .../events/CarrierShipmentDeliveryEndEvent.java | 16 ++++++++++++++++ .../CarrierShipmentDeliveryStartEvent.java | 16 ++++++++++++++++ .../events/CarrierShipmentPickupEndEvent.java | 16 ++++++++++++++++ .../events/CarrierShipmentPickupStartEvent.java | 16 ++++++++++++++++ 7 files changed, 101 insertions(+), 1 deletion(-) diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java index c268d7d1184..20eb9dad5a8 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierEventsReaders.java @@ -36,9 +36,14 @@ public class CarrierEventsReaders { public static Map createCustomEventMappers() { return Map.of( + CarrierServiceStartEvent.EVENT_TYPE, CarrierServiceStartEvent::convert, + CarrierServiceEndEvent.EVENT_TYPE, CarrierServiceEndEvent::convert, + CarrierShipmentDeliveryStartEvent.EVENT_TYPE, CarrierShipmentDeliveryStartEvent::convert, + CarrierShipmentDeliveryEndEvent.EVENT_TYPE, CarrierShipmentDeliveryEndEvent::convert, + CarrierShipmentPickupStartEvent.EVENT_TYPE, CarrierShipmentPickupStartEvent::convert, + CarrierShipmentPickupEndEvent.EVENT_TYPE, CarrierShipmentPickupEndEvent::convert, CarrierTourStartEvent.EVENT_TYPE, CarrierTourStartEvent::convert, // CarrierTourEndEvent.EVENT_TYPE, CarrierTourEndEvent::convert - // more will follow later, KMT feb'23 ); } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java index e1527edc9b0..0c4585932a0 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceEndEvent.java @@ -24,6 +24,8 @@ import java.util.Map; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierService; import org.matsim.vehicles.Vehicle; @@ -67,4 +69,17 @@ public Map getAttributes() { attr.put(ATTRIBUTE_SERVICE_DURATION, String.valueOf(serviceDuration)); return attr; } + + public static CarrierServiceEndEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id carrierServiceId = Id.create(attributes.get(ATTRIBUTE_SERVICE_ID), CarrierService.class); + Id locationLinkId = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + CarrierService service = CarrierService.Builder.newInstance(carrierServiceId, locationLinkId) + .setServiceDuration(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .build(); + Id vehicleId = Id.create(attributes.get(ATTRIBUTE_VEHICLE), Vehicle.class); + return new CarrierServiceEndEvent(time, carrierId, service, vehicleId); + } } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java index 719bfc96978..6c8550df63f 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierServiceStartEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierService; import org.matsim.vehicles.Vehicle; @@ -77,4 +79,18 @@ public Map getAttributes() { attr.put(ATTRIBUTE_CAPACITYDEMAND, String.valueOf(capacityDemand)); return attr; } + + public static CarrierServiceStartEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id carrierServiceId = Id.create(attributes.get(ATTRIBUTE_SERVICE_ID), CarrierService.class); + Id locationLinkId = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + CarrierService service = CarrierService.Builder.newInstance(carrierServiceId, locationLinkId) + .setServiceDuration(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .setCapacityDemand(Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND))) + .build(); + Id vehicleId = Id.create(attributes.get(ATTRIBUTE_VEHICLE), Vehicle.class); + return new CarrierServiceStartEvent(time, carrierId, service, vehicleId); + } } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java index 9e702f3359d..eb462b77a88 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryEndEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -76,4 +78,18 @@ public Map getAttributes() { return attr; } + public static CarrierShipmentDeliveryEndEvent convert(GenericEvent event) { + var attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentTo = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int size = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, null, shipmentTo, size) + .setDeliveryServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentDeliveryEndEvent(time, carrierId, shipment, vehicleId); + } + } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java index 6e097bbe64d..08b77671842 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentDeliveryStartEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -75,4 +77,18 @@ public Map getAttributes() { return attr; } + public static CarrierShipmentDeliveryStartEvent convert(GenericEvent event) { + var attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentTo = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int size = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, null, shipmentTo, size) + .setDeliveryServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_SERVICE_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentDeliveryStartEvent(time, carrierId, shipment, vehicleId); + } + } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java index 664dbfa45c7..e88ead0cb37 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupEndEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -69,4 +71,18 @@ public Map getAttributes() { attr.put(ATTRIBUTE_CAPACITYDEMAND, String.valueOf(capacityDemand)); return attr; } + + public static CarrierShipmentPickupEndEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentFrom = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int shipmentSize = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, shipmentFrom, null, shipmentSize) + .setPickupServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_PICKUP_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentPickupEndEvent(time, carrierId, shipment, vehicleId); + } } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java index 7285434d134..4f7725e6ece 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/events/CarrierShipmentPickupStartEvent.java @@ -22,6 +22,8 @@ package org.matsim.contrib.freight.events; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.GenericEvent; +import org.matsim.api.core.v01.network.Link; import org.matsim.contrib.freight.carrier.Carrier; import org.matsim.contrib.freight.carrier.CarrierShipment; import org.matsim.vehicles.Vehicle; @@ -69,4 +71,18 @@ public Map getAttributes() { attr.put(ATTRIBUTE_CAPACITYDEMAND, String.valueOf(capacityDemand)); return attr; } + + public static CarrierShipmentPickupStartEvent convert(GenericEvent event) { + Map attributes = event.getAttributes(); + double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME)); + Id carrierId = Id.create(attributes.get(ATTRIBUTE_CARRIER_ID), Carrier.class); + Id shipmentId = Id.create(attributes.get(ATTRIBUTE_SHIPMENT_ID), CarrierShipment.class); + Id shipmentFrom = Id.createLinkId(attributes.get(ATTRIBUTE_LINK)); + int shipmentSize = Integer.parseInt(attributes.get(ATTRIBUTE_CAPACITYDEMAND)); + CarrierShipment shipment = CarrierShipment.Builder.newInstance(shipmentId, shipmentFrom, null, shipmentSize) + .setPickupServiceTime(Double.parseDouble(attributes.get(ATTRIBUTE_PICKUP_DURATION))) + .build(); + Id vehicleId = Id.createVehicleId(attributes.get(ATTRIBUTE_VEHICLE)); + return new CarrierShipmentPickupStartEvent(time, carrierId, shipment, vehicleId); + } } From 48de00ed28a9f3115fa78cde0129a7faf93e9983 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:54:31 +0000 Subject: [PATCH 116/258] build(deps): bump org.apache.maven.plugins:maven-javadoc-plugin Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.5.0 to 3.6.0. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.5.0...maven-javadoc-plugin-3.6.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ab7183acfd3..e8637f7a05c 100644 --- a/pom.xml +++ b/pom.xml @@ -412,7 +412,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.5.0 + 3.6.0 org.codehaus.mojo From 8ddabe852967f4dbe53d3ab380782545c29a6e19 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Tue, 19 Sep 2023 14:27:40 +0200 Subject: [PATCH 117/258] Acknowledgement and GPL header - Add acknowledgement note to README. - Add GPL header to all source files. - Remove all TODOs. --- contribs/railsim/README.md | 4 ++++ .../matsim/contrib/railsim/RailsimUtils.java | 19 +++++++++++++++++ .../railsim/analysis/RailsimCsvWriter.java | 19 +++++++++++++++++ .../railsim/events/RailsimDetourEvent.java | 19 +++++++++++++++++ .../events/RailsimLinkStateChangeEvent.java | 19 +++++++++++++++++ .../events/RailsimTrainLeavesLinkEvent.java | 19 +++++++++++++++++ .../events/RailsimTrainStateEvent.java | 19 +++++++++++++++++ .../railsim/qsimengine/FuzzyUtils.java | 19 +++++++++++++++++ .../contrib/railsim/qsimengine/RailLink.java | 19 +++++++++++++++++ .../railsim/qsimengine/RailResource.java | 19 +++++++++++++++++ .../qsimengine/RailResourceManager.java | 19 +++++++++++++++++ .../railsim/qsimengine/RailsimCalc.java | 19 +++++++++++++++++ .../qsimengine/RailsimDriverAgentFactory.java | 19 +++++++++++++++++ .../railsim/qsimengine/RailsimEngine.java | 21 ++++++++++++++++++- .../qsimengine/RailsimTransitDriverAgent.java | 19 +++++++++++++++++ .../railsim/qsimengine/TrackState.java | 19 +++++++++++++++++ .../contrib/railsim/qsimengine/TrainInfo.java | 19 +++++++++++++++++ .../railsim/qsimengine/TrainState.java | 19 +++++++++++++++++ .../railsim/qsimengine/UpdateEvent.java | 19 +++++++++++++++++ .../disposition/SimpleDisposition.java | 19 +++++++++++++++++ .../disposition/TrainDisposition.java | 19 +++++++++++++++++ .../qsimengine/router/TrainRouter.java | 19 +++++++++++++++++ .../integration/RailsimIntegrationTest.java | 20 +++++++++++++++++- .../railsim/integration/SnzActivities.java | 2 +- .../railsim/qsimengine/EventAssert.java | 19 +++++++++++++++++ .../railsim/qsimengine/EventsAssert.java | 19 +++++++++++++++++ .../railsim/qsimengine/RailsimCalcTest.java | 19 +++++++++++++++++ .../railsim/qsimengine/RailsimEngineTest.java | 19 +++++++++++++++++ .../railsim/qsimengine/RailsimTestUtils.java | 19 +++++++++++++++++ .../railsim/qsimengine/TestVehicle.java | 19 +++++++++++++++++ 30 files changed, 538 insertions(+), 3 deletions(-) diff --git a/contribs/railsim/README.md b/contribs/railsim/README.md index baec9278bb1..3efbc30fedc 100644 --- a/contribs/railsim/README.md +++ b/contribs/railsim/README.md @@ -29,3 +29,7 @@ See `RunRailsimExample.java` - [network-specification](docs/network-specification.md) - [train-specification](docs/train-specification.md) - [events-specification](docs/events-specification.md) + +## Acknowledgments + +This contrib was initiated and initially funded by the [Swiss Federal Railways](https://www.sbb.ch) (SBB). diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java index 66599468fda..6eff2e945f2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/RailsimUtils.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java index 433c7a45569..61462ddebbd 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/analysis/RailsimCsvWriter.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.analysis; import ch.sbb.matsim.contrib.railsim.RailsimUtils; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java index 3db37d3ffa0..f5a84b0e8ff 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimDetourEvent.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.events; import org.matsim.api.core.v01.Id; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java index 317d07ea73e..55a73fdf6d7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimLinkStateChangeEvent.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.events; import ch.sbb.matsim.contrib.railsim.qsimengine.TrackState; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java index dc9e0ce0bfd..eb2d4038f81 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainLeavesLinkEvent.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.events; import org.matsim.api.core.v01.Id; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java index 05425d5489c..7fa89816c1b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/events/RailsimTrainStateEvent.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.events; import ch.sbb.matsim.contrib.railsim.RailsimUtils; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java index 5ba5f9485b5..3e27651c038 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/FuzzyUtils.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java index f71b38e3d5f..d59ae3801a2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailLink.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.RailsimUtils; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java index e3353c57bfa..ded83867f7d 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResource.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import org.matsim.core.mobsim.framework.MobsimDriverAgent; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java index 01e0b685532..6f00f25b9d6 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailResourceManager.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java index 8ae7d12ccda..62437d0bfa7 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalc.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import java.util.ArrayList; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java index 7278d0fd4ec..6a68c0ffd59 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimDriverAgentFactory.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.config.RailsimConfigGroup; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java index 64354293285..6573ed1b47e 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngine.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import java.util.ArrayList; @@ -79,9 +98,9 @@ public void doSimStep(double time) { public void updateAllStates(double time) { // Process all waiting events first - // TODO: Consider potential duplication of position update events here, if time matches updateInterval. doSimStep(time); + // Potential duplication of position update events here, if time matches updateInterval updateAllPositions(time); } diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java index f4352b5b031..be2ebcb6ffb 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTransitDriverAgent.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import org.apache.logging.log4j.LogManager; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java index 635082d1593..976d2d2e9ad 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrackState.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; /** diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java index 38ee43dcce9..9d395d785d0 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainInfo.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.RailsimUtils; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java index d1fb639ec9c..1c0f66cb969 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/TrainState.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java index 1df31a75eba..52776792c9b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/UpdateEvent.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import java.util.Objects; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java index ef296159711..99418a7547b 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/SimpleDisposition.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine.disposition; import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java index 2fa1d2e8178..eb178dc663c 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/disposition/TrainDisposition.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine.disposition; import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java index 0d4ef7a0b3b..34af433a24e 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine.router; import ch.sbb.matsim.contrib.railsim.qsimengine.RailLink; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java index 4b468ea4476..7148d935cb3 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/RailsimIntegrationTest.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.integration; import ch.sbb.matsim.contrib.railsim.RailsimModule; @@ -219,7 +238,6 @@ public void testMicroTrainFollowingConstantSpeed() { } // This test is similar to testMicroTrainFollowingConstantSpeed but with varying speed levels along the corridor. - // TODO: Right now, there are some runtime exceptions which I don't understand. @Test public void testMicroTrainFollowingVaryingSpeed() { EventsCollector collector = runSimulation(new File(utils.getPackageInputDirectory(), "microTrainFollowingVaryingSpeed")); diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java index 99fa9ad6fc0..9c21fd13385 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/integration/SnzActivities.java @@ -1,4 +1,4 @@ -/** +/* * Avoid dependency on vsp contrib, copy from: * https://github.com/matsim-org/matsim-libs/blob/b2305e5e0f744b357486c8bbab253bb7c38aaad4/contribs/vsp/src/main/java/org/matsim/contrib/vsp/scenario/SnzActivities.java */ diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java index ebd02a9e983..7614ac66c3f 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventAssert.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import org.assertj.core.api.AbstractAssert; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java index 4ebb2550ca7..726b229ba80 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/EventsAssert.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.events.RailsimTrainStateEvent; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java index 7e078722dd5..e969aa3cb2c 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimCalcTest.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import org.assertj.core.data.Offset; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java index aeb0bde1a3f..a993b46fd49 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimEngineTest.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.RailsimUtils; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java index 1b4aa3c7de6..63f5249cef6 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/RailsimTestUtils.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; import ch.sbb.matsim.contrib.railsim.analysis.RailsimCsvWriter; diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java index 19f4ed8afa4..0094ab53c15 100644 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java +++ b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/qsimengine/TestVehicle.java @@ -1,3 +1,22 @@ +/* *********************************************************************** * + * project: org.matsim.* + * * + * *********************************************************************** * + * * + * copyright : (C) 2023 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + package ch.sbb.matsim.contrib.railsim.qsimengine; /** From 0f5aeb7fddad34ed4506ce6a88f20e752faf993a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:06:43 +0000 Subject: [PATCH 118/258] build(deps): bump org.openjfx:javafx-graphics from 20.0.2 to 21 Bumps org.openjfx:javafx-graphics from 20.0.2 to 21. --- updated-dependencies: - dependency-name: org.openjfx:javafx-graphics dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- contribs/vsp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/vsp/pom.xml b/contribs/vsp/pom.xml index 3db82269372..f7d6c310905 100644 --- a/contribs/vsp/pom.xml +++ b/contribs/vsp/pom.xml @@ -174,7 +174,7 @@ org.openjfx javafx-graphics - 20.0.2 + 21 com.graphhopper From 8c78ddfa2bc11f2567117845d1f7cfbcaf2fe3e9 Mon Sep 17 00:00:00 2001 From: rakow Date: Wed, 20 Sep 2023 07:25:46 +0200 Subject: [PATCH 119/258] fix sbb module test, add missing entries for new contrib --- .github/workflows/verify-push.yaml | 1 + contribs/README.md | 1 + .../sbb/matsim/mobsim/qsim/pt/TestQSimModule.java | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify-push.yaml b/.github/workflows/verify-push.yaml index 528ebc740d8..6c98df57073 100644 --- a/.github/workflows/verify-push.yaml +++ b/.github/workflows/verify-push.yaml @@ -55,6 +55,7 @@ jobs: - contribs/socnetsim - contribs/sumo - contribs/pseudosimulation + - contribs/railsim - contribs/roadpricing - contribs/analysis - contribs/eventsBasedPTRouter diff --git a/contribs/README.md b/contribs/README.md index cf682e61410..f29f739c4ef 100644 --- a/contribs/README.md +++ b/contribs/README.md @@ -41,6 +41,7 @@ The MATSim core development team cannot make any guarantee that these extensions | [parking](parking/README.md) | Parking infrastructure and supply constraints | [protobuf](protobuf/README.md) | Protocol buffer implementation and converter for the MATSim event infrastructure | [pseudosimulation](pseudosimulation/README.md) | Pseudo-simulation to speed-up simulation times +| [railsim](railsim/README.md) | A large-scale hybrid micro- and mesoscopic simulation approach for railway operation | [roadpricing](roadpricing/README.md) | Functionality to simulate different road-pricing scenarios in MATSim | [shared_mobility](shared_mobility/README.md) | Simulate human-driven shared mobility (i.e., micromobility) | [signals](signals/README.md) | Simulate traffic lights microscopically diff --git a/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/TestQSimModule.java b/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/TestQSimModule.java index f57e5e54ab6..14d2c36a06a 100644 --- a/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/TestQSimModule.java +++ b/contribs/sbb-extensions/src/test/java/ch/sbb/matsim/mobsim/qsim/pt/TestQSimModule.java @@ -4,10 +4,13 @@ package ch.sbb.matsim.mobsim.qsim.pt; +import com.google.inject.Provides; +import com.google.inject.Singleton; import org.matsim.core.config.Config; +import org.matsim.core.events.EventsUtils; import org.matsim.core.mobsim.qsim.AbstractQSimModule; -import org.matsim.core.mobsim.qsim.pt.ComplexTransitStopHandlerFactory; -import org.matsim.core.mobsim.qsim.pt.TransitStopHandlerFactory; +import org.matsim.core.mobsim.qsim.QSim; +import org.matsim.core.mobsim.qsim.pt.*; import org.matsim.core.replanning.ReplanningContext; /** @@ -29,8 +32,15 @@ public TestQSimModule(Config config) { protected void configureQSim() { bind(ReplanningContext.class).toInstance(context); bind(TransitStopHandlerFactory.class).to(ComplexTransitStopHandlerFactory.class); + bind(TransitDriverAgentFactory.class).to(DefaultTransitDriverAgentFactory.class); } + @Provides + @Singleton + public TransitStopAgentTracker transitStopAgentTracker(QSim qSim) { + return new TransitStopAgentTracker(qSim.getEventsManager()); + } + public static final class DummyReplanningContext implements ReplanningContext { private int iteration = 0; From 2dad8cd766121adda51d807072bb03983ef4af04 Mon Sep 17 00:00:00 2001 From: nixlaos Date: Thu, 21 Sep 2023 11:55:09 +0200 Subject: [PATCH 120/258] correcting typo --- ...sisEventbased.java => RunFreightAnalysisEventBased.java} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/{RunFreightAnalysisEventbased.java => RunFreightAnalysisEventBased.java} (97%) diff --git a/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventbased.java b/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventBased.java similarity index 97% rename from contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventbased.java rename to contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventBased.java index bcc3753f621..d2db6bb2f4c 100644 --- a/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventbased.java +++ b/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventBased.java @@ -48,9 +48,9 @@ * * @author kturner (Kai Martins-Turner) */ -public class RunFreightAnalysisEventbased { +public class RunFreightAnalysisEventBased { - private static final Logger log = LogManager.getLogger(RunFreightAnalysisEventbased.class); + private static final Logger log = LogManager.getLogger(RunFreightAnalysisEventBased.class); //Were is your simulation output, that should be analysed? private final String SIM_OUTPUT_PATH ; @@ -62,7 +62,7 @@ public class RunFreightAnalysisEventbased { * @param analysisOutputPath The directory where the result of the analysis should go to * @param globalCrs */ - public RunFreightAnalysisEventbased(String simOutputPath, String analysisOutputPath, String globalCrs) { + public RunFreightAnalysisEventBased(String simOutputPath, String analysisOutputPath, String globalCrs) { this.SIM_OUTPUT_PATH = simOutputPath; this.ANALYSIS_OUTPUT_PATH = analysisOutputPath; this.GLOBAL_CRS = globalCrs; From 2c7ca00415f458893d20c31d47d49fb2219f9612 Mon Sep 17 00:00:00 2001 From: nixlaos Date: Thu, 21 Sep 2023 13:03:24 +0200 Subject: [PATCH 121/258] jsprit score added to carrier stats --- .../matsim/contrib/freight/analysis/CarrierPlanAnalysis.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/CarrierPlanAnalysis.java b/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/CarrierPlanAnalysis.java index 5892430bf81..8de996a9bf0 100644 --- a/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/CarrierPlanAnalysis.java +++ b/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/CarrierPlanAnalysis.java @@ -40,7 +40,7 @@ public void runAnalysisAndWriteStats(String analysisOutputDirectory) throws IOEx BufferedWriter bw1 = new BufferedWriter(new FileWriter(fileName)); //Write headline: - bw1.write("carrierId \t scoreOfSelectedPlan \t nuOfTours \t nuOfShipments(input) \t nuOfServices(input) "); + bw1.write("carrierId \t MATSimScoreSelectedPlan \t jSpritScoreSelectedPlan \t nuOfTours \t nuOfShipments(input) \t nuOfServices(input) "); bw1.newLine(); final TreeMap, Carrier> sortedCarrierMap = new TreeMap<>(carriers.getCarriers()); @@ -48,6 +48,7 @@ public void runAnalysisAndWriteStats(String analysisOutputDirectory) throws IOEx for (Carrier carrier : sortedCarrierMap.values()) { bw1.write(carrier.getId().toString()); bw1.write("\t" + carrier.getSelectedPlan().getScore()); + bw1.write("\t" + carrier.getSelectedPlan().getJspritScore()); bw1.write("\t" + carrier.getSelectedPlan().getScheduledTours().size()); bw1.write("\t" + carrier.getShipments().size()); bw1.write("\t" + carrier.getServices().size()); From 210f498b3645d0473d1d68e2e886b3906f73953f Mon Sep 17 00:00:00 2001 From: nixlaos Date: Thu, 21 Sep 2023 13:24:17 +0200 Subject: [PATCH 122/258] loading vehicleTypes from allVehicles-file --- .../contrib/freight/analysis/RunFreightAnalysisEventBased.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventBased.java b/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventBased.java index d2db6bb2f4c..3f5792e8ace 100644 --- a/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventBased.java +++ b/contribs/vsp/src/main/java/org/matsim/contrib/freight/analysis/RunFreightAnalysisEventBased.java @@ -81,7 +81,7 @@ public void runAnalysis() throws IOException { //freight settings FreightConfigGroup freightConfigGroup = ConfigUtils.addOrGetModule( config, FreightConfigGroup.class ) ; freightConfigGroup.setCarriersFile( SIM_OUTPUT_PATH + "output_carriers.xml.gz"); - freightConfigGroup.setCarriersVehicleTypesFile(SIM_OUTPUT_PATH + "output_carriersVehicleTypes.xml.gz"); + freightConfigGroup.setCarriersVehicleTypesFile(SIM_OUTPUT_PATH + "output_allVehicles.xml.gz"); //Were to store the analysis output? String analysisOutputDirectory = ANALYSIS_OUTPUT_PATH; From 3d48f591901e0b6aa483d2ae054270d072515e81 Mon Sep 17 00:00:00 2001 From: nixlaos Date: Thu, 21 Sep 2023 13:24:47 +0200 Subject: [PATCH 123/258] new test for event based freight analysis --- .../FreightAnalysisEventBasedTest.java | 25 ++++++++++++++++++ .../output_allVehicles.xml.gz | Bin 0 -> 499 bytes .../output_carriers.xml.gz | Bin 0 -> 750 bytes .../output_events.xml.gz | Bin 0 -> 1622 bytes .../output_network.xml.gz | Bin 0 -> 2493 bytes .../Carrier_stats.tsv | 2 ++ .../Load_perVehicle.tsv | 1 + .../TimeDistance_perVehicle.tsv | 1 + .../TimeDistance_perVehicleType.tsv | 2 ++ 9 files changed, 31 insertions(+) create mode 100644 contribs/vsp/src/test/java/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest.java create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_allVehicles.xml.gz create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_carriers.xml.gz create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_events.xml.gz create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_network.xml.gz create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Carrier_stats.tsv create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Load_perVehicle.tsv create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicle.tsv create mode 100644 contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicleType.tsv diff --git a/contribs/vsp/src/test/java/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest.java b/contribs/vsp/src/test/java/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest.java new file mode 100644 index 00000000000..4974a90d130 --- /dev/null +++ b/contribs/vsp/src/test/java/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest.java @@ -0,0 +1,25 @@ +package org.matsim.contrib.freight.analysis; + +import org.junit.Rule; +import org.junit.Test; +import org.matsim.testcases.MatsimTestUtils; + +import java.io.IOException; + +public class FreightAnalysisEventBasedTest { + + @Rule + public MatsimTestUtils testUtils = new MatsimTestUtils(); + + @Test + public void runFreightAnalysisEventBasedTest() throws IOException { + + RunFreightAnalysisEventBased analysisEventBased = new RunFreightAnalysisEventBased(testUtils.getClassInputDirectory(), testUtils.getOutputDirectory(),null); + analysisEventBased.runAnalysis(); + + MatsimTestUtils.assertEqualFilesLineByLine(testUtils.getInputDirectory() + "Carrier_stats.tsv", testUtils.getOutputDirectory() + "Carrier_stats.tsv"); + MatsimTestUtils.assertEqualFilesLineByLine(testUtils.getInputDirectory() + "Load_perVehicle.tsv", testUtils.getOutputDirectory() + "Load_perVehicle.tsv"); + MatsimTestUtils.assertEqualFilesLineByLine(testUtils.getInputDirectory() + "TimeDistance_perVehicle.tsv", testUtils.getOutputDirectory() + "TimeDistance_perVehicle.tsv"); + MatsimTestUtils.assertEqualFilesLineByLine(testUtils.getInputDirectory() + "TimeDistance_perVehicleType.tsv", testUtils.getOutputDirectory() + "TimeDistance_perVehicleType.tsv"); + } +} diff --git a/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_allVehicles.xml.gz b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_allVehicles.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..199380ef6a00f3cbdf64aa1d06dfc09a60ad7ca5 GIT binary patch literal 499 zcmVr$Diq^u>4LEI;t|1(1$`Lq#{j&Zhebf=Mrv9 zY-XDTzWpvv89}YoZBL2+zWdqtzjJ(ds7h9Yv8YwTM?u6HD5*14MZ%vp55oz+n_XSS zHEdCqFozr!ItsRoGL=pE*16p@44bA2D&Z_vfi^{$V+l6QT*kY&>A~VfZ1c&-T}RO< zd|o}SXnG;i1vhOgPLhU6NE<|a#)!!XS|$G(C5dy zi%WEU^`V#MR0s~!;Cs_>_-umgcZ}3XFQrln{MmziXON{Cq@ zCogDK!2sRRQ9;Z3C@UqKGl8x`FLj&WoAuqj0Kl@MPrTrgveFeV@PcZidHy30Og zkGIHjY}hl;iyEtG@8&SY@wDLcXZgj9A!o)`OZkzP-!hoB=zBvvxJQnBTQSw4=fZwb zQ9y9{AgPcO4)|1`*8WrkbYFhAHP%l)ZLb%Zm+J|sPmK6R0wbZ3$jI2prIA>Px{i2|x@Iwp=mKL) zZe}~1ozLv8?vNRY75T3-!#ME{GE->2AqK%VTvCg9)z;>d%eG) ze~%b-VhqO#KFWBRg#IC8WEn%P$_!6pB^_AD_IUr0b)gv>RDLRI*a;PCER2uc)L4xl zpc1>C1TLr1&}v}Sw@P#_L_NWds$JYMC$N<5{}5$R|F>6RT|(_%wa{s6A;!6gaV}z< zi=0s3em6hzju&_q7a-WQx}In&e@BD*O+mpciCcERg;b|eXN7H@8TGCH gtGk-I>IK%1vHIt%uc>M^4S1-30Br~3G}{aS0KsQ@aR2}S literal 0 HcmV?d00001 diff --git a/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_events.xml.gz b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_events.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..23e96d838a79a1ac748b180e72b12243bb4ce7ec GIT binary patch literal 1622 zcmV-c2C4ZUiwFP!000000L`4=Z`(Ey$Dh}~!thfIbSouNBn2et)800q7>2zXhQlOE zT_;dfZ~N~jJ6V%+#G@j8&(>3$HumT4NFMK(boTk5Z|m7M)DP9&=6s(0qUN)(S>0V$ zo9pxW_vY$nKmUC3?ksG>rg@;hc=7J1$A6zS)wggy&oisk?hBfK@7o`jt0rtN=d=6v zGrNym)uFn+X%?$xU00#b7Ta+1{|}3-T&#Eh`&YQBR_n0H=CgIR`FcLTeXl-dAO4un z+OPX_|Gf{*vTo+H*{1~mGTgVn_`MD=5!T`Q_q)sXtJ{zC5a7$6`s=1?|9tw=;i7CG z|N8b1CdC}JPU`gQ^jd_It&U6{n4&qFT3V^o`P!uob}OS!XK4jbXCEg1baqp7rF3G`qPGu4b}O0-P)j*C z6tiuaa6>yq;nYk!R#5@l5@ffc8c)r0&fkjImLOw_;;D}FMl!Z_NoQp6RBbtDBw|~F zj42AI8pS&!0oxK}cc`$Vf@99yl)N{RFm9RjMg^W)X#YAWT8HH}08^H+D>No3q8dKt zj=$~on^cZZ#oT>#yIjL8pUkii@T=yj8UW%y7a2mmtyZC12CMlCLty(8WcQ)7c&f9! zAC*RSE9&r6uS>G4AO*%Z?I|Jwu*BH=U=aZ(TVkYQN|mlodJlM`*pyKsj3CTd^a8J# z<-X)o42hFoCQ}TLr%kIRi;;d`Yqy2$w8RbcNE zWXy-c!z*nK7t$Sp+3j4c0G9N7QDYr%*;PaU;9N)FgGKn09qXWCLUZGZ+yYK>_qj+2 zL6EV)MZ>Ai3Ek%z&Q*+C%9k2Ww%Sd_gjKt3&v~)~JgnMn4@c@a=j$DRr3-9-f{YzH z1Ew0K_#%xoGNxz&r#cV1&j;=)Dgm(C-S=Qo0VZ4RreZ>G^oiU9t%m$mpFliIna8TS zm`rm8Ly?0{kTLgS(bPQSOTYrQCCFn{T}`^bZPW^l^0!36$rjS6n7~4s>yz&v(1C?C*SF+hjOS-eL^i+gF=T?r+AK9?BFv06 zNAXyvEGY6GxUGl)&`MgrlSTNGt)x*gwDAoAE?-djlS;5~MBKyDS@i-

h34$pjN7Jr`s} zut#`M?Ue7^oEckHbf>E*jd&+!wA`cIC{b{_`4JHlmLEMDKJ7&*9hM$F;(JB0 zHaGB^{RjboVRX+mp!~^3(Ns*SDEfpBqg9Zvj^Uj!MRwnE1-w&WHQW1?M;FC-7LC=);IJV-?_WUscPX^UGOCd zab`@_0**DFfBsCI8B^83u{P(Q`VnWwRMl{-EBNPr#F;TwN4)1Jg7rS-mV}uC0~Xy_ zuX6rv9^y=argj9I75r;1#F+w3?Fcsa9j#LIeEuoljZHP2Y-&Zt1f^E}yK4t{*o1zx UyLR?z&wsf158`1mIN4+X02-?_2LJ#7 literal 0 HcmV?d00001 diff --git a/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_network.xml.gz b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/output_network.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..cb668e2933be9be78d131bab45abf47d9c42a048 GIT binary patch literal 2493 zcmbu6X*iqd8po?ig(9f2jki*zWpuF&J#VhW*4V1kSjsTf+FHhOv_ctzAWSXcpsiLL zK_t^dY^^bur8*);e1FA4TG|^fD-YLgqk9A#Kg{m)4ceq_rluzl*MHMESqk zTHFZTA&`={9URK)y1PG#p5Q0jJKEOgBHAZo{C76iIw!}kr5;lMetza+I)*(N-I8ma#Pp`RJBBuC=4QbVfSEm&a|zuZW(-Z`rUnhqjjp?04f=D}&!J zy<3V*OvRLzHjnM}R*ufhEa8XCa)!#lG}}Ns0dMEox_GMW)56Hur>V9EcKPwVQ%8tNnjUcA*B{o!2Ht zxtQu+Z4^w~%BHE^l-~Y{-tZik`~IExPpRjhv`916Md@4$+QBzEoTfU{wcsIxCWp$s z4&;j05r)7KLm7L>Zwb$2J)FbH z76rs9@U0?HFAOmtJj=|w*|mo>1>4r9gmA=2MwS+E6^6tU2x=h6;n)iIkh;LU z#C8zT>0!?&X$}`t7Dqg}PyQ_tx5yGJH?~C|uOq)cBlF)RPl$I=imB-Bm*ca|3y>(s zqG$(G`75v{{UMDl=o>y-orJ-!LF!UskVA!mgt!&|(6Wg0-s-ts_Sc0CE!?@)03H_E z%AH*Wd0^)|!>40nv>Yy|ppR*R;+Ih=j9Z@51NR9j_ilMk72L0T-}~I|u~m_iZDMUi zw|6t^O|&m`)D2SP3s_V&H|SqJ&EB?&fUSY=?dqs=fNhxv4!@08(Eb<)g}bBUD-NAAg#WG7XugYBz{3%4>t8|A5XaRsY5lJg zgjl@P==XM2D+CYr0KwsfD`5x~bh*aD)odzEN*>6fORYBN!CA+bPCs&A$JUQwUS4M zx_&j$=XF+yEq>%Jz)~N1-)pu0Nd;85iTDCjbS|Pq(m*>%ZZ(3P0L&7G!kezs-*TsJ zR`t9rIF1TTgcVzP39c`^)6~?R;bZ4lQbm-Mp{`ibGgI(1J0@@vKs(m!@BRL zDALhI`FS6~5+x;PQl@4GGbujt4@5WYc`#+|o&lz^_K8{Z?!i`snSKpGFIvT!j%$8_KKZI6cTnqCpo&t?8EX3Ckd?fj4k4hoi9?> zcCqWU*H!0!{7SN%6!=FIosr8O;ZBK4?0Yf#Tyh2_E)cw;o

?5oKQnGvQlw{C7_sE$Cq9ychg&I9P<^zo3GE4I_OpKr${1w3b^e!;GE1J?quey3z3z;_(R`*Kd9_ zo61{hj{&pse|ALIVC;5u(z3*)TGg#C)-W=}EXeR?h}k8)KJQyGR)ga;fc=u=#h_UO zjlU7;nN@AXUoxwF(c$2=f65&daZU0_MO<%OAIk=f+R9eVW>lJE$cMArql`=itZnJQ95H<-J58H^gj?#n9RvGk5_Veo`7sCs*ZGN8yi=t87eXd>_-eWVmD} dA#i=8NA|(syIYN~*v^}5%NZYQPRfA;{{;$c&VT>_ literal 0 HcmV?d00001 diff --git a/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Carrier_stats.tsv b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Carrier_stats.tsv new file mode 100644 index 00000000000..21421192918 --- /dev/null +++ b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Carrier_stats.tsv @@ -0,0 +1,2 @@ +carrierId MATSimScoreSelectedPlan jSpritScoreSelectedPlan nuOfTours nuOfShipments(input) nuOfServices(input) +carrier1 -263.4 null 2 0 7 diff --git a/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Load_perVehicle.tsv b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Load_perVehicle.tsv new file mode 100644 index 00000000000..8cff896d44c --- /dev/null +++ b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/Load_perVehicle.tsv @@ -0,0 +1 @@ +vehicleId capacity maxLoad load state during tour diff --git a/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicle.tsv b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicle.tsv new file mode 100644 index 00000000000..a3716b50cdc --- /dev/null +++ b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicle.tsv @@ -0,0 +1 @@ +vehicleId carrierId vehicleTypeId tourId tourDuration[s] travelDistance[m] costPerSecond[EUR/s] costPerMeter[EUR/m] fixedCosts[EUR] varCostsTime[EUR] varCostsDist[EUR] totalCosts[EUR] diff --git a/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicleType.tsv b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicleType.tsv new file mode 100644 index 00000000000..dacd1779828 --- /dev/null +++ b/contribs/vsp/test/input/org/matsim/contrib/freight/analysis/FreightAnalysisEventBasedTest/runFreightAnalysisEventBasedTest/TimeDistance_perVehicleType.tsv @@ -0,0 +1,2 @@ +vehicleTypeId nuOfVehicles SumOfTourDuration[s] SumOfTravelDistances[m] costPerSecond[EUR/s] costPerMeter[EUR/m] fixedCosts[EUR/veh] varCostsTime[EUR] varCostsDist[EUR] fixedCosts[EUR] totalCosts[EUR] +light 0 0.0 60000.0 0.008 4.7E-4 84.0 0.0 28.2 0.0 28.2 From bba5cbb3267827df7157c0d3c4d1d35edc724133 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Sep 2023 14:28:04 +0000 Subject: [PATCH 124/258] build(deps): bump com.google.errorprone:error_prone_annotations Bumps [com.google.errorprone:error_prone_annotations](https://github.com/google/error-prone) from 2.21.1 to 2.22.0. - [Release notes](https://github.com/google/error-prone/releases) - [Commits](https://github.com/google/error-prone/compare/v2.21.1...v2.22.0) --- updated-dependencies: - dependency-name: com.google.errorprone:error_prone_annotations dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e8637f7a05c..275d32a0877 100644 --- a/pom.xml +++ b/pom.xml @@ -185,7 +185,7 @@ com.google.errorprone error_prone_annotations - 2.21.1 + 2.22.0 From 789ea36dbff48e2f9e3248dfecbce3f4eab67e3c Mon Sep 17 00:00:00 2001 From: tschlenther Date: Thu, 28 Sep 2023 16:45:12 +0200 Subject: [PATCH 125/258] analyse number of rejection by time bin --- .../DrtAnalysisControlerListener.java | 129 ++++++++++++++---- 1 file changed, 102 insertions(+), 27 deletions(-) diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java index 7fd5197abe8..17f55751141 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java @@ -19,31 +19,7 @@ package org.matsim.contrib.drt.analysis; -import static java.util.stream.Collectors.toList; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.io.BufferedWriter; -import java.io.FileOutputStream; -import java.io.IOException; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Function; -import java.util.stream.Collectors; - +import com.google.common.base.Preconditions; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.apache.logging.log4j.LogManager; @@ -70,12 +46,13 @@ import org.matsim.contrib.drt.passenger.events.DrtRequestSubmittedEvent; import org.matsim.contrib.drt.run.DrtConfigGroup; import org.matsim.contrib.drt.schedule.DrtStayTask; +import org.matsim.contrib.dvrp.analysis.VehicleOccupancyProfileCalculator; import org.matsim.contrib.dvrp.fleet.DvrpVehicle; import org.matsim.contrib.dvrp.fleet.DvrpVehicleSpecification; import org.matsim.contrib.dvrp.fleet.FleetSpecification; import org.matsim.contrib.dvrp.optimizer.Request; import org.matsim.contrib.dvrp.passenger.PassengerPickedUpEvent; -import org.matsim.contrib.dvrp.analysis.VehicleOccupancyProfileCalculator; +import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent; import org.matsim.core.config.Config; import org.matsim.core.config.groups.QSimConfigGroup; import org.matsim.core.controler.MatsimServices; @@ -87,7 +64,20 @@ import org.matsim.core.utils.misc.Time; import org.matsim.vehicles.Vehicle; -import com.google.common.base.Preconditions; +import java.awt.*; +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; /** * @author jbischoff @@ -176,6 +166,13 @@ public void notifyIterationEnds(IterationEndsEvent event) { .sorted(Comparator.comparing(leg -> leg.departureTime)) .collect(toList()); + List rejectionEvents = drtEventSequenceCollector.getRejectedRequestSequences() + .values() + .stream() + .map(eventSequence -> eventSequence.getRejected().get()) + .sorted(Comparator.comparing(rejectionEvent -> rejectionEvent.getTime())) + .collect(toList()); + collection2Text(drtEventSequenceCollector.getRejectedRequestSequences().values(), filename(event, "drt_rejections", ".csv"), String.join(delimiter, "time", "personId", "fromLinkId", "toLinkId", "fromX", "fromY", "toX", "toY"), seq -> { DrtRequestSubmittedEvent submission = seq.getSubmitted(); @@ -252,6 +249,7 @@ public void notifyIterationEnds(IterationEndsEvent event) { writeVehicleDistances(drtVehicleStats.getVehicleStates(), filename(event, "vehicleDistanceStats", ".csv"), delimiter); analyseDetours(network, legs, drtVehicleStats.getTravelDistances(), drtCfg, filename(event, "drt_detours"), createGraphs, delimiter); analyseWaitTimes(filename(event, "waitStats"), legs, 1800, createGraphs, delimiter); + analyseRejections(filename(event,"drt_rejections_perTimeBin"), rejectionEvents,1800, createGraphs, delimiter); analyseConstraints(filename(event, "constraints"), legs, createGraphs); double endTime = qSimCfg.getEndTime() @@ -399,6 +397,7 @@ public void notifyShutdown(ShutdownEvent event) { dumpOutput(event.getIteration(), "waitTimeComparison", ".png"); dumpOutput(event.getIteration(), "waitTimeComparison", ".csv"); dumpOutput(event.getIteration(), "drt_rejections", ".csv"); + dumpOutput(event.getIteration(), "drt_rejections_perTimeBin", ".csv"); dumpOutput(event.getIteration(), "drt_legs", ".csv"); dumpOutput(event.getIteration(), "vehicleDistanceStats", ".csv"); dumpOutput(event.getIteration(), "drt_detours", ".csv"); @@ -453,6 +452,33 @@ private static Map> splitLegsIntoBins(Collection le return splitLegs; } + private static Map> splitEventsIntoBins(List rejectionEvents, int binSize_s) { + Map> rejections = new TreeMap<>(); + + int startTime = ((int)(rejectionEvents.get(0).getTime() / binSize_s)) * binSize_s; + int endTime = ((int)(rejectionEvents.get(rejectionEvents.size() - 1).getTime() / binSize_s) + 1) * binSize_s; + + for (int time = startTime; time < endTime; time = time + binSize_s) { + + // rejection list in this timebin + List rejectionList = new ArrayList<>(); + + //Iterate through each rejection + for (PassengerRequestRejectedEvent rejectedEvent : rejectionEvents){ + double rejectionTime = rejectedEvent.getTime(); + if (rejectionTime > endTime || rejectionTime < startTime) { + LogManager.getLogger(DrtAnalysisControlerListener.class).error("wrong end / start Times for analysis"); + } + + if (rejectionTime > time && rejectionTime < time + binSize_s) { + rejectionList.add(rejectedEvent); + } + } + rejections.put((double)time, rejectionList); + } + return rejections; + } + private static void analyzeBoardingsAndDeboardings(List legs, String delimiter, double startTime, double endTime, double timeBinSize, String boardingsFile, String deboardingsFile, Network network) { if (endTime < startTime) { @@ -708,6 +734,55 @@ private static void analyseWaitTimes(String fileName, List legs, int bin } + private static void analyseRejections(String fileName, List rejectionEvents, int binsize_s, boolean createGraphs, String delimiter) { + if (rejectionEvents.size() == 0) + return; + + Map> splitEvents = splitEventsIntoBins(rejectionEvents, binsize_s); + + DecimalFormat format = new DecimalFormat(); + format.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US)); + format.setMinimumIntegerDigits(1); + format.setMaximumFractionDigits(2); + format.setGroupingUsed(false); + + SimpleDateFormat sdf2 = new SimpleDateFormat("HH:mm:ss"); + + BufferedWriter bw = IOUtils.getBufferedWriter(fileName + ".csv"); + TimeSeriesCollection dataset = new TimeSeriesCollection(); + TimeSeries rejections = new TimeSeries("number of rejections"); + + try { + bw.write(String.join(delimiter, "timebin", "rejections")); + + for(Map.Entry> e : splitEvents.entrySet()){ + int drt_numOfRejection = 0; + if (!e.getValue().isEmpty()) { + drt_numOfRejection = e.getValue().size(); + } + + Minute h = new Minute(sdf2.parse(Time.writeTime(e.getKey()))); + + rejections.addOrUpdate(h, Double.valueOf(drt_numOfRejection)); + bw.newLine(); + bw.write(String.join(delimiter, Time.writeTime(e.getKey()) + "",// + format.format(drt_numOfRejection) +"")); + } + + bw.flush(); + bw.close(); + if (createGraphs) { + dataset.addSeries(rejections); + JFreeChart chart = chartProfile(splitEvents.size(), dataset, "Number of rejections", "Number"); + ChartSaveUtils.saveAsPNG(chart, fileName, 1500, 1000); + } + + } catch (IOException | ParseException e) { + + e.printStackTrace(); + } + } + private static JFreeChart chartProfile(int length, TimeSeriesCollection dataset, String descriptor, String yax) { JFreeChart chart = ChartFactory.createTimeSeriesChart(descriptor, "Time", yax, dataset); From 77bec6857f66ce092d9e5195123b1f37646d4ac0 Mon Sep 17 00:00:00 2001 From: tschlenther Date: Thu, 28 Sep 2023 17:14:52 +0200 Subject: [PATCH 126/258] visualize number of rejection per time bin --- .../extension/dashboards/DrtDashboard.java | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/dashboards/DrtDashboard.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/dashboards/DrtDashboard.java index 291c0fbaf92..b7e1aeda0f9 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/dashboards/DrtDashboard.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/dashboards/DrtDashboard.java @@ -140,11 +140,6 @@ public void configure(Header header, Layout layout) { viz.center = data.context().getCenter(); viz.zoom = data.context().mapZoomLevel; }) - //TODO group rejections per time bin. better put it into the plot with wait stats over daty time. -// .el(Line.class, (viz, data) -> { -// viz.dataset = data.output("ITERS/it." + lastIteration + "/*rejections_" + drtConfigGroup.mode + ".csv"); -// viz.x = "time"; -// }) ; // This plot is not absolutely necesarry given the hex plots @@ -163,12 +158,13 @@ public void configure(Header header, Layout layout) { viz.title = "Final Demand and Wait Stats over day time"; viz.description = "Number of rides (customers) is displayed in bars, wait statistics in lines"; - Plotly.DataSet dataset = viz.addDataset(data.output("*_waitStats_" + drtConfigGroup.mode + ".csv")); + Plotly.DataSet waitStats = viz.addDataset(data.output("*_waitStats_" + drtConfigGroup.mode + ".csv")); + Plotly.DataSet rejections = viz.addDataset(data.output("*drt_rejections_perTimeBin_" + drtConfigGroup.mode + ".csv")); viz.layout = tech.tablesaw.plotly.components.Layout.builder() .xAxis(Axis.builder().title("Time Bin").build()) .yAxis(Axis.builder().title("Wait Time [s]").build()) - .yAxis2(Axis.builder().title("Nr of Rides") + .yAxis2(Axis.builder().title("Nr of Rides/Rejections") .side(Axis.Side.right) .overlaying(ScatterTrace.YAxis.Y) .build()) @@ -179,7 +175,7 @@ public void configure(Header header, Layout layout) { .mode(ScatterTrace.Mode.LINE) .name("Average") .build(), - dataset.mapping() + waitStats.mapping() .x("timebin") .y("average_wait") ); @@ -188,7 +184,7 @@ public void configure(Header header, Layout layout) { .mode(ScatterTrace.Mode.LINE) .name("P5") .build(), - dataset.mapping() + waitStats.mapping() .x("timebin") .y("p_5") ); @@ -197,7 +193,7 @@ public void configure(Header header, Layout layout) { .mode(ScatterTrace.Mode.LINE) .name("P95") .build(), - dataset.mapping() + waitStats.mapping() .x("timebin") .y("p_95") ); @@ -207,11 +203,21 @@ public void configure(Header header, Layout layout) { .yAxis(ScatterTrace.YAxis.Y2.toString()) .name("Rides") .build(), - dataset.mapping() + waitStats.mapping() .x("timebin") .y("legs") ); + viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT) + .opacity(0.3) + .yAxis(ScatterTrace.YAxis.Y2.toString()) + .name("Rejections") + .build(), + rejections.mapping() + .x("timebin") + .y("rejections") + ); + }) .el(Area.class, (viz, data) -> { viz.title = "Vehicle occupancy"; //actually, without title the area plot won't work @@ -248,7 +254,7 @@ public void configure(Header header, Layout layout) { dataset.mapping() .x("iteration") .y("rejections") - .color(Plotly.ColorScheme.RdBu) +// .color(Plotly.ColorScheme.RdBu) ); viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT) @@ -257,7 +263,7 @@ public void configure(Header header, Layout layout) { dataset.mapping() .x("iteration") .y("rides") - .color(Plotly.ColorScheme.RdBu) +// .color(Plotly.ColorScheme.RdBu) ); }) @@ -294,7 +300,7 @@ public void configure(Header header, Layout layout) { dataset.mapping() .x("iteration") .y("inVehicleTravelTime_mean") - .color(Plotly.ColorScheme.RdBu) +// .color(Plotly.ColorScheme.RdBu) ); viz.addTrace(BarTrace.builder(Plotly.OBJ_INPUT, Plotly.INPUT) @@ -304,7 +310,7 @@ public void configure(Header header, Layout layout) { dataset.mapping() .x("iteration") .y("wait_average") - .color(Plotly.ColorScheme.RdBu) +// .color(Plotly.ColorScheme.RdBu) ); }) .el(Line.class, (viz, data) -> { From e0adb13e7063ac362f606e2465662331469ebab9 Mon Sep 17 00:00:00 2001 From: Michal Maciejewski Date: Fri, 29 Sep 2023 11:47:12 +0200 Subject: [PATCH 127/258] deps: group dependabot version updates This is to avoid having a lot of coexisting dependabot-triggered PRs (each addressing an individual version bump), which results in a lot of: - CI builds (especially that we require PRs to be in sync with the master branch) - PR-based releases deployed to our mvn repo --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b76b8957033..749aa797fa1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,14 @@ updates: directory: "/" schedule: interval: "daily" + groups: + maven: + patterns: ["*"] + - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" + groups: + github-actions: + patterns: ["*"] From bacf537db21614fe4b5ea450e960d357bcf256a7 Mon Sep 17 00:00:00 2001 From: Michal Maciejewski Date: Fri, 29 Sep 2023 13:27:53 +0200 Subject: [PATCH 128/258] vsp: bump graphhopper-core to 7.0 --- contribs/vsp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/vsp/pom.xml b/contribs/vsp/pom.xml index f7d6c310905..9afdb4adc00 100644 --- a/contribs/vsp/pom.xml +++ b/contribs/vsp/pom.xml @@ -179,7 +179,7 @@ com.graphhopper graphhopper-core - 6.2 + 7.0 From 7f9917a5f9081efa831299fdc8410fa503a88b26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:44:15 +0000 Subject: [PATCH 129/258] build(deps): bump org.apache.poi:poi-ooxml from 5.2.3 to 5.2.4 Bumps org.apache.poi:poi-ooxml from 5.2.3 to 5.2.4. --- updated-dependencies: - dependency-name: org.apache.poi:poi-ooxml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- contribs/vsp/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contribs/vsp/pom.xml b/contribs/vsp/pom.xml index 9afdb4adc00..96a0cd8ca37 100644 --- a/contribs/vsp/pom.xml +++ b/contribs/vsp/pom.xml @@ -140,7 +140,7 @@ org.apache.poi poi-ooxml - 5.2.3 + 5.2.4 From 312924ef786aa53b1671f4258b23170ecca2f7c3 Mon Sep 17 00:00:00 2001 From: Michal Maciejewski Date: Sat, 30 Sep 2023 12:28:02 +0200 Subject: [PATCH 130/258] build: remove obsolete condition from deploy-on-pr-merge The condition was used when deploying snapshots on push to master. --- .github/workflows/deploy-on-pr-merge.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-on-pr-merge.yaml b/.github/workflows/deploy-on-pr-merge.yaml index deed3a54d24..897e56f5980 100644 --- a/.github/workflows/deploy-on-pr-merge.yaml +++ b/.github/workflows/deploy-on-pr-merge.yaml @@ -10,8 +10,8 @@ on: jobs: deploy-snapshot: name: deploy PR-labelled version - # for PR-labelled deployment -- only if closed by merging - if: github.event_name == 'push' || github.event.pull_request.merged == true + # only if PR closed by merging + if: github.event.pull_request.merged == true runs-on: ubuntu-latest From 26c97331424d5c3e4d7cfc927fc7664dadf2e5ff Mon Sep 17 00:00:00 2001 From: Michal Maciejewski Date: Sat, 30 Sep 2023 12:31:57 +0200 Subject: [PATCH 131/258] build: simplify maven server-id definition in deploy-on-pr-merge The existing expression was necessary when deploying snapshots on push to master. Now we only deploy proper (i.e. non-snapshot) releases. --- .github/workflows/deploy-on-pr-merge.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-on-pr-merge.yaml b/.github/workflows/deploy-on-pr-merge.yaml index 897e56f5980..9e56e91d071 100644 --- a/.github/workflows/deploy-on-pr-merge.yaml +++ b/.github/workflows/deploy-on-pr-merge.yaml @@ -25,7 +25,7 @@ jobs: java-version: 17 distribution: 'zulu' cache: 'maven' - server-id: ${{ github.event_name == 'push' && 'matsim-snapshots' || 'matsim-releases' }} #choose mvn repo + server-id: 'matsim-releases' server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD From 6d2a734c8b471e54e325f322d78d67eaf1d523be Mon Sep 17 00:00:00 2001 From: Michal Maciejewski Date: Sat, 30 Sep 2023 19:29:00 +0200 Subject: [PATCH 132/258] build: submit dependency graph when deploy-on-pr-merge Generate a complete dependency graph and submit the graph to the GitHub repository. The goal is to improve security alerts from dependabot, because dependabot is not able to compute the complete dependency graph. --- .github/workflows/deploy-on-pr-merge.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deploy-on-pr-merge.yaml b/.github/workflows/deploy-on-pr-merge.yaml index 9e56e91d071..332a3dfe178 100644 --- a/.github/workflows/deploy-on-pr-merge.yaml +++ b/.github/workflows/deploy-on-pr-merge.yaml @@ -43,5 +43,10 @@ jobs: MAVEN_USERNAME: ${{ secrets.REPOMATSIM_USERNAME }} MAVEN_PASSWORD: ${{ secrets.REPOMATSIM_TOKEN }} + - name: Submit Dependency Graph + # Generate a complete dependency graph and submit the graph to the GitHub repository. + # The goal is to improve security alerts from dependabot, because dependabot is not able to compute the complete dependency graph. + uses: advanced-security/maven-dependency-submission-action@v3 + env: MAVEN_OPTS: -Xmx2g From 293c6b824b05f4fda4e47fca74d5b6e955febbd7 Mon Sep 17 00:00:00 2001 From: Michal Maciejewski Date: Sun, 1 Oct 2023 11:36:15 +0200 Subject: [PATCH 133/258] build: add CodeQL workflow on push to master --- .github/workflows/codeql.yml | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000000..afe0a9d4073 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,49 @@ +name: CodeQL + +on: + push: + branches: 'master' + +jobs: + analyze: + name: Analyze + runs-on: 'ubuntu-latest' + timeout-minutes: 360 + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'zulu' + cache: 'maven' + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: 'java' + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:java" From 030956af32284759a3b978fdccc82aee0511d58b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:50:34 +0000 Subject: [PATCH 134/258] build(deps): bump the maven group with 1 update Bumps the maven group with 1 update: [com.github.luben:zstd-jni](https://github.com/luben/zstd-jni). - [Commits](https://github.com/luben/zstd-jni/compare/v.1.5.5-5...v1.5.5-6) --- updated-dependencies: - dependency-name: com.github.luben:zstd-jni dependency-type: direct:production update-type: version-update:semver-patch dependency-group: maven ... Signed-off-by: dependabot[bot] --- matsim/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matsim/pom.xml b/matsim/pom.xml index b7feb34e61d..4dc0348e536 100644 --- a/matsim/pom.xml +++ b/matsim/pom.xml @@ -203,7 +203,7 @@ com.github.luben zstd-jni - 1.5.5-5 + 1.5.5-6 jakarta.validation From 10f6c8edffbd501e4967db5e5bc69ba53214f3ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 15:52:16 +0000 Subject: [PATCH 135/258] build(deps): bump the github-actions group with 1 update Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index afe0a9d4073..021bae1c67c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v3 From a6921eaad7ca07688c7922711e3f93086b02e445 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:23:16 +0000 Subject: [PATCH 136/258] build(deps): bump the maven group with 1 update Bumps the maven group with 1 update: [org.checkerframework:checker-qual](https://github.com/typetools/checker-framework). - [Release notes](https://github.com/typetools/checker-framework/releases) - [Changelog](https://github.com/typetools/checker-framework/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/typetools/checker-framework/compare/checker-framework-3.38.0...checker-framework-3.39.0) --- updated-dependencies: - dependency-name: org.checkerframework:checker-qual dependency-type: direct:production update-type: version-update:semver-minor dependency-group: maven ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 275d32a0877..867da53e42b 100644 --- a/pom.xml +++ b/pom.xml @@ -264,7 +264,7 @@ org.checkerframework checker-qual - 3.38.0 + 3.39.0 From 5e50d86826d9a0a6bb2dcc6a53e6ced8d88c8999 Mon Sep 17 00:00:00 2001 From: nixlaos Date: Thu, 5 Oct 2023 10:55:26 +0200 Subject: [PATCH 137/258] added test input, changed filenames in test --- .../carrier/CarrierEventsReadersTest.java | 14 +- .../serviceBasedEvents.xml | 195 ++++++++++++++ .../shipmentBasedEvents.xml | 243 ++++++++++++++++++ 3 files changed, 446 insertions(+), 6 deletions(-) create mode 100644 contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/serviceBasedEvents.xml create mode 100644 contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/shipmentBasedEvents.xml diff --git a/contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java b/contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java index f19068177c2..43509b23a37 100644 --- a/contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java +++ b/contribs/freight/src/test/java/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest.java @@ -24,12 +24,14 @@ public void testWriteReadServiceBasedEvents() { eventsManager.addHandler(collector); eventsManager.initProcessing(); - carrierEventsReaders.readFile(utils.getClassInputDirectory() + "serviceBasedEventsFile.xml"); + carrierEventsReaders.readFile(utils.getClassInputDirectory() + "serviceBasedEvents.xml"); eventsManager.finishProcessing(); + //Hier müsste dann wohl "serviceBasedEventsWritten.xml" geschrieben werden und dann auch nochmal der eventsManager für die ausgeschriebene Datei durchlaufen werden + assertEquals("number of events should be same", collector.getEvents().size(), collector.getEvents().size()); - MatsimTestUtils.assertEqualEventsFiles(utils.getClassInputDirectory() + "serviceBasedEventsFile.xml", utils.getClassInputDirectory() + "serviceBasedEventsFileWritten.xml"); - MatsimTestUtils.assertEqualFilesLineByLine(utils.getClassInputDirectory() + "serviceBasedEventsFile.xml", utils.getClassInputDirectory() + "serviceBasedEventsFileWritten.xml"); + MatsimTestUtils.assertEqualEventsFiles(utils.getClassInputDirectory() + "serviceBasedEvents.xml", utils.getClassInputDirectory() + "serviceBasedEventsWritten.xml"); + MatsimTestUtils.assertEqualFilesLineByLine(utils.getClassInputDirectory() + "serviceBasedEvents.xml", utils.getClassInputDirectory() + "serviceBasedEventsWritten.xml"); } @Test @@ -40,12 +42,12 @@ public void testWriteReadShipmentBasedEvents() { eventsManager.addHandler(collector); eventsManager.initProcessing(); - carrierEventsReaders.readFile(utils.getClassInputDirectory() + "shipmentBasedEventsFile.xml"); + carrierEventsReaders.readFile(utils.getClassInputDirectory() + "shipmentBasedEvents.xml"); eventsManager.finishProcessing(); assertEquals("number of events should be same", collector.getEvents().size(), collector.getEvents().size()); - MatsimTestUtils.assertEqualEventsFiles(utils.getClassInputDirectory() + "shipmentBasedEventsFile.xml", utils.getClassInputDirectory() + "shipmentBasedEventsFileWritten.xml"); - MatsimTestUtils.assertEqualFilesLineByLine(utils.getClassInputDirectory() + "shipmentBasedEventsFile.xml", utils.getClassInputDirectory() + "shipmentBasedEventsFileWritten.xml"); + MatsimTestUtils.assertEqualEventsFiles(utils.getClassInputDirectory() + "shipmentBasedEvents.xml", utils.getClassInputDirectory() + "shipmentBasedEventsWritten.xml"); + MatsimTestUtils.assertEqualFilesLineByLine(utils.getClassInputDirectory() + "shipmentBasedEvents.xml", utils.getClassInputDirectory() + "shipmentBasedEventsWritten.xml"); } diff --git a/contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/serviceBasedEvents.xml b/contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/serviceBasedEvents.xml new file mode 100644 index 00000000000..f2bb0c1175e --- /dev/null +++ b/contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/serviceBasedEvents.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/shipmentBasedEvents.xml b/contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/shipmentBasedEvents.xml new file mode 100644 index 00000000000..82c90c41829 --- /dev/null +++ b/contribs/freight/test/input/org/matsim/contrib/freight/carrier/CarrierEventsReadersTest/shipmentBasedEvents.xml @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f1aeebe5f4e362e80c9486c70900aeb92f0da5e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:30:21 +0000 Subject: [PATCH 138/258] build(deps): bump the maven group with 1 update Bumps the maven group with 1 update: [com.google.protobuf:protobuf-java](https://github.com/protocolbuffers/protobuf). - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.24.3...v3.24.4) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production update-type: version-update:semver-patch dependency-group: maven ... Signed-off-by: dependabot[bot] --- contribs/hybridsim/pom.xml | 2 +- contribs/protobuf/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/hybridsim/pom.xml b/contribs/hybridsim/pom.xml index d340e18bbb6..b8ad9953749 100644 --- a/contribs/hybridsim/pom.xml +++ b/contribs/hybridsim/pom.xml @@ -10,7 +10,7 @@ hybridsim - 3.24.3 + 3.24.4 1.58.0 diff --git a/contribs/protobuf/pom.xml b/contribs/protobuf/pom.xml index 7c8509d4e15..a615a38b049 100644 --- a/contribs/protobuf/pom.xml +++ b/contribs/protobuf/pom.xml @@ -11,7 +11,7 @@ protobuf - 3.24.3 + 3.24.4 From f05781917352893c42f512a17e6589a0e7a6ddb8 Mon Sep 17 00:00:00 2001 From: steffenaxer <26229392+steffenaxer@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:25:02 +0200 Subject: [PATCH 139/258] Initial version of returnToDepot after timeout --- .../drt/optimizer/DefaultDrtOptimizer.java | 33 ++++++++++++++----- .../contrib/drt/run/DrtConfigGroup.java | 8 +++++ .../drt/scheduler/EmptyVehicleRelocator.java | 7 ++-- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java index 0c7fd8da55e..e31463d0c35 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java @@ -43,6 +43,8 @@ import org.matsim.core.mobsim.framework.MobsimTimer; import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent; +import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STAY; + /** * @author michalm */ @@ -95,6 +97,7 @@ public void notifyMobsimBeforeSimStep(@SuppressWarnings("rawtypes") MobsimBefore requestInserter.scheduleUnplannedRequests(unplannedRequests.getSchedulableRequests()); } + returnToDepot(drtCfg.returnToDepotEvaluationInterval, drtCfg.returnToDepotTimeout); if (rebalancingInterval != null && e.getSimulationTime() % rebalancingInterval == 0) { if (!scheduleTimingUpdated) { for (DvrpVehicle v : fleet.getVehicles().values()) { @@ -116,7 +119,7 @@ private void rebalanceFleet() { for (Relocation r : relocations) { Link currentLink = ((DrtStayTask)r.vehicle.getSchedule().getCurrentTask()).getLink(); if (currentLink != r.link) { - relocator.relocateVehicle(r.vehicle, r.link); + relocator.relocateVehicle(r.vehicle, r.link, EmptyVehicleRelocator.RELOCATE_VEHICLE_TASK_TYPE); } } } @@ -130,15 +133,29 @@ public void requestSubmitted(Request request) { @Override public void nextTask(DvrpVehicle vehicle) { scheduleTimingUpdater.updateBeforeNextTask(vehicle); - vehicle.getSchedule().nextTask(); + } - // if STOP->STAY then choose the best depot - if (drtCfg.idleVehiclesReturnToDepots && Depots.isSwitchingFromStopToStay(vehicle)) { - Link depotLink = depotFinder.findDepot(vehicle); - if (depotLink != null) { - relocator.relocateVehicle(vehicle, depotLink); - } + private void returnToDepot(double evaluationInterval, double timeout) { + if (drtCfg.idleVehiclesReturnToDepots && mobsimTimer.getTimeOfDay() % evaluationInterval == 0) { + Stream backToDepot = fleet.getVehicles().values().stream() + .filter(scheduleInquiry::isIdle).filter(v -> stayTimeoutExceeded(v, timeout)); + + backToDepot.forEach(v -> { + Link depotLink = depotFinder.findDepot(v); + if (depotLink != null) { + relocator.relocateVehicle(v, depotLink, EmptyVehicleRelocator.RELOCATE_VEHICLE_TO_DEPOT_TASK_TYPE); + } + }); + } + } + + boolean stayTimeoutExceeded(DvrpVehicle vehicle, double timeout) { + if (STAY.isBaseTypeOf(vehicle.getSchedule().getCurrentTask())) { + double now = mobsimTimer.getTimeOfDay(); + double taskStart = vehicle.getSchedule().getCurrentTask().getBeginTime(); + return (now - taskStart) > timeout; } + return false; } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java index 43c2cff26fb..2acc3109c4a 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java @@ -130,6 +130,14 @@ public static DrtConfigGroup getSingleModeDrtConfig(Config config) { @Comment("Idle vehicles return to the nearest of all start links. See: DvrpVehicle.getStartLink()") public boolean idleVehiclesReturnToDepots = false; + @Parameter + @Comment("Specifies the time that needs to be exceeded in order to return a vehicle back into depot.") + public double returnToDepotTimeout = 1800; + + @Parameter + @Comment("Specifies the time interval a vehicle gets evaluated to be send back to depot") + public double returnToDepotEvaluationInterval = 300; + public enum OperationalScheme { stopbased, door2door, serviceAreaBased } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java index 57567dd5634..446930ecc60 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java @@ -41,6 +41,7 @@ */ public class EmptyVehicleRelocator { public static final DrtTaskType RELOCATE_VEHICLE_TASK_TYPE = new DrtTaskType("RELOCATE", DRIVE); + public static final DrtTaskType RELOCATE_VEHICLE_TO_DEPOT_TASK_TYPE = new DrtTaskType("RELOCATE_DEPOT", DRIVE); private final TravelTime travelTime; private final MobsimTimer timer; @@ -55,7 +56,7 @@ public EmptyVehicleRelocator(Network network, TravelTime travelTime, TravelDisut router = new SpeedyALTFactory().createPathCalculator(network, travelDisutility, travelTime); } - public void relocateVehicle(DvrpVehicle vehicle, Link link) { + public void relocateVehicle(DvrpVehicle vehicle, Link link, DrtTaskType relocationTaskType) { DrtStayTask currentTask = (DrtStayTask)vehicle.getSchedule().getCurrentTask(); Link currentLink = currentTask.getLink(); @@ -63,12 +64,12 @@ public void relocateVehicle(DvrpVehicle vehicle, Link link) { VrpPathWithTravelData path = VrpPaths.calcAndCreatePath(currentLink, link, timer.getTimeOfDay(), router, travelTime); if (path.getArrivalTime() < vehicle.getServiceEndTime()) { - relocateVehicleImpl(vehicle, path); + relocateVehicleImpl(vehicle, path, relocationTaskType); } } } - private void relocateVehicleImpl(DvrpVehicle vehicle, VrpPathWithTravelData vrpPath) { + private void relocateVehicleImpl(DvrpVehicle vehicle, VrpPathWithTravelData vrpPath, DrtTaskType taskType) { Schedule schedule = vehicle.getSchedule(); DrtStayTask stayTask = (DrtStayTask)schedule.getCurrentTask(); if (stayTask.getTaskIdx() != schedule.getTaskCount() - 1) { From 9d8a9c66c3f8e0953bf6d73ac89840c479d3544a Mon Sep 17 00:00:00 2001 From: steffenaxer <26229392+steffenaxer@users.noreply.github.com> Date: Thu, 5 Oct 2023 20:11:21 +0200 Subject: [PATCH 140/258] Reformat code --- .../drt/optimizer/DefaultDrtOptimizer.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java index e31463d0c35..df0f1c4ce83 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DefaultDrtOptimizer.java @@ -138,15 +138,15 @@ public void nextTask(DvrpVehicle vehicle) { private void returnToDepot(double evaluationInterval, double timeout) { if (drtCfg.idleVehiclesReturnToDepots && mobsimTimer.getTimeOfDay() % evaluationInterval == 0) { - Stream backToDepot = fleet.getVehicles().values().stream() - .filter(scheduleInquiry::isIdle).filter(v -> stayTimeoutExceeded(v, timeout)); - - backToDepot.forEach(v -> { - Link depotLink = depotFinder.findDepot(v); - if (depotLink != null) { - relocator.relocateVehicle(v, depotLink, EmptyVehicleRelocator.RELOCATE_VEHICLE_TO_DEPOT_TASK_TYPE); - } - }); + fleet.getVehicles().values().stream() + .filter(scheduleInquiry::isIdle) + .filter(v -> stayTimeoutExceeded(v, timeout)) + .forEach(v -> { + Link depotLink = depotFinder.findDepot(v); + if (depotLink != null) { + relocator.relocateVehicle(v, depotLink, EmptyVehicleRelocator.RELOCATE_VEHICLE_TO_DEPOT_TASK_TYPE); + } + }); } } From 13369d0eefe33df4d227e40ec159124db5e8b08f Mon Sep 17 00:00:00 2001 From: steffenaxer <26229392+steffenaxer@users.noreply.github.com> Date: Thu, 5 Oct 2023 20:11:31 +0200 Subject: [PATCH 141/258] Change comment --- .../main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java index 2acc3109c4a..2c721931f79 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java @@ -131,11 +131,11 @@ public static DrtConfigGroup getSingleModeDrtConfig(Config config) { public boolean idleVehiclesReturnToDepots = false; @Parameter - @Comment("Specifies the time that needs to be exceeded in order to return a vehicle back into depot.") + @Comment("Specifies the duration a vehicle needs to be idle in order to get send back to the depot.") public double returnToDepotTimeout = 1800; @Parameter - @Comment("Specifies the time interval a vehicle gets evaluated to be send back to depot") + @Comment("Specifies the time interval a vehicle gets evaluated to be send back to depot.") public double returnToDepotEvaluationInterval = 300; public enum OperationalScheme { From f5012fe4a9e82b50816d282a3284cb477e6b20ac Mon Sep 17 00:00:00 2001 From: steffenaxer <26229392+steffenaxer@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:05:35 +0200 Subject: [PATCH 142/258] Add warning, adjust defaults --- .../org/matsim/contrib/drt/run/DrtConfigGroup.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java index 2c721931f79..5ef3e7adadd 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtConfigGroup.java @@ -131,12 +131,13 @@ public static DrtConfigGroup getSingleModeDrtConfig(Config config) { public boolean idleVehiclesReturnToDepots = false; @Parameter - @Comment("Specifies the duration a vehicle needs to be idle in order to get send back to the depot.") - public double returnToDepotTimeout = 1800; + @Comment("Specifies the duration (seconds) a vehicle needs to be idle in order to get send back to the depot." + + "Please be aware, that returnToDepotEvaluationInterval describes the minimal time a vehicle will be idle before it gets send back to depot.") + public double returnToDepotTimeout = 60; @Parameter - @Comment("Specifies the time interval a vehicle gets evaluated to be send back to depot.") - public double returnToDepotEvaluationInterval = 300; + @Comment("Specifies the time interval (seconds) a vehicle gets evaluated to be send back to depot.") + public double returnToDepotEvaluationInterval = 60; public enum OperationalScheme { stopbased, door2door, serviceAreaBased @@ -281,6 +282,11 @@ protected void checkConsistency(Config config) { + " in order to speed up the DRT route update during the replanning phase."); } + if (this.idleVehiclesReturnToDepots && this.returnToDepotTimeout < this.returnToDepotEvaluationInterval) { + log.warn("idleVehiclesReturnToDepots is active and returnToDepotTimeout < returnToDepotEvaluationInterval. " + + "Vehicles will be send back to depot after {} seconds",returnToDepotEvaluationInterval); + } + Verify.verify(getParameterSets(MinCostFlowRebalancingStrategyParams.SET_NAME).size() <= 1, "More than one rebalancing parameter sets is specified"); From be65878f5e6035bd6d40e0d01b95611e22e9ad60 Mon Sep 17 00:00:00 2001 From: steffenaxer <26229392+steffenaxer@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:48:50 +0200 Subject: [PATCH 143/258] Pass through task type --- .../matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java index 446930ecc60..017a424c15a 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/scheduler/EmptyVehicleRelocator.java @@ -69,7 +69,7 @@ public void relocateVehicle(DvrpVehicle vehicle, Link link, DrtTaskType relocati } } - private void relocateVehicleImpl(DvrpVehicle vehicle, VrpPathWithTravelData vrpPath, DrtTaskType taskType) { + private void relocateVehicleImpl(DvrpVehicle vehicle, VrpPathWithTravelData vrpPath, DrtTaskType relocationTaskType) { Schedule schedule = vehicle.getSchedule(); DrtStayTask stayTask = (DrtStayTask)schedule.getCurrentTask(); if (stayTask.getTaskIdx() != schedule.getTaskCount() - 1) { @@ -77,7 +77,7 @@ private void relocateVehicleImpl(DvrpVehicle vehicle, VrpPathWithTravelData vrpP } stayTask.setEndTime(vrpPath.getDepartureTime()); // finish STAY - schedule.addTask(taskFactory.createDriveTask(vehicle, vrpPath, RELOCATE_VEHICLE_TASK_TYPE)); // add RELOCATE + schedule.addTask(taskFactory.createDriveTask(vehicle, vrpPath, relocationTaskType)); // add RELOCATE // append STAY schedule.addTask(taskFactory.createStayTask(vehicle, vrpPath.getArrivalTime(), vehicle.getServiceEndTime(), vrpPath.getToLink())); From ba9603580d9e3e745bdb261e65c32da8d7de3393 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Mon, 9 Oct 2023 14:19:50 +0200 Subject: [PATCH 144/258] remove FastDijkstra, FastAStarEuclidean and FastAStarLandmarks They are no longer of much use with SpeedyALT Closes #2816 --- .../traveltime/SampleValidationRoutes.java | 4 +- .../traveltime/TravelTimeComparison.java | 7 +- .../scheduler/ShiftTaskSchedulerImpl.java | 5 +- .../jsprit/NetworkBasedTransportCosts.java | 8 +- .../StrategyManagerFactoryForTests.java | 4 +- .../sim/ParkingManagerModule.java | 7 - .../qsimengine/router/TrainRouter.java | 4 +- .../VspConfigConsistencyCheckerImpl.java | 32 ++-- .../config/groups/ControlerConfigGroup.java | 29 ++-- .../core/router/FastAStarEuclidean.java | 140 ----------------- .../router/FastAStarEuclideanFactory.java | 93 ----------- .../core/router/FastAStarLandmarks.java | 146 ------------------ .../router/FastAStarLandmarksFactory.java | 104 ------------- .../org/matsim/core/router/FastDijkstra.java | 142 ----------------- .../core/router/FastDijkstraFactory.java | 97 ------------ .../core/router/FastMultiNodeDijkstra.java | 45 +++--- .../router/LeastCostPathCalculatorModule.java | 6 +- .../matsim/core/router/speedy/SpeedyALT.java | 2 +- .../core/router/util/RoutingNetwork.java | 11 +- .../TravelTimeDataArray.java | 4 +- ...rsonalizableDisutilityIntegrationTest.java | 83 +++------- .../org/matsim/core/router/RoutingIT.java | 64 +------- .../integration/replanning/ReRoutingIT.java | 36 +---- 23 files changed, 106 insertions(+), 967 deletions(-) delete mode 100644 matsim/src/main/java/org/matsim/core/router/FastAStarEuclidean.java delete mode 100644 matsim/src/main/java/org/matsim/core/router/FastAStarEuclideanFactory.java delete mode 100644 matsim/src/main/java/org/matsim/core/router/FastAStarLandmarks.java delete mode 100644 matsim/src/main/java/org/matsim/core/router/FastAStarLandmarksFactory.java delete mode 100644 matsim/src/main/java/org/matsim/core/router/FastDijkstra.java delete mode 100644 matsim/src/main/java/org/matsim/core/router/FastDijkstraFactory.java diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/SampleValidationRoutes.java b/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/SampleValidationRoutes.java index da7a70673d6..168bf28499e 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/SampleValidationRoutes.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/SampleValidationRoutes.java @@ -24,8 +24,8 @@ import org.matsim.application.options.OutputOptions; import org.matsim.application.options.ShpOptions; import org.matsim.core.network.NetworkUtils; -import org.matsim.core.router.FastDijkstraFactory; import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility; +import org.matsim.core.router.speedy.SpeedyALTFactory; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.scenario.ProjectionUtils; import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; @@ -156,7 +156,7 @@ public Integer call() throws Exception { FreeSpeedTravelTime tt = new FreeSpeedTravelTime(); OnlyTimeDependentTravelDisutility util = new OnlyTimeDependentTravelDisutility(tt); - LeastCostPathCalculator router = new FastDijkstraFactory(false).createPathCalculator(network, util, tt); + LeastCostPathCalculator router = new SpeedyALTFactory().createPathCalculator(network, util, tt); List routes = sampleRoutes(network, router, rnd); diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/TravelTimeComparison.java b/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/TravelTimeComparison.java index 5947ce4231b..3c22fc678a3 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/TravelTimeComparison.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/traffic/traveltime/TravelTimeComparison.java @@ -11,15 +11,14 @@ import org.matsim.application.options.OutputOptions; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.events.EventsUtils; -import org.matsim.core.router.FastDijkstraFactory; import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility; +import org.matsim.core.router.speedy.SpeedyALTFactory; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.TravelTime; import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; import org.matsim.core.trafficmonitoring.TravelTimeCalculator; import org.matsim.core.utils.io.IOUtils; import picocli.CommandLine; -import scala.util.parsing.combinator.testing.Str; import tech.tablesaw.api.ColumnType; import tech.tablesaw.api.DoubleColumn; import tech.tablesaw.api.Row; @@ -81,8 +80,8 @@ public Integer call() throws Exception { OnlyTimeDependentTravelDisutility util = new OnlyTimeDependentTravelDisutility(tt); - LeastCostPathCalculator congestedRouter = new FastDijkstraFactory(false).createPathCalculator(network, util, tt); - LeastCostPathCalculator freeflowRouter = new FastDijkstraFactory(false).createPathCalculator(network, new OnlyTimeDependentTravelDisutility(fs), fs); + LeastCostPathCalculator congestedRouter = new SpeedyALTFactory().createPathCalculator(network, util, tt); + LeastCostPathCalculator freeflowRouter = new SpeedyALTFactory().createPathCalculator(network, new OnlyTimeDependentTravelDisutility(fs), fs); data.addColumns( DoubleColumn.create("simulated", data.rowCount()), diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/scheduler/ShiftTaskSchedulerImpl.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/scheduler/ShiftTaskSchedulerImpl.java index 8b076be9b0d..f5101d68987 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/scheduler/ShiftTaskSchedulerImpl.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/scheduler/ShiftTaskSchedulerImpl.java @@ -4,7 +4,6 @@ import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; -import org.matsim.contrib.drt.extension.operations.DrtOperationsParams; import org.matsim.contrib.drt.extension.operations.shifts.config.ShiftsParams; import org.matsim.contrib.drt.extension.operations.shifts.fleet.ShiftDvrpVehicle; import org.matsim.contrib.drt.extension.operations.shifts.schedule.*; @@ -26,7 +25,7 @@ import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker; import org.matsim.contrib.dvrp.util.LinkTimePair; import org.matsim.core.mobsim.framework.MobsimTimer; -import org.matsim.core.router.FastAStarEuclideanFactory; +import org.matsim.core.router.speedy.SpeedyALTFactory; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.router.util.TravelTime; @@ -60,7 +59,7 @@ public ShiftTaskSchedulerImpl(Network network, TravelTime travelTime, TravelDisu this.taskFactory = taskFactory; this.network = network; this.drtShiftParams = drtShiftParams; - this.router = new FastAStarEuclideanFactory().createPathCalculator(network, travelDisutility, travelTime); + this.router = new SpeedyALTFactory().createPathCalculator(network, travelDisutility, travelTime); ShiftSchedules.initSchedules(operationFacilities, fleet, taskFactory); } diff --git a/contribs/freight/src/main/java/org/matsim/contrib/freight/jsprit/NetworkBasedTransportCosts.java b/contribs/freight/src/main/java/org/matsim/contrib/freight/jsprit/NetworkBasedTransportCosts.java index 6cb52bcc5a2..1d6fb1f6f6c 100644 --- a/contribs/freight/src/main/java/org/matsim/contrib/freight/jsprit/NetworkBasedTransportCosts.java +++ b/contribs/freight/src/main/java/org/matsim/contrib/freight/jsprit/NetworkBasedTransportCosts.java @@ -23,7 +23,7 @@ import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.population.Person; import org.matsim.contrib.freight.carrier.CarrierVehicle; -import org.matsim.core.router.FastDijkstraFactory; +import org.matsim.core.router.speedy.SpeedyALTFactory; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.LeastCostPathCalculator.Path; import org.matsim.core.router.util.LeastCostPathCalculatorFactory; @@ -369,7 +369,7 @@ public static Builder newInstance(Network network) { private int timeSliceWidth = Integer.MAX_VALUE; - private LeastCostPathCalculatorFactory leastCostPathCalculatorFactory = (network, travelCosts, travelTimes) -> new FastDijkstraFactory().createPathCalculator(network, travelCosts, travelTime); + private LeastCostPathCalculatorFactory leastCostPathCalculatorFactory = (network, travelCosts, travelTimes) -> new SpeedyALTFactory().createPathCalculator(network, travelCosts, travelTime); private VehicleTypeDependentRoadPricingCalculator roadPricingCalculator = new VehicleTypeDependentRoadPricingCalculator(); @@ -455,7 +455,7 @@ public Builder setFIFO(boolean isFIFO) { * each thread a new LCPA is created with the same LCPA-factory. That is, * memorizing data in the factory-obj might violate thread-safety. *

- * By default, it use {@link FastDijkstraFactory} + * By default, it use {@link SpeedyALTFactory} * * @param {@link {@link LeastCostPathCalculatorFactory} * @return this builder @@ -548,7 +548,7 @@ public void addVehicleTypeSpecificCosts(String typeId, double fix, double perSec private final VehicleTypeDependentRoadPricingCalculator roadPricingCalc; /** - * by default sets the {@link FastDijkstraFactory} + * by default sets the {@link SpeedyALTFactory} */ private final LeastCostPathCalculatorFactory leastCostPathCalculatorFactory; diff --git a/contribs/freight/src/test/java/org/matsim/contrib/freight/mobsim/StrategyManagerFactoryForTests.java b/contribs/freight/src/test/java/org/matsim/contrib/freight/mobsim/StrategyManagerFactoryForTests.java index 7a84c5fa2e3..e7c4e9c88ee 100644 --- a/contribs/freight/src/test/java/org/matsim/contrib/freight/mobsim/StrategyManagerFactoryForTests.java +++ b/contribs/freight/src/test/java/org/matsim/contrib/freight/mobsim/StrategyManagerFactoryForTests.java @@ -13,7 +13,7 @@ import org.matsim.contrib.freight.controler.FreightUtils; import org.matsim.core.replanning.GenericPlanStrategyImpl; import org.matsim.core.replanning.selectors.BestPlanSelector; -import org.matsim.core.router.FastDijkstraFactory; +import org.matsim.core.router.speedy.SpeedyALTFactory; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.router.util.TravelTime; @@ -57,7 +57,7 @@ private double disutility(double distance, double time) { @Override public CarrierStrategyManager get() { - final LeastCostPathCalculator router = new FastDijkstraFactory().createPathCalculator(network, new MyTravelCosts(travelTimes.get(TransportMode.car)), travelTimes.get(TransportMode.car)); + final LeastCostPathCalculator router = new SpeedyALTFactory().createPathCalculator(network, new MyTravelCosts(travelTimes.get(TransportMode.car)), travelTimes.get(TransportMode.car)); GenericPlanStrategyImpl planStrat_reRoutePlan = new GenericPlanStrategyImpl<>( new BestPlanSelector<>() ); planStrat_reRoutePlan.addStrategyModule(new CarrierReRouteVehicles.Factory(router, network, travelTimes.get(TransportMode.car )).build() ); diff --git a/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingsearch/sim/ParkingManagerModule.java b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingsearch/sim/ParkingManagerModule.java index 475d6d85442..dff408ec017 100644 --- a/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingsearch/sim/ParkingManagerModule.java +++ b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingsearch/sim/ParkingManagerModule.java @@ -27,14 +27,7 @@ import org.matsim.contrib.parking.parkingsearch.manager.ParkingSearchManager; import org.matsim.contrib.parking.parkingsearch.manager.ZoneParkingManager; import org.matsim.core.config.Config; -import org.matsim.core.config.groups.ControlerConfigGroup; import org.matsim.core.controler.AbstractModule; -import org.matsim.core.router.AStarLandmarksFactory; -import org.matsim.core.router.DijkstraFactory; -import org.matsim.core.router.FastAStarLandmarksFactory; -import org.matsim.core.router.FastDijkstraFactory; -import org.matsim.core.router.speedy.SpeedyALTFactory; -import org.matsim.core.router.util.LeastCostPathCalculatorFactory; public class ParkingManagerModule extends AbstractModule { diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java index 34af433a24e..bb25b2ac3b2 100644 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java +++ b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/qsimengine/router/TrainRouter.java @@ -27,7 +27,7 @@ import org.matsim.api.core.v01.network.Node; import org.matsim.api.core.v01.population.Person; import org.matsim.core.mobsim.qsim.QSim; -import org.matsim.core.router.FastDijkstraFactory; +import org.matsim.core.router.speedy.SpeedyALTFactory; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime; @@ -54,7 +54,7 @@ public TrainRouter(Network network, RailResourceManager resources) { this.resources = resources; // uses the full network, which should not be slower than filtered network as long as dijkstra is used - this.lpc = new FastDijkstraFactory(false).createPathCalculator(network, new DisUtility(), new FreeSpeedTravelTime()); + this.lpc = new SpeedyALTFactory().createPathCalculator(network, new DisUtility(), new FreeSpeedTravelTime()); } /** diff --git a/matsim/src/main/java/org/matsim/core/config/consistency/VspConfigConsistencyCheckerImpl.java b/matsim/src/main/java/org/matsim/core/config/consistency/VspConfigConsistencyCheckerImpl.java index 9fdd9cc0c70..336426e9a51 100644 --- a/matsim/src/main/java/org/matsim/core/config/consistency/VspConfigConsistencyCheckerImpl.java +++ b/matsim/src/main/java/org/matsim/core/config/consistency/VspConfigConsistencyCheckerImpl.java @@ -53,9 +53,9 @@ public final class VspConfigConsistencyCheckerImpl implements ConfigConsistencyC // VSP says that people < 18J should not use car, and implements that via car availability. How to handle that? - // + // private static final Logger log = LogManager.getLogger(VspConfigConsistencyCheckerImpl.class); - + public VspConfigConsistencyCheckerImpl() { // empty. only here to find out where it is called. } @@ -75,11 +75,11 @@ public void checkConsistency(Config config) { default -> throw new RuntimeException( "not implemented" ); } log.info("running checkConsistency ..."); - + boolean problem = false ; // ini - + // yy: sort the config groups alphabetically - + // === controler: problem = checkControlerConfigGroup( config, lvl, problem ); @@ -136,12 +136,12 @@ public void checkConsistency(Config config) { containsModeChoice = true ; } } - + // added jun'16 - if ( config.qsim().getVehiclesSource()==VehiclesSource.fromVehiclesData - && config.qsim().getUsePersonIdForMissingVehicleId() - && containsModeChoice - && config.qsim().getMainModes().size() > 1 ) + if ( config.qsim().getVehiclesSource()==VehiclesSource.fromVehiclesData + && config.qsim().getUsePersonIdForMissingVehicleId() + && containsModeChoice + && config.qsim().getMainModes().size() > 1 ) { problem = true ; log.log( lvl, "You can't use more than one main (=vehicular) mode while using the agent ID as missing vehicle ID ... " @@ -149,14 +149,14 @@ public void checkConsistency(Config config) { } // === zzz: - + if ( problem && config.vspExperimental().getVspDefaultsCheckingLevel() == VspDefaultsCheckingLevel.abort ) { - String str = "found a situation that leads to vsp-abort. aborting ..." ; + String str = "found a situation that leads to vsp-abort. aborting ..." ; System.out.flush() ; - log.fatal( str ) ; + log.fatal( str ) ; throw new RuntimeException( str ) ; } - + } private boolean checkSubtourModeChoiceConfigGroup( Config config, Level lvl, boolean problem ){ if ( config.subtourModeChoice().considerCarAvailability() ) { @@ -356,7 +356,7 @@ private static boolean checkPlanCalcScoreConfigGroup( Config config, Level lvl, System.out.flush() ; log.log( lvl, "found marginal utility of waiting != 0. vsp default is setting this to 0. " ) ; } - + // added apr'15: for ( ActivityParams params : config.planCalcScore().getActivityParams() ) { if ( PtConstants.TRANSIT_ACTIVITY_TYPE.equals( params.getActivityType() ) ) { @@ -490,8 +490,6 @@ private static boolean checkControlerConfigGroup( Config config, Level lvl, bool switch ( config.controler().getRoutingAlgorithmType() ) { case Dijkstra: case AStarLandmarks: - case FastDijkstra: - case FastAStarLandmarks: log.log( lvl, "you are not using SpeedyALT as routing algorithm. vsp default (since may'21) is to use SpeedeALT.") ; System.out.flush(); break; diff --git a/matsim/src/main/java/org/matsim/core/config/groups/ControlerConfigGroup.java b/matsim/src/main/java/org/matsim/core/config/groups/ControlerConfigGroup.java index 343dda723fd..ba4f3122c78 100644 --- a/matsim/src/main/java/org/matsim/core/config/groups/ControlerConfigGroup.java +++ b/matsim/src/main/java/org/matsim/core/config/groups/ControlerConfigGroup.java @@ -34,10 +34,10 @@ public final class ControlerConfigGroup extends ReflectiveConfigGroup { private static final Logger log = LogManager.getLogger( ControlerConfigGroup.class ); - public enum RoutingAlgorithmType {Dijkstra, AStarLandmarks, FastDijkstra, FastAStarLandmarks, SpeedyALT} - + public enum RoutingAlgorithmType {Dijkstra, AStarLandmarks, SpeedyALT} + public enum EventTypeToCreateScoringFunctions {IterationStarts, BeforeMobsim} - + public enum EventsFileFormat {xml, pb, json} public enum CompressionType { @@ -77,7 +77,7 @@ public enum CleanIterations { private static final String CLEAN_ITERS_AT_END = "cleanItersAtEnd"; private static final String COMPRESSION_TYPE = "compressionType"; private static final String EVENT_TYPE_TO_CREATE_SCORING_FUNCTIONS = "createScoringFunctionType"; - + /*package*/ static final String MOBSIM = "mobsim"; public enum MobsimType {qsim, JDEQSim, hermes} @@ -89,7 +89,7 @@ public enum MobsimType {qsim, JDEQSim, hermes} private int lastIteration = 1000; private RoutingAlgorithmType routingAlgorithmType = RoutingAlgorithmType.AStarLandmarks; private EventTypeToCreateScoringFunctions eventTypeToCreateScoringFunctions = EventTypeToCreateScoringFunctions.IterationStarts; - + private boolean linkToLinkRoutingEnabled = false; private String runId = null; @@ -127,7 +127,7 @@ public final Map getComments() { map.put(WRITE_PLANS_INTERVAL, "iterationNumber % writePlansInterval == 0 defines (hopefully) in which iterations plans are " + "written to a file. `0' disables plans writing completely. Some plans in early iterations are always written"); map.put(LINKTOLINK_ROUTING_ENABLED, "Default=false. If enabled, the router takes travel times needed for turning moves into account." - + " Cannot be used if the (Fast)AStarLandmarks routing or TravelTimeCalculator.separateModes is enabled."); + + " Can only be used with Dijkstra routing. Cannot be used when TravelTimeCalculator.separateModes is enabled."); map.put(FIRST_ITERATION, "Default=0. First Iteration of a simulation."); map.put(LAST_ITERATION, "Default=1000. Last Iteration of a simulation."); @@ -136,16 +136,11 @@ public final Map getComments() { " but add a significant overhead in smaller runs or in test cases where the graphical output is not even requested." ); map.put(COMPRESSION_TYPE, "Compression algorithm to use when writing out data to files. Possible values: " + Arrays.toString(CompressionType.values())); map.put(EVENT_TYPE_TO_CREATE_SCORING_FUNCTIONS, "Defines when the scoring functions for the population are created. Default=IterationStarts. Possible values: " + Arrays.toString(EventTypeToCreateScoringFunctions.values())); - - StringBuilder mobsimTypes = new StringBuilder(); - for ( MobsimType mtype : MobsimType.values() ) { - mobsimTypes.append(mtype.toString()); - mobsimTypes.append(' '); - } - map.put(MOBSIM, "Defines which mobility simulation will be used. Currently supported: " + mobsimTypes + IOUtils.NATIVE_NEWLINE + "\t\t" + + + map.put(MOBSIM, "Defines which mobility simulation will be used. Currently supported: " + Arrays.toString(MobsimType.values()) + IOUtils.NATIVE_NEWLINE + "\t\t" + "Depending on the chosen mobsim, you'll have to add additional config modules to configure the corresponding mobsim." + IOUtils.NATIVE_NEWLINE + "\t\t" + "For 'qsim', add a module 'qsim' to the config."); - + map.put(SNAPSHOT_FORMAT, "Comma-separated list of visualizer output file formats. `transims' and `otfvis'."); map.put(WRITE_SNAPSHOTS_INTERVAL, "iterationNumber % " + WRITE_SNAPSHOTS_INTERVAL + " == 0 defines in which iterations snapshots are written " + "to a file. `0' disables snapshots writing completely"); @@ -347,12 +342,12 @@ public int getWritePlansInterval() { public void setWritePlansInterval(final int writePlansInterval) { this.writePlansInterval = writePlansInterval; } - + @StringGetter( WRITE_SNAPSHOTS_INTERVAL ) public int getWriteSnapshotsInterval() { return writeSnapshotsInterval; } - + @StringSetter( WRITE_SNAPSHOTS_INTERVAL ) public void setWriteSnapshotsInterval(int writeSnapshotsInterval) { this.writeSnapshotsInterval = writeSnapshotsInterval; @@ -433,7 +428,7 @@ public int getWriteEventsUntilIteration() { public void setWriteEventsUntilIteration(int val) { this.writeEventsUntilIteration = val ; } - @Override + @Override protected void checkConsistency(Config config) { if ( config.controler().getOverwriteFileSetting() == OverwriteFileSetting.overwriteExistingFiles ) { log.warn( "setting overwriting behavior to "+overwriteFileSetting ); diff --git a/matsim/src/main/java/org/matsim/core/router/FastAStarEuclidean.java b/matsim/src/main/java/org/matsim/core/router/FastAStarEuclidean.java deleted file mode 100644 index 1020cca92f5..00000000000 --- a/matsim/src/main/java/org/matsim/core/router/FastAStarEuclidean.java +++ /dev/null @@ -1,140 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastAStarEuclidean.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import org.matsim.api.core.v01.network.Node; -import org.matsim.api.core.v01.population.Person; -import org.matsim.core.router.priorityqueue.BinaryMinHeap; -import org.matsim.core.router.util.AStarNodeData; -import org.matsim.core.router.util.AStarNodeDataFactory; -import org.matsim.core.router.util.ArrayRoutingNetwork; -import org.matsim.core.router.util.ArrayRoutingNetworkNode; -import org.matsim.core.router.util.PreProcessDijkstra; -import org.matsim.core.router.util.PreProcessEuclidean; -import org.matsim.core.router.util.RoutingNetwork; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.router.util.TravelDisutility; -import org.matsim.core.router.util.TravelTime; -import org.matsim.core.utils.collections.RouterPriorityQueue; -import org.matsim.vehicles.Vehicle; - -/** - *

- * Performance optimized version of the AStarEuclidean {@link org.matsim.core.router.AStarEuclidean} - * least cost path router which uses its own network to route within. - *

- * - * @see org.matsim.core.router.AStarEuclidean - * @see org.matsim.core.router.util.RoutingNetwork - * @author cdobler - */ -public class FastAStarEuclidean extends AStarEuclidean { - - private final RoutingNetwork routingNetwork; - private final FastRouterDelegate fastRouter; - private BinaryMinHeap heap = null; - private int maxSize = -1; - - FastAStarEuclidean(final RoutingNetwork routingNetwork, final PreProcessEuclidean preProcessData, - final TravelDisutility costFunction, final TravelTime timeFunction, final double overdoFactor, - final FastRouterDelegateFactory fastRouterFactory) { - super(routingNetwork, preProcessData, costFunction, timeFunction, overdoFactor); - - this.routingNetwork = routingNetwork; - this.fastRouter = fastRouterFactory.createFastRouterDelegate(this, new AStarNodeDataFactory(), routingNetwork); - - this.nodeData.clear(); - } - - /* - * Replace the references to the from and to nodes with their corresponding - * nodes in the routing network. - */ - @Override - public Path calcLeastCostPath(final Node fromNode, final Node toNode, final double startTime, final Person person, final Vehicle vehicle) { - - this.fastRouter.initialize(); - this.routingNetwork.initialize(); - - RoutingNetworkNode routingNetworkFromNode = this.routingNetwork.getNodes().get(fromNode.getId()); - RoutingNetworkNode routingNetworkToNode = this.routingNetwork.getNodes().get(toNode.getId()); - - return super.calcLeastCostPath(routingNetworkFromNode, routingNetworkToNode, startTime, person, vehicle); - } - - @Override - /*package*/ RouterPriorityQueue createRouterPriorityQueue() { - /* - * Re-use existing BinaryMinHeap instead of creating a new one. For large networks (> 10^6 nodes and links) this reduced - * the computation time by 40%! cdobler, oct'15 - */ - if (this.routingNetwork instanceof ArrayRoutingNetwork) { - int size = this.routingNetwork.getNodes().size(); - if (this.heap == null || this.maxSize != size) { - this.maxSize = size; - this.heap = new BinaryMinHeap<>(maxSize); - return this.heap; - } else { - this.heap.reset(); - return this.heap; - } -// int maxSize = this.routingNetwork.getNodes().size(); -// return new BinaryMinHeap(maxSize); - } else { - return super.createRouterPriorityQueue(); - } - } - - /* - * Constructs the path and replaces the nodes and links from the routing network - * with their corresponding nodes and links from the network. - */ - @Override - protected Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { - return this.fastRouter.constructPath(fromNode, toNode, startTime, arrivalTime); - } - - /* - * For performance reasons the outgoing links of a node are stored in - * the routing network in an array instead of a map. Therefore we have - * to iterate over an array instead of over a map. - */ - @Override - protected void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { - this.fastRouter.relaxNode(outNode, toNode, pendingNodes); - } - - /* - * The DijkstraNodeData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected AStarNodeData getData(final Node n) { - return (AStarNodeData) this.fastRouter.getData(n); - } - - /* - * The DeadEndData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected PreProcessDijkstra.DeadEndData getPreProcessData(final Node n) { - return this.fastRouter.getPreProcessData(n); - } -} diff --git a/matsim/src/main/java/org/matsim/core/router/FastAStarEuclideanFactory.java b/matsim/src/main/java/org/matsim/core/router/FastAStarEuclideanFactory.java deleted file mode 100644 index dc301c9363e..00000000000 --- a/matsim/src/main/java/org/matsim/core/router/FastAStarEuclideanFactory.java +++ /dev/null @@ -1,93 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastAStarEuclideanFactory.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import java.util.HashMap; -import java.util.Map; - -import org.matsim.api.core.v01.network.Network; -import org.matsim.core.router.util.ArrayRoutingNetworkFactory; -import org.matsim.core.router.util.LeastCostPathCalculator; -import org.matsim.core.router.util.LeastCostPathCalculatorFactory; -import org.matsim.core.router.util.PreProcessEuclidean; -import org.matsim.core.router.util.RoutingNetwork; -import org.matsim.core.router.util.RoutingNetworkFactory; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.router.util.TravelDisutility; -import org.matsim.core.router.util.TravelTime; - -/** - * @author cdobler - */ -public class FastAStarEuclideanFactory implements LeastCostPathCalculatorFactory { - - private final RoutingNetworkFactory routingNetworkFactory; - private final Map routingNetworks = new HashMap<>(); - private final Map preProcessData = new HashMap<>(); - private final double overdoFactor; - - public FastAStarEuclideanFactory() { - this(1); - } - - public FastAStarEuclideanFactory(double overdoFactor) { - this(FastRouterType.ARRAY, overdoFactor); - } - - private FastAStarEuclideanFactory(final FastRouterType fastRouterType, double overdoFactor) { - this.overdoFactor = overdoFactor; - switch (fastRouterType) { - case ARRAY: - this.routingNetworkFactory = new ArrayRoutingNetworkFactory(); - break; - case POINTER: - throw new RuntimeException( - "PointerRoutingNetworks are no longer supported. Use ArrayRoutingNetworks instead. Aborting!"); - default: - throw new RuntimeException("Undefined FastRouterType: " + fastRouterType); - } - } - - @Override - public synchronized LeastCostPathCalculator createPathCalculator(final Network network, - final TravelDisutility travelCosts, final TravelTime travelTimes) { - RoutingNetwork routingNetwork = this.routingNetworks.get(network); - PreProcessEuclidean preProcessEuclidean = this.preProcessData.get(network); - - if (routingNetwork == null) { - routingNetwork = this.routingNetworkFactory.createRoutingNetwork(network); - - preProcessEuclidean = new PreProcessEuclidean(travelCosts); - preProcessEuclidean.run(network); - this.preProcessData.put(network, preProcessEuclidean); - - for (RoutingNetworkNode node : routingNetwork.getNodes().values()) { - node.setDeadEndData(preProcessEuclidean.getNodeData(node.getNode())); - } - - this.routingNetworks.put(network, routingNetwork); - } - FastRouterDelegateFactory fastRouterFactory = new ArrayFastRouterDelegateFactory(); - - return new FastAStarEuclidean(routingNetwork, preProcessEuclidean, travelCosts, travelTimes, overdoFactor, - fastRouterFactory); - } -} \ No newline at end of file diff --git a/matsim/src/main/java/org/matsim/core/router/FastAStarLandmarks.java b/matsim/src/main/java/org/matsim/core/router/FastAStarLandmarks.java deleted file mode 100644 index 50b8909d825..00000000000 --- a/matsim/src/main/java/org/matsim/core/router/FastAStarLandmarks.java +++ /dev/null @@ -1,146 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastAStarLandmarks.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import org.matsim.api.core.v01.network.Node; -import org.matsim.api.core.v01.population.Person; -import org.matsim.core.router.priorityqueue.BinaryMinHeap; -import org.matsim.core.router.util.AStarNodeData; -import org.matsim.core.router.util.AStarNodeDataFactory; -import org.matsim.core.router.util.ArrayRoutingNetwork; -import org.matsim.core.router.util.ArrayRoutingNetworkNode; -import org.matsim.core.router.util.PreProcessLandmarks; -import org.matsim.core.router.util.RoutingNetwork; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.router.util.TravelDisutility; -import org.matsim.core.router.util.TravelTime; -import org.matsim.core.utils.collections.RouterPriorityQueue; -import org.matsim.vehicles.Vehicle; - -/** - *

- * Performance optimized version of the Dijkstra {@link org.matsim.core.router.FastAStarLandmarks} - * least cost path router which uses its own network to route within. - *

- * - * @see org.matsim.core.router.FastAStarLandmarks - * @see org.matsim.core.router.util.RoutingNetwork - * @author cdobler - */ -public class FastAStarLandmarks extends AStarLandmarks { - - private final RoutingNetwork routingNetwork; - private final FastRouterDelegate fastRouter; - private BinaryMinHeap heap = null; - private int maxSize = -1; - - FastAStarLandmarks(final RoutingNetwork routingNetwork, final PreProcessLandmarks preProcessData, - final TravelDisutility costFunction, final TravelTime timeFunction, final double overdoFactor, - final FastRouterDelegateFactory fastRouterFactory) { - super(routingNetwork, preProcessData, costFunction, timeFunction, overdoFactor); - - this.routingNetwork = routingNetwork; - this.fastRouter = fastRouterFactory.createFastRouterDelegate(this, new AStarNodeDataFactory(), routingNetwork); - - this.nodeData.clear(); - } - - /* - * Replace the references to the from and to nodes with their corresponding - * nodes in the routing network. - */ - @Override - public Path calcLeastCostPath(final Node fromNode, final Node toNode, final double startTime, final Person person, final Vehicle vehicle) { - - this.fastRouter.initialize(); - this.routingNetwork.initialize(); - - RoutingNetworkNode routingNetworkFromNode = routingNetwork.getNodes().get(fromNode.getId()); - RoutingNetworkNode routingNetworkToNode = routingNetwork.getNodes().get(toNode.getId()); - - return super.calcLeastCostPath(routingNetworkFromNode, routingNetworkToNode, startTime, person, vehicle); - } - - @Override - /*package*/ RouterPriorityQueue createRouterPriorityQueue() { - /* - * Re-use existing BinaryMinHeap instead of creating a new one. For large networks (> 10^6 nodes and links) this reduced - * the computation time by 40%! cdobler, oct'15 - */ - if (this.routingNetwork instanceof ArrayRoutingNetwork) { - int size = this.routingNetwork.getNodes().size(); - if (this.heap == null || this.maxSize != size) { - this.maxSize = size; - this.heap = new BinaryMinHeap<>(maxSize); - return this.heap; - } else { - this.heap.reset(); - return this.heap; - } - } else { - return super.createRouterPriorityQueue(); - } - } - - /* - * Constructs the path and replaces the nodes and links from the routing network - * with their corresponding nodes and links from the network. - */ - @Override - protected Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { - return this.fastRouter.constructPath(fromNode, toNode, startTime, arrivalTime); - } - - /* - * For performance reasons the outgoing links of a node are stored in - * the routing network in an array instead of a map. Therefore we have - * to iterate over an array instead of over a map. - */ - @Override - protected void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { - this.controlCounter++; - if (this.controlCounter == controlInterval) { - int newLandmarkIndex = checkToAddLandmark(outNode, toNode); - if (newLandmarkIndex > 0) { - updatePendingNodes(newLandmarkIndex, toNode, pendingNodes); - } - this.controlCounter = 0; - } - - this.fastRouter.relaxNode(outNode, toNode, pendingNodes); - } - - /* - * The DijkstraNodeData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected AStarNodeData getData(final Node n) { - return (AStarNodeData) this.fastRouter.getData(n); - } - - /* - * The LandmarksData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected PreProcessLandmarks.LandmarksData getPreProcessData(final Node n) { - return (PreProcessLandmarks.LandmarksData) this.fastRouter.getPreProcessData(n); - } -} \ No newline at end of file diff --git a/matsim/src/main/java/org/matsim/core/router/FastAStarLandmarksFactory.java b/matsim/src/main/java/org/matsim/core/router/FastAStarLandmarksFactory.java deleted file mode 100644 index f3dadbad642..00000000000 --- a/matsim/src/main/java/org/matsim/core/router/FastAStarLandmarksFactory.java +++ /dev/null @@ -1,104 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastAStarLandmarksFactory.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -import org.matsim.api.core.v01.network.Network; -import org.matsim.core.config.groups.GlobalConfigGroup; -import org.matsim.core.router.util.ArrayRoutingNetworkFactory; -import org.matsim.core.router.util.LeastCostPathCalculator; -import org.matsim.core.router.util.LeastCostPathCalculatorFactory; -import org.matsim.core.router.util.PreProcessLandmarks; -import org.matsim.core.router.util.RoutingNetwork; -import org.matsim.core.router.util.RoutingNetworkFactory; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.router.util.TravelDisutility; -import org.matsim.core.router.util.TravelTime; - -/** - * @author cdobler - */ -@Singleton -public class FastAStarLandmarksFactory implements LeastCostPathCalculatorFactory { - - private final RoutingNetworkFactory routingNetworkFactory; - private final Map routingNetworks = new HashMap<>(); - private final Map preProcessData = new HashMap<>(); - - private final int nThreads; - - @Inject - public FastAStarLandmarksFactory(final GlobalConfigGroup globalConfigGroup) { - this(FastRouterType.ARRAY, globalConfigGroup.getNumberOfThreads()); - } - - public FastAStarLandmarksFactory(int nThreads) { - this(FastRouterType.ARRAY, nThreads); - } - - // hide this constructor, as only one router type is allowed anyway... - private FastAStarLandmarksFactory(final FastRouterType fastRouterType, int numberOfThreads) { - switch (fastRouterType) { - case ARRAY: - this.routingNetworkFactory = new ArrayRoutingNetworkFactory(); - break; - case POINTER: - throw new RuntimeException( - "PointerRoutingNetworks are no longer supported. Use ArrayRoutingNetworks instead. Aborting!"); - default: - throw new RuntimeException("Undefined FastRouterType: " + fastRouterType); - } - - this.nThreads = numberOfThreads; - } - - @Override - public synchronized LeastCostPathCalculator createPathCalculator(final Network network, - final TravelDisutility travelCosts, final TravelTime travelTimes) { - RoutingNetwork routingNetwork = this.routingNetworks.get(network); - PreProcessLandmarks preProcessLandmarks = this.preProcessData.get(network); - - if (routingNetwork == null) { - routingNetwork = this.routingNetworkFactory.createRoutingNetwork(network); - - preProcessLandmarks = new PreProcessLandmarks(travelCosts); - preProcessLandmarks.setNumberOfThreads(nThreads); - preProcessLandmarks.run(network); - this.preProcessData.put(network, preProcessLandmarks); - - for (RoutingNetworkNode node : routingNetwork.getNodes().values()) { - node.setDeadEndData(preProcessLandmarks.getNodeData(node.getNode())); - } - - this.routingNetworks.put(network, routingNetwork); - } - FastRouterDelegateFactory fastRouterFactory = new ArrayFastRouterDelegateFactory(); - - final double overdoFactor = 1.0; - return new FastAStarLandmarks(routingNetwork, preProcessLandmarks, travelCosts, travelTimes, overdoFactor, - fastRouterFactory); - } -} diff --git a/matsim/src/main/java/org/matsim/core/router/FastDijkstra.java b/matsim/src/main/java/org/matsim/core/router/FastDijkstra.java deleted file mode 100644 index a048971a04e..00000000000 --- a/matsim/src/main/java/org/matsim/core/router/FastDijkstra.java +++ /dev/null @@ -1,142 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastDijkstra.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import org.matsim.api.core.v01.network.Node; -import org.matsim.api.core.v01.population.Person; -import org.matsim.core.router.priorityqueue.BinaryMinHeap; -import org.matsim.core.router.util.ArrayRoutingNetwork; -import org.matsim.core.router.util.ArrayRoutingNetworkNode; -import org.matsim.core.router.util.DijkstraNodeData; -import org.matsim.core.router.util.DijkstraNodeDataFactory; -import org.matsim.core.router.util.PreProcessDijkstra; -import org.matsim.core.router.util.RoutingNetwork; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.router.util.TravelDisutility; -import org.matsim.core.router.util.TravelTime; -import org.matsim.core.utils.collections.RouterPriorityQueue; -import org.matsim.vehicles.Vehicle; - -/** - *

- * Performance optimized version of the Dijkstra {@link org.matsim.core.router.Dijkstra} - * least cost path router which uses its own network to route within. - *

- * - * @see org.matsim.core.router.Dijkstra - * @see org.matsim.core.router.util.RoutingNetwork - * @author cdobler - */ -public class FastDijkstra extends Dijkstra { - - private final RoutingNetwork routingNetwork; - private final FastRouterDelegate fastRouter; - private BinaryMinHeap heap = null; - private int maxSize = -1; - - /* - * Create the routing network here and clear the nodeData map - * which is not used by this implementation. - */ - FastDijkstra(final RoutingNetwork routingNetwork, final TravelDisutility costFunction, final TravelTime timeFunction, - final PreProcessDijkstra preProcessData, final FastRouterDelegateFactory fastRouterFactory) { - super(routingNetwork, costFunction, timeFunction, preProcessData); - - this.routingNetwork = routingNetwork; - this.fastRouter = fastRouterFactory.createFastRouterDelegate(this, new DijkstraNodeDataFactory(), routingNetwork); - - this.nodeData.clear(); - } - - /* - * Replace the references to the from and to nodes with their corresponding - * nodes in the routing network. - */ - @Override - public Path calcLeastCostPath(final Node fromNode, final Node toNode, final double startTime, final Person person, final Vehicle vehicle) { - - this.fastRouter.initialize(); - this.routingNetwork.initialize(); - - RoutingNetworkNode routingNetworkFromNode = this.routingNetwork.getNodes().get(fromNode.getId()); - RoutingNetworkNode routingNetworkToNode = this.routingNetwork.getNodes().get(toNode.getId()); - - return super.calcLeastCostPath(routingNetworkFromNode, routingNetworkToNode, startTime, person, vehicle); - } - - @Override - /*package*/ RouterPriorityQueue createRouterPriorityQueue() { - /* - * Re-use existing BinaryMinHeap instead of creating a new one. For large networks (> 10^6 nodes and links) this reduced - * the computation time by 40%! cdobler, oct'15 - */ - if (this.routingNetwork instanceof ArrayRoutingNetwork) { - int size = this.routingNetwork.getNodes().size(); - if (this.heap == null || this.maxSize != size) { - this.maxSize = size; - this.heap = new BinaryMinHeap<>(maxSize); - return this.heap; - } else { - this.heap.reset(); - return this.heap; - } -// int maxSize = this.routingNetwork.getNodes().size(); -// return new BinaryMinHeap(maxSize); - } else { - return super.createRouterPriorityQueue(); - } - } - - /* - * Constructs the path and replaces the nodes and links from the routing network - * with their corresponding nodes and links from the network. - */ - @Override - protected Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { - return this.fastRouter.constructPath(fromNode, toNode, startTime, arrivalTime); - } - - /* - * For performance reasons the outgoing links of a node are stored in - * the routing network in an array instead of a map. Therefore we have - * to iterate over an array instead of over a map. - */ - @Override - protected void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { - this.fastRouter.relaxNode(outNode, toNode, pendingNodes); - } - - /* - * The DijkstraNodeData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected DijkstraNodeData getData(final Node n) { - return (DijkstraNodeData) this.fastRouter.getData(n); - } - - /* - * The DeadEndData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected PreProcessDijkstra.DeadEndData getPreProcessData(final Node n) { - return this.fastRouter.getPreProcessData(n); - } -} diff --git a/matsim/src/main/java/org/matsim/core/router/FastDijkstraFactory.java b/matsim/src/main/java/org/matsim/core/router/FastDijkstraFactory.java deleted file mode 100644 index 46f37f798a1..00000000000 --- a/matsim/src/main/java/org/matsim/core/router/FastDijkstraFactory.java +++ /dev/null @@ -1,97 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastDijkstraFactory.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.inject.Inject; -import jakarta.inject.Singleton; - -import org.matsim.api.core.v01.network.Network; -import org.matsim.core.router.util.ArrayRoutingNetworkFactory; -import org.matsim.core.router.util.LeastCostPathCalculator; -import org.matsim.core.router.util.LeastCostPathCalculatorFactory; -import org.matsim.core.router.util.PreProcessDijkstra; -import org.matsim.core.router.util.RoutingNetwork; -import org.matsim.core.router.util.RoutingNetworkFactory; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.router.util.TravelDisutility; -import org.matsim.core.router.util.TravelTime; - -@Singleton -public class FastDijkstraFactory implements LeastCostPathCalculatorFactory { - - private final boolean usePreProcessData; - private final RoutingNetworkFactory routingNetworkFactory; - private final Map routingNetworks = new HashMap<>(); - private final Map preProcessData = new HashMap<>(); - - @Inject - public FastDijkstraFactory() { - this(false, FastRouterType.ARRAY); - } - - public FastDijkstraFactory(final boolean usePreProcessData) { - this(usePreProcessData, FastRouterType.ARRAY); - } - - private FastDijkstraFactory(final boolean usePreProcessData, final FastRouterType fastRouterType) { - this.usePreProcessData = usePreProcessData; - - switch (fastRouterType) { - case ARRAY: - this.routingNetworkFactory = new ArrayRoutingNetworkFactory(); - break; - case POINTER: - throw new RuntimeException("PointerRoutingNetworks are no longer supported. " - + "Use ArrayRoutingNetworks instead. Aborting!"); - default: - throw new RuntimeException("Undefined FastRouterType: " + fastRouterType); - } - } - - @Override - public synchronized LeastCostPathCalculator createPathCalculator(final Network network, - final TravelDisutility travelCosts, final TravelTime travelTimes) { - RoutingNetwork routingNetwork = this.routingNetworks.get(network); - PreProcessDijkstra preProcessDijkstra = this.preProcessData.get(network); - - if (routingNetwork == null) { - routingNetwork = this.routingNetworkFactory.createRoutingNetwork(network); - - if (this.usePreProcessData) { - preProcessDijkstra = new PreProcessDijkstra(); - preProcessDijkstra.run(network); - this.preProcessData.put(network, preProcessDijkstra); - - for (RoutingNetworkNode node : routingNetwork.getNodes().values()) { - node.setDeadEndData(preProcessDijkstra.getNodeData(node.getNode())); - } - } - - this.routingNetworks.put(network, routingNetwork); - } - FastRouterDelegateFactory fastRouterFactory = new ArrayFastRouterDelegateFactory(); - - return new FastDijkstra(routingNetwork, travelCosts, travelTimes, preProcessDijkstra, fastRouterFactory); - } -} diff --git a/matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java b/matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java index e2315d02101..c2a1f2716ec 100644 --- a/matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java +++ b/matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java @@ -39,11 +39,10 @@ import org.matsim.vehicles.Vehicle; /** - *

Performance optimized version of the MultiNodeDijkstra least cost path router + *

Performance optimized version of the MultiNodeDijkstra least cost path router * which uses its own network to route within.

- * + * * @see org.matsim.core.router.MultiNodeDijkstra - * @see org.matsim.core.router.FastDijkstra * @see org.matsim.core.router.util.RoutingNetwork * @author cdobler */ @@ -53,35 +52,35 @@ public class FastMultiNodeDijkstra extends MultiNodeDijkstra { private final FastRouterDelegate fastRouter; private BinaryMinHeap heap = null; private int maxSize = -1; - + /* - * Create the routing network here and clear the nodeData map + * Create the routing network here and clear the nodeData map * which is not used by this implementation. */ - protected FastMultiNodeDijkstra(final RoutingNetwork routingNetwork, final TravelDisutility costFunction, - final TravelTime timeFunction, final PreProcessDijkstra preProcessData, + protected FastMultiNodeDijkstra(final RoutingNetwork routingNetwork, final TravelDisutility costFunction, + final TravelTime timeFunction, final PreProcessDijkstra preProcessData, final FastRouterDelegateFactory fastRouterFactory, boolean searchAllEndNodes) { super(routingNetwork, costFunction, timeFunction, preProcessData, searchAllEndNodes); - + this.routingNetwork = routingNetwork; this.fastRouter = fastRouterFactory.createFastRouterDelegate(this, new DijkstraNodeDataFactory(), routingNetwork); this.nodeData.clear(); } - + /* * Replace the references to the from and to nodes with their corresponding * nodes in the routing network. */ @Override public Path calcLeastCostPath(final Node fromNode, final Node toNode, final double startTime, final Person person, final Vehicle vehicle) { - + this.fastRouter.initialize(); this.routingNetwork.initialize(); Node routingNetworkFromNode; Node routingNetworkToNode; - + if (fromNode instanceof ImaginaryNode) { Collection initialNodes = ((ImaginaryNode) fromNode).initialNodes; for (InitialNode initialNode : initialNodes) initialNode.node = routingNetwork.getNodes().get(initialNode.node.getId()); @@ -93,10 +92,10 @@ public Path calcLeastCostPath(final Node fromNode, final Node toNode, final doub for (InitialNode initialNode : initialNodes) initialNode.node = routingNetwork.getNodes().get(initialNode.node.getId()); routingNetworkToNode = toNode; } else routingNetworkToNode = routingNetwork.getNodes().get(toNode.getId()); - + return super.calcLeastCostPath(routingNetworkFromNode, routingNetworkToNode, startTime, person, vehicle); } - + @Override /*package*/ RouterPriorityQueue createRouterPriorityQueue() { /* @@ -119,7 +118,7 @@ public Path calcLeastCostPath(final Node fromNode, final Node toNode, final doub return super.createRouterPriorityQueue(); } } - + /* * Constructs the path and replaces the nodes and links from the routing network * with their corresponding nodes and links from the network. @@ -135,12 +134,12 @@ protected Path constructPath(Node fromNode, Node toNode, double startTime, doubl */ ImaginaryNode imaginaryNode = null; if (fromNode instanceof ImaginaryNode) imaginaryNode = (ImaginaryNode) fromNode; - + if (!(fromNode instanceof RoutingNetworkNode)) fromNode = this.routingNetwork.getNodes().get(fromNode.getId()); if (!(toNode instanceof RoutingNetworkNode)) toNode = this.routingNetwork.getNodes().get(toNode.getId()); - + Path path = this.fastRouter.constructPath(fromNode, toNode, startTime, arrivalTime); - + /* * Here, we correct the path's travel time and cost if necessary. * To do so, we look for the InitialNode that matches the path's first node. @@ -151,7 +150,7 @@ protected Path constructPath(Node fromNode, Node toNode, double startTime, doubl Node pathFromNode = path.getFromNode(); double initialCost = 0.0; double initialTime = 0.0; - + Iterator iter = imaginaryNode.initialNodes.iterator(); while (iter.hasNext()) { InitialNode initialNode = iter.next(); @@ -164,10 +163,10 @@ protected Path constructPath(Node fromNode, Node toNode, double startTime, doubl return new Path(path.nodes, path.links, path.travelTime - initialTime, path.travelCost - initialCost); } - + return path; } - + /* * Constructs the path and replaces the nodes and links from the routing network * with their corresponding nodes and links from the network. @@ -179,17 +178,17 @@ public Path constructPath(Node fromNode, Node toNode, double startTime) { if (!(toNode instanceof RoutingNetworkNode)) toNode = this.routingNetwork.getNodes().get(toNode.getId()); return super.constructPath(fromNode, toNode, startTime); } - + /* * For performance reasons the outgoing links of a node are stored in * the routing network in an array instead of a map. Therefore we have - * to iterate over an array instead of over a map. + * to iterate over an array instead of over a map. */ @Override protected void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { fastRouter.relaxNode(outNode, toNode, pendingNodes); } - + /* * The DijkstraNodeData is taken from the RoutingNetworkNode and not from a map. */ diff --git a/matsim/src/main/java/org/matsim/core/router/LeastCostPathCalculatorModule.java b/matsim/src/main/java/org/matsim/core/router/LeastCostPathCalculatorModule.java index b8ff6a40616..e11652b3155 100644 --- a/matsim/src/main/java/org/matsim/core/router/LeastCostPathCalculatorModule.java +++ b/matsim/src/main/java/org/matsim/core/router/LeastCostPathCalculatorModule.java @@ -35,16 +35,12 @@ public void install() { // yy The code below will install _one_ LeastCostPathCalculator, which will be Dijkstra or Landmarks or something. It will be the // same Landmarks instance for all modes ... although one could do better by doing the preprocessing separately for the different modes. // kai/mm, jan'17 - + Config config = getConfig(); if (config.controler().getRoutingAlgorithmType().equals(ControlerConfigGroup.RoutingAlgorithmType.Dijkstra)) { bind(LeastCostPathCalculatorFactory.class).to(DijkstraFactory.class); } else if (config.controler().getRoutingAlgorithmType().equals(ControlerConfigGroup.RoutingAlgorithmType.AStarLandmarks)) { bind(LeastCostPathCalculatorFactory.class).to(AStarLandmarksFactory.class); - } else if (config.controler().getRoutingAlgorithmType().equals(ControlerConfigGroup.RoutingAlgorithmType.FastDijkstra)) { - bind(LeastCostPathCalculatorFactory.class).to(FastDijkstraFactory.class); - } else if (config.controler().getRoutingAlgorithmType().equals(ControlerConfigGroup.RoutingAlgorithmType.FastAStarLandmarks)) { - bind(LeastCostPathCalculatorFactory.class).to(FastAStarLandmarksFactory.class); } else if (config.controler().getRoutingAlgorithmType().equals(ControlerConfigGroup.RoutingAlgorithmType.SpeedyALT)) { bind(LeastCostPathCalculatorFactory.class).to(SpeedyALTFactory.class); } diff --git a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALT.java b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALT.java index 7fc5a740218..fb2c56a7f35 100644 --- a/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALT.java +++ b/matsim/src/main/java/org/matsim/core/router/speedy/SpeedyALT.java @@ -23,7 +23,7 @@ * * This implementation always looks at all landmarks and does not filter them, as performance measurements * suggest that selecting and re-selecting landmarks regularly actually results in an overhead compared - * to to just calculate the values for each landmark in each step (using a typical value of 16 landmarks). + * to just calculate the values for each landmark in each step (using a typical value of 16 landmarks). * This might be due to the fact that all values for each landmark are just next to each other in the memory, * so when accessing the travelcosts to/from one landmark basically already loads the values of all landmarks in * the CPU cache, making the calculation for the remaining landmarks very fast. diff --git a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetwork.java b/matsim/src/main/java/org/matsim/core/router/util/RoutingNetwork.java index 833d407c7f7..d55be2fa8a2 100644 --- a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetwork.java +++ b/matsim/src/main/java/org/matsim/core/router/util/RoutingNetwork.java @@ -30,16 +30,13 @@ * A network that is used by FastDijkstra, FastAStarEuclidean and FastAStarLandmarks. * Instead of storing the node data in a map, the data is attached directly to the nodes * which is faster but also consumes more memory. - * - * @see org.matsim.core.router.FastDijkstra - * @see org.matsim.core.router.FastAStarEuclidean - * @see org.matsim.core.router.FastAStarLandmarks + * * @author cdobler */ public interface RoutingNetwork extends Network { - + public void initialize(); - + @Override public Map, RoutingNetworkNode> getNodes(); -} \ No newline at end of file +} diff --git a/matsim/src/main/java/org/matsim/core/trafficmonitoring/TravelTimeDataArray.java b/matsim/src/main/java/org/matsim/core/trafficmonitoring/TravelTimeDataArray.java index d469ff9dac9..1e2a267aad0 100644 --- a/matsim/src/main/java/org/matsim/core/trafficmonitoring/TravelTimeDataArray.java +++ b/matsim/src/main/java/org/matsim/core/trafficmonitoring/TravelTimeDataArray.java @@ -26,7 +26,7 @@ /** * Implementation of {@link TravelTimeData} that stores the data per time bin - * in simple arrays. Useful if not too many empty time bins (time bins with + * in simple arrays. Useful if not too many empty time bins (time bins with * no traffic on a link) exist, so no memory is wasted. * * @author mrieser @@ -111,7 +111,7 @@ public double getTravelTime(final int timeSlot, final double now) { double ttime = traveltime(val); if (ttime >= 0.0) return ttime; // negative values are invalid. - // ttime can only be <0 if it never accumulated anything, i.e. if cnt == 9, so just use freespeed + // ttime can only be <0 if it never accumulated anything, i.e. if cnt == 0, so just use freespeed double freespeed = this.link.getLength() / this.link.getFreespeed(now); this.data[timeSlot] = encode(0, freespeed); return freespeed; diff --git a/matsim/src/test/java/org/matsim/core/router/PersonalizableDisutilityIntegrationTest.java b/matsim/src/test/java/org/matsim/core/router/PersonalizableDisutilityIntegrationTest.java index fdb79a4b356..a2822d64d4a 100644 --- a/matsim/src/test/java/org/matsim/core/router/PersonalizableDisutilityIntegrationTest.java +++ b/matsim/src/test/java/org/matsim/core/router/PersonalizableDisutilityIntegrationTest.java @@ -30,6 +30,7 @@ import org.matsim.core.config.ConfigUtils; import org.matsim.core.network.io.MatsimNetworkReader; import org.matsim.core.population.PopulationUtils; +import org.matsim.core.router.speedy.SpeedyALTFactory; import org.matsim.core.router.util.LeastCostPathCalculator; import org.matsim.core.router.util.LeastCostPathCalculatorFactory; import org.matsim.core.router.util.PreProcessEuclidean; @@ -44,33 +45,18 @@ * @author mrieser / senozon */ public class PersonalizableDisutilityIntegrationTest { - + @Test public void testPersonAvailableForDisutility_Dijkstra() { Fixture f = new Fixture(); - + Dijkstra router = new Dijkstra(f.network, f.costFunction, new FreeSpeedTravelTime()); router.calcLeastCostPath( - f.network.getNodes().get(Id.create("2", Node.class)), + f.network.getNodes().get(Id.create("2", Node.class)), f.network.getNodes().get(Id.create("1", Node.class)), 07*3600, f.person, f.vehicle); // hopefully there was no Exception until here... - - Assert.assertEquals(22, f.costFunction.cnt); // make sure the costFunction was actually used - } - - @Test - public void testPersonAvailableForDisutility_FastDijkstra() { - Fixture f = new Fixture(); - - LeastCostPathCalculatorFactory routerFactory = new FastDijkstraFactory(); - LeastCostPathCalculator router = routerFactory.createPathCalculator(f.network, f.costFunction, new FreeSpeedTravelTime()); - router.calcLeastCostPath( - f.network.getNodes().get(Id.create("2", Node.class)), - f.network.getNodes().get(Id.create("1", Node.class)), - 07*3600, f.person, f.vehicle); - // hopefully there was no Exception until here... - + Assert.assertEquals(22, f.costFunction.cnt); // make sure the costFunction was actually used } @@ -81,86 +67,57 @@ public void testPersonAvailableForDisutility_AStarEuclidean() { preprocess.run(f.network); AStarEuclidean router = new AStarEuclidean(f.network, preprocess, new FreeSpeedTravelTime()); router.calcLeastCostPath( - f.network.getNodes().get(Id.create("2", Node.class)), - f.network.getNodes().get(Id.create("1", Node.class)), - 07*3600, f.person, f.vehicle); - // hopefully there was no Exception until here... - - Assert.assertEquals(22, f.costFunction.cnt); // make sure the costFunction was actually used - } - - @Test - public void testPersonAvailableForDisutility_FastAStarEuclidean() { - Fixture f = new Fixture(); - PreProcessEuclidean preprocess = new PreProcessEuclidean(f.costFunction); - preprocess.run(f.network); - AStarEuclidean router = new AStarEuclidean(f.network, preprocess, new FreeSpeedTravelTime()); - router.calcLeastCostPath( - f.network.getNodes().get(Id.create("2", Node.class)), - f.network.getNodes().get(Id.create("1", Node.class)), + f.network.getNodes().get(Id.create("2", Node.class)), + f.network.getNodes().get(Id.create("1", Node.class)), 07*3600, f.person, f.vehicle); // hopefully there was no Exception until here... - - Assert.assertEquals(22, f.costFunction.cnt); // make sure the costFunction was actually used - } - @Test - public void testPersonAvailableForDisutility_AStarLandmarks() { - Fixture f = new Fixture(); - LeastCostPathCalculatorFactory routerFactory = new FastAStarEuclideanFactory(); - LeastCostPathCalculator router = routerFactory.createPathCalculator(f.network, f.costFunction, new FreeSpeedTravelTime()); - router.calcLeastCostPath( - f.network.getNodes().get(Id.create("2", Node.class)), - f.network.getNodes().get(Id.create("1", Node.class)), - 07*3600, f.person, f.vehicle); - // hopefully there was no Exception until here... - Assert.assertEquals(22, f.costFunction.cnt); // make sure the costFunction was actually used } - + @Test - public void testPersonAvailableForDisutility_FastAStarLandmarks() { + public void testPersonAvailableForDisutility_SpeedyALT() { Fixture f = new Fixture(); - LeastCostPathCalculatorFactory routerFactory = new FastAStarLandmarksFactory(2); + LeastCostPathCalculatorFactory routerFactory = new SpeedyALTFactory(); LeastCostPathCalculator router = routerFactory.createPathCalculator(f.network, f.costFunction, new FreeSpeedTravelTime()); router.calcLeastCostPath( - f.network.getNodes().get(Id.create("2", Node.class)), - f.network.getNodes().get(Id.create("1", Node.class)), + f.network.getNodes().get(Id.create("2", Node.class)), + f.network.getNodes().get(Id.create("1", Node.class)), 07*3600, f.person, f.vehicle); // hopefully there was no Exception until here... - + Assert.assertEquals(22, f.costFunction.cnt); // make sure the costFunction was actually used } - + private static class Fixture { /*package*/ final Scenario scenario; /*package*/ final Network network; /*package*/ final Vehicle vehicle; /*package*/ final Person person; /*package*/ final PersonEnforcingTravelDisutility costFunction; - + public Fixture() { this.scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig()); new MatsimNetworkReader(this.scenario.getNetwork()).readFile("test/scenarios/equil/network.xml"); - + this.person = PopulationUtils.getFactory().createPerson(Id.create(1, Person.class)); Id dummyId = Id.createVehicleId( "dummy" ) ; VehicleType dummyType = VehicleUtils.createVehicleType( Id.create( "dummyVehicleType", VehicleType.class ) ); this.vehicle = VehicleUtils.createVehicle(dummyId, dummyType ); - + this.costFunction = new PersonEnforcingTravelDisutility(); this.costFunction.setExpectations(this.person, this.vehicle); - + this.network = this.scenario.getNetwork(); } } - + private static class PersonEnforcingTravelDisutility implements TravelDisutility { private Person person = null; private Vehicle veh = null; - + /*package*/ int cnt = 0; /*package*/ void setExpectations(final Person person, final Vehicle veh) { diff --git a/matsim/src/test/java/org/matsim/core/router/RoutingIT.java b/matsim/src/test/java/org/matsim/core/router/RoutingIT.java index 1539991fadc..a2b33bac4bf 100644 --- a/matsim/src/test/java/org/matsim/core/router/RoutingIT.java +++ b/matsim/src/test/java/org/matsim/core/router/RoutingIT.java @@ -54,7 +54,7 @@ public class RoutingIT { /*package*/ static final Logger log = LogManager.getLogger(RoutingIT.class); - + @Rule public MatsimTestUtils utils = new MatsimTestUtils(); private interface RouterProvider { @@ -75,19 +75,6 @@ public LeastCostPathCalculatorFactory getFactory(final Network network, final Tr }); } @Test - public void testFastDijkstra() { - doTest(new RouterProvider() { - @Override - public String getName() { - return "FastDijkstra"; - } - @Override - public LeastCostPathCalculatorFactory getFactory(final Network network, final TravelDisutility costCalc, final TravelTime timeCalc) { - return new FastDijkstraFactory(); - } - }); - } - @Test public void testSpeedyDijkstra() { doTest(new RouterProvider() { @Override @@ -113,20 +100,8 @@ public LeastCostPathCalculatorFactory getFactory(final Network network, final Tr } }); } + @Test - public void testFastDijkstraPruneDeadEnds() { - doTest(new RouterProvider() { - @Override - public String getName() { - return "FastDijkstraPruneDeadends"; - } - @Override - public LeastCostPathCalculatorFactory getFactory(final Network network, final TravelDisutility costCalc, final TravelTime timeCalc) { - return new FastDijkstraFactory(); - } - }); - } - @Test public void testAStarEuclidean() { doTest(new RouterProvider() { @Override @@ -140,19 +115,6 @@ public LeastCostPathCalculatorFactory getFactory(final Network network, final Tr }); } @Test - public void testFastAStarEuclidean() { - doTest(new RouterProvider() { - @Override - public String getName() { - return "FastAStarEuclidean"; - } - @Override - public LeastCostPathCalculatorFactory getFactory(final Network network, final TravelDisutility costCalc, final TravelTime timeCalc) { - return new FastAStarEuclideanFactory(); - } - }); - } - @Test public void testAStarLandmarks() { doTest(new RouterProvider() { @Override @@ -165,19 +127,7 @@ public LeastCostPathCalculatorFactory getFactory(final Network network, final Tr } }); } - @Test - public void testFastAStarLandmarks() { - doTest(new RouterProvider() { - @Override - public String getName() { - return "FastAStarLandmarks"; - } - @Override - public LeastCostPathCalculatorFactory getFactory(final Network network, final TravelDisutility costCalc, final TravelTime timeCalc) { - return new FastAStarLandmarksFactory(2); - } - }); - } + @Test public void testSpeedyALT() { doTest(new RouterProvider() { @@ -200,13 +150,13 @@ private void doTest(final RouterProvider provider) { // final String inPlansName = "test/input/" + this.getClass().getCanonicalName().replace('.', '/') + "/plans.xml.gz"; final String inPlansName = utils.getClassInputDirectory() + "/plans.xml.gz" ; new PopulationReader(scenario).readFile(inPlansName); - + calcRoute(provider, scenario); final Scenario referenceScenario = ScenarioUtils.createScenario(config); new MatsimNetworkReader(referenceScenario.getNetwork()).readFile(config.network().getInputFile()); new PopulationReader(referenceScenario).readFile(inPlansName); - + final boolean isEqual = PopulationUtils.equalPopulation(referenceScenario.getPopulation(), scenario.getPopulation()); if ( !isEqual ) { new PopulationWriter(referenceScenario.getPopulation(), scenario.getNetwork()).write(this.utils.getOutputDirectory() + "/reference_population.xml.gz"); @@ -251,9 +201,9 @@ public TravelDisutility createTravelDisutility(TravelTime timeCalculator) { final TripRouter tripRouter = injector.getInstance(TripRouter.class); final PersonAlgorithm router = new PlanRouter(tripRouter, injector.getInstance(TimeInterpretation.class)); - + for ( Person p : scenario.getPopulation().getPersons().values() ) { router.run(p); } } -} \ No newline at end of file +} diff --git a/matsim/src/test/java/org/matsim/integration/replanning/ReRoutingIT.java b/matsim/src/test/java/org/matsim/integration/replanning/ReRoutingIT.java index d338fb590d2..585af080862 100644 --- a/matsim/src/test/java/org/matsim/integration/replanning/ReRoutingIT.java +++ b/matsim/src/test/java/org/matsim/integration/replanning/ReRoutingIT.java @@ -56,19 +56,19 @@ private Scenario loadScenario() { config.qsim().setRemoveStuckVehicles(true); config.controler().setEventsFileFormats(EnumSet.of(EventsFileFormat.xml)); config.controler().setLastIteration(1); - /* linear interpolate the into time bins aggregated travel time data to avoid artifacts at the boundaries of time bins: + /* linear interpolate the into time bins aggregated travel time data to avoid artifacts at the boundaries of time bins: * e.g. a first time bin with aggregated travel time of 90 seconds and a second time bin with 45 seconds; time bin size 60; - * i.e. consolidateData-method in TravelTimeCalculator will accept this difference; imagine an requested route starting 2 + * i.e. consolidateData-method in TravelTimeCalculator will accept this difference; imagine an requested route starting 2 * seconds before the end of the first time bin, another route starts 2 seconds after the start of the second time bin; then - * the second one will arrive 41 seconds earlier than the first. Depending on the algorithm, some routers will detect this, - * some not (see MATSim-730), which is why we decided to test the linear interpolated travel time data here (which does not + * the second one will arrive 41 seconds earlier than the first. Depending on the algorithm, some routers will detect this, + * some not (see MATSim-730), which is why we decided to test the linear interpolated travel time data here (which does not * contain this artifacts). theresa, sep'17 * */ config.travelTimeCalculator().setTravelTimeGetterType("linearinterpolation"); /* * The input plans file is not sorted. After switching from TreeMap to LinkedHashMap - * to store the persons in the population, we have to sort the population manually. + * to store the persons in the population, we have to sort the population manually. * cdobler, oct'11 */ Scenario scenario = ScenarioUtils.loadScenario(config); @@ -87,17 +87,6 @@ public void testReRoutingDijkstra() throws MalformedURLException { this.evaluate(); } - @Test - public void testReRoutingFastDijkstra() throws MalformedURLException { - Scenario scenario = this.loadScenario(); - scenario.getConfig().controler().setRoutingAlgorithmType(RoutingAlgorithmType.FastDijkstra); - Controler controler = new Controler(scenario); - controler.getConfig().controler().setCreateGraphs(false); - controler.getConfig().controler().setDumpDataAtEnd(false); - controler.run(); - this.evaluate(); - } - @Test public void testReRoutingAStarLandmarks() throws MalformedURLException { Scenario scenario = this.loadScenario(); @@ -109,17 +98,6 @@ public void testReRoutingAStarLandmarks() throws MalformedURLException { this.evaluate(); } - @Test - public void testReRoutingFastAStarLandmarks() throws MalformedURLException { - Scenario scenario = this.loadScenario(); - scenario.getConfig().controler().setRoutingAlgorithmType(RoutingAlgorithmType.FastAStarLandmarks); - Controler controler = new Controler(scenario); - controler.getConfig().controler().setCreateGraphs(false); - controler.getConfig().controler().setDumpDataAtEnd(false); - controler.run(); - this.evaluate(); - } - @Test public void testReRoutingSpeedyALT() throws MalformedURLException { Scenario scenario = this.loadScenario(); @@ -154,5 +132,5 @@ private void evaluate(String plansFilename) throws MalformedURLException { } Assert.assertTrue("different plans files.", isEqual); } - -} \ No newline at end of file + +} From 8c47edbd637c152e28e8935b14f1095446acdfed Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Mon, 9 Oct 2023 15:23:14 +0200 Subject: [PATCH 145/258] move an outdated class to contrib eventsBasedPTRouter it's the only place the class is still used --- .../contrib/eventsBasedPTRouter}/MultiNodeDijkstra.java | 8 +++++--- .../eventsBasedPTRouter/TransitRouterVariableImpl.java | 1 - .../eventsBasedPTRouter}/MultiNodeDijkstraTest.java | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) rename {matsim/src/main/java/org/matsim/pt/router => contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter}/MultiNodeDijkstra.java (95%) rename {matsim/src/test/java/org/matsim/pt/router => contribs/eventsBasedPTRouter/src/test/java/org/matsim/contrib/eventsBasedPTRouter}/MultiNodeDijkstraTest.java (99%) diff --git a/matsim/src/main/java/org/matsim/pt/router/MultiNodeDijkstra.java b/contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter/MultiNodeDijkstra.java similarity index 95% rename from matsim/src/main/java/org/matsim/pt/router/MultiNodeDijkstra.java rename to contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter/MultiNodeDijkstra.java index 40c1258214a..457d0e68bc3 100644 --- a/matsim/src/main/java/org/matsim/pt/router/MultiNodeDijkstra.java +++ b/contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter/MultiNodeDijkstra.java @@ -18,7 +18,7 @@ * * * *********************************************************************** */ -package org.matsim.pt.router; +package org.matsim.contrib.eventsBasedPTRouter; import java.util.HashMap; import java.util.Map; @@ -28,6 +28,8 @@ import org.matsim.core.router.InitialNode; import org.matsim.core.router.util.LeastCostPathCalculator.Path; import org.matsim.core.router.util.TravelTime; +import org.matsim.pt.router.TransitLeastCostPathTree; +import org.matsim.pt.router.TransitTravelDisutility; public class MultiNodeDijkstra /*extends Dijkstra*/ { @@ -35,7 +37,7 @@ public class MultiNodeDijkstra /*extends Dijkstra*/ { * The network on which we find routes. */ protected Network network; - + /** * The cost calculator. Provides the cost for each link and time step. */ @@ -45,7 +47,7 @@ public class MultiNodeDijkstra /*extends Dijkstra*/ { * The travel time calculator. Provides the travel time for each link and time step. */ private final TravelTime timeFunction; - + public MultiNodeDijkstra(final Network network, final TransitTravelDisutility costFunction, final TravelTime timeFunction) { this.network = network; this.costFunction = costFunction; diff --git a/contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter/TransitRouterVariableImpl.java b/contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter/TransitRouterVariableImpl.java index c3abfc1fb38..3c867f3240b 100644 --- a/contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter/TransitRouterVariableImpl.java +++ b/contribs/eventsBasedPTRouter/src/main/java/org/matsim/contrib/eventsBasedPTRouter/TransitRouterVariableImpl.java @@ -46,7 +46,6 @@ import org.matsim.core.router.util.PreProcessDijkstra; import org.matsim.core.utils.geometry.CoordUtils; import org.matsim.facilities.Facility; -import org.matsim.pt.router.MultiNodeDijkstra; import org.matsim.pt.router.TransitRouter; import org.matsim.pt.router.TransitRouterConfig; import org.matsim.pt.router.TransitRouterNetworkTravelTimeAndDisutility; diff --git a/matsim/src/test/java/org/matsim/pt/router/MultiNodeDijkstraTest.java b/contribs/eventsBasedPTRouter/src/test/java/org/matsim/contrib/eventsBasedPTRouter/MultiNodeDijkstraTest.java similarity index 99% rename from matsim/src/test/java/org/matsim/pt/router/MultiNodeDijkstraTest.java rename to contribs/eventsBasedPTRouter/src/test/java/org/matsim/contrib/eventsBasedPTRouter/MultiNodeDijkstraTest.java index 4136ce038ae..96574ed0d84 100644 --- a/matsim/src/test/java/org/matsim/pt/router/MultiNodeDijkstraTest.java +++ b/contribs/eventsBasedPTRouter/src/test/java/org/matsim/contrib/eventsBasedPTRouter/MultiNodeDijkstraTest.java @@ -18,7 +18,7 @@ * * * *********************************************************************** */ -package org.matsim.pt.router; +package org.matsim.contrib.eventsBasedPTRouter; import static org.junit.Assert.*; @@ -36,6 +36,8 @@ import org.matsim.core.router.InitialNode; import org.matsim.core.router.util.LeastCostPathCalculator.Path; import org.matsim.core.router.util.TravelTime; +import org.matsim.pt.router.CustomDataManager; +import org.matsim.pt.router.TransitTravelDisutility; import org.matsim.vehicles.Vehicle; /** From de50dcd8cdf05ead20f1d39dcf9cbdf0e9346e6e Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Mon, 9 Oct 2023 15:25:54 +0200 Subject: [PATCH 146/258] move remaining Fast*-router classes to locationchoice contrib the locationchoice is now the only place where some customized fast-routing algorithms are used. --- .../router/AbstractFastRouterDelegate.java | 224 +++++----- .../core/router/ArrayFastRouterDelegate.java | 122 +++--- .../ArrayFastRouterDelegateFactory.java | 0 .../core/router/FastMultiNodeDijkstra.java | 414 +++++++++--------- .../router/FastMultiNodeDijkstraFactory.java | 0 .../core/router/FastRouterDelegate.java | 130 +++--- .../router/FastRouterDelegateFactory.java | 2 +- .../org/matsim/core/router/ImaginaryNode.java | 20 +- .../matsim/core/router/MultiNodeDijkstra.java | 88 ++-- .../core/router/MultiNodeDijkstraFactory.java | 10 +- .../core/router/MultiNodePathCalculator.java | 3 +- .../router/RoutingNetworkImaginaryNode.java | 0 .../router/util/AbstractRoutingNetwork.java | 0 .../util/AbstractRoutingNetworkFactory.java | 4 +- .../util/AbstractRoutingNetworkLink.java | 0 .../util/AbstractRoutingNetworkNode.java | 0 .../core/router/util/ArrayRoutingNetwork.java | 0 .../util/ArrayRoutingNetworkFactory.java | 21 +- .../router/util/ArrayRoutingNetworkLink.java | 0 .../router/util/ArrayRoutingNetworkNode.java | 0 .../core/router/util/RoutingNetwork.java | 1 + .../router/util/RoutingNetworkFactory.java | 6 +- .../core/router/util/RoutingNetworkLink.java | 10 +- .../core/router/util/RoutingNetworkNode.java | 8 +- .../matsim/core/router/FastMultiNodeTest.java | 0 .../core/router/MultiNodeDijkstraTest.java | 0 .../matsim/core/router/FastRouterType.java | 32 -- .../core/router/priorityqueue/HasIndex.java | 10 +- 28 files changed, 535 insertions(+), 570 deletions(-) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/AbstractFastRouterDelegate.java (97%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/ArrayFastRouterDelegate.java (97%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/ArrayFastRouterDelegateFactory.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java (95%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/FastMultiNodeDijkstraFactory.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/FastRouterDelegate.java (97%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/FastRouterDelegateFactory.java (94%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/ImaginaryNode.java (97%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/MultiNodeDijkstra.java (97%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/MultiNodeDijkstraFactory.java (98%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/MultiNodePathCalculator.java (98%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/RoutingNetworkImaginaryNode.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/AbstractRoutingNetwork.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkFactory.java (99%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkLink.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkNode.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/ArrayRoutingNetwork.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkFactory.java (98%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkLink.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkNode.java (100%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/RoutingNetwork.java (97%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/RoutingNetworkFactory.java (99%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/RoutingNetworkLink.java (98%) rename {matsim => contribs/locationchoice}/src/main/java/org/matsim/core/router/util/RoutingNetworkNode.java (99%) rename {matsim => contribs/locationchoice}/src/test/java/org/matsim/core/router/FastMultiNodeTest.java (100%) rename {matsim => contribs/locationchoice}/src/test/java/org/matsim/core/router/MultiNodeDijkstraTest.java (100%) delete mode 100644 matsim/src/main/java/org/matsim/core/router/FastRouterType.java diff --git a/matsim/src/main/java/org/matsim/core/router/AbstractFastRouterDelegate.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/AbstractFastRouterDelegate.java similarity index 97% rename from matsim/src/main/java/org/matsim/core/router/AbstractFastRouterDelegate.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/AbstractFastRouterDelegate.java index 9fb320b1409..b26cce699a6 100644 --- a/matsim/src/main/java/org/matsim/core/router/AbstractFastRouterDelegate.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/AbstractFastRouterDelegate.java @@ -1,112 +1,112 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastRouterDelegate.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Node; -import org.matsim.core.router.util.LeastCostPathCalculator.Path; -import org.matsim.core.router.util.NodeData; -import org.matsim.core.router.util.NodeDataFactory; -import org.matsim.core.router.util.PreProcessDijkstra; -import org.matsim.core.router.util.RoutingNetworkLink; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.utils.collections.RouterPriorityQueue; - -import java.util.ArrayList; - -/*package*/ abstract class AbstractFastRouterDelegate implements FastRouterDelegate { - - /*package*/ final Dijkstra dijkstra; - /*package*/ final NodeDataFactory nodeDataFactory; - - /*package*/ AbstractFastRouterDelegate(final Dijkstra dijkstra, final NodeDataFactory nodeDataFactory) { - this.dijkstra = dijkstra; - this.nodeDataFactory = nodeDataFactory; - } - - @Override - public void initialize() { - // Some classes might override this method and do some additional stuff... - } - - @Override - public Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { - ArrayList nodes = new ArrayList<>(); - ArrayList links = new ArrayList<>(); - - nodes.add(0, ((RoutingNetworkNode) toNode).getNode()); - Link tmpLink = getData(toNode).getPrevLink(); -// if (tmpLink != null) { - // original code -// while (tmpLink.getFromNode() != fromNode) { -// links.add(0, ((RoutingNetworkLink) tmpLink).getLink()); -// nodes.add(0, ((RoutingNetworkLink) tmpLink).getLink().getFromNode()); -// tmpLink = getData(tmpLink.getFromNode()).getPrevLink(); -// } -// links.add(0, ((RoutingNetworkLink) tmpLink).getLink()); -// nodes.add(0, ((RoutingNetworkNode) tmpLink.getFromNode()).getNode()); - - /* - * Adapted this code to be compatible with the MultiNodeDijkstra located in - * the location choice contrib. When a MultiNodeDijkstra uses multiple start nodes, - * there is not a single start node that could be used to check whether - * "tmpLink.getFromNode() != fromNode" is true. Instead, the start nodes do not have - * a previous link. - * For the regular Dikstra, this is also fine since the start node also does not have - * a previous node. - * cdobler, feb'14 - */ - while (tmpLink != null) { - links.add(0, ((RoutingNetworkLink) tmpLink).getLink()); - nodes.add(0, ((RoutingNetworkLink) tmpLink).getLink().getFromNode()); - tmpLink = getData(tmpLink.getFromNode()).getPrevLink(); - } -// } - - NodeData toNodeData = getData(toNode); - return new Path(nodes, links, arrivalTime - startTime, toNodeData.getCost()); - } - - @Override - public void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { - - RoutingNetworkNode routingNetworkNode = (RoutingNetworkNode) outNode; - NodeData outData = getData(routingNetworkNode); - double currTime = outData.getTime(); - double currCost = outData.getCost(); - if (this.dijkstra.pruneDeadEnds) { - PreProcessDijkstra.DeadEndData ddOutData = getPreProcessData(routingNetworkNode); - - for (Link l : routingNetworkNode.getOutLinksArray()) { - this.dijkstra.relaxNodeLogic(l, pendingNodes, currTime, currCost, toNode, ddOutData); - } - } else { // this.pruneDeadEnds == false - for (Link l : routingNetworkNode.getOutLinksArray()) { - this.dijkstra.relaxNodeLogic(l, pendingNodes, currTime, currCost, toNode, null); - } - } - } - - @Override - public PreProcessDijkstra.DeadEndData getPreProcessData(final Node n) { - return ((RoutingNetworkNode) n).getDeadEndData(); - } -} +/* *********************************************************************** * + * project: org.matsim.* + * FastRouterDelegate.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2011 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.core.router; + +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.router.util.LeastCostPathCalculator.Path; +import org.matsim.core.router.util.NodeData; +import org.matsim.core.router.util.NodeDataFactory; +import org.matsim.core.router.util.PreProcessDijkstra; +import org.matsim.core.router.util.RoutingNetworkLink; +import org.matsim.core.router.util.RoutingNetworkNode; +import org.matsim.core.utils.collections.RouterPriorityQueue; + +import java.util.ArrayList; + +/*package*/ abstract class AbstractFastRouterDelegate implements FastRouterDelegate { + + /*package*/ final Dijkstra dijkstra; + /*package*/ final NodeDataFactory nodeDataFactory; + + /*package*/ AbstractFastRouterDelegate(final Dijkstra dijkstra, final NodeDataFactory nodeDataFactory) { + this.dijkstra = dijkstra; + this.nodeDataFactory = nodeDataFactory; + } + + @Override + public void initialize() { + // Some classes might override this method and do some additional stuff... + } + + @Override + public Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { + ArrayList nodes = new ArrayList<>(); + ArrayList links = new ArrayList<>(); + + nodes.add(0, ((RoutingNetworkNode) toNode).getNode()); + Link tmpLink = getData(toNode).getPrevLink(); +// if (tmpLink != null) { + // original code +// while (tmpLink.getFromNode() != fromNode) { +// links.add(0, ((RoutingNetworkLink) tmpLink).getLink()); +// nodes.add(0, ((RoutingNetworkLink) tmpLink).getLink().getFromNode()); +// tmpLink = getData(tmpLink.getFromNode()).getPrevLink(); +// } +// links.add(0, ((RoutingNetworkLink) tmpLink).getLink()); +// nodes.add(0, ((RoutingNetworkNode) tmpLink.getFromNode()).getNode()); + + /* + * Adapted this code to be compatible with the MultiNodeDijkstra located in + * the location choice contrib. When a MultiNodeDijkstra uses multiple start nodes, + * there is not a single start node that could be used to check whether + * "tmpLink.getFromNode() != fromNode" is true. Instead, the start nodes do not have + * a previous link. + * For the regular Dikstra, this is also fine since the start node also does not have + * a previous node. + * cdobler, feb'14 + */ + while (tmpLink != null) { + links.add(0, ((RoutingNetworkLink) tmpLink).getLink()); + nodes.add(0, ((RoutingNetworkLink) tmpLink).getLink().getFromNode()); + tmpLink = getData(tmpLink.getFromNode()).getPrevLink(); + } +// } + + NodeData toNodeData = getData(toNode); + return new Path(nodes, links, arrivalTime - startTime, toNodeData.getCost()); + } + + @Override + public void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { + + RoutingNetworkNode routingNetworkNode = (RoutingNetworkNode) outNode; + NodeData outData = getData(routingNetworkNode); + double currTime = outData.getTime(); + double currCost = outData.getCost(); + if (this.dijkstra.pruneDeadEnds) { + PreProcessDijkstra.DeadEndData ddOutData = getPreProcessData(routingNetworkNode); + + for (Link l : routingNetworkNode.getOutLinksArray()) { + this.dijkstra.relaxNodeLogic(l, pendingNodes, currTime, currCost, toNode, ddOutData); + } + } else { // this.pruneDeadEnds == false + for (Link l : routingNetworkNode.getOutLinksArray()) { + this.dijkstra.relaxNodeLogic(l, pendingNodes, currTime, currCost, toNode, null); + } + } + } + + @Override + public PreProcessDijkstra.DeadEndData getPreProcessData(final Node n) { + return ((RoutingNetworkNode) n).getDeadEndData(); + } +} diff --git a/matsim/src/main/java/org/matsim/core/router/ArrayFastRouterDelegate.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/ArrayFastRouterDelegate.java similarity index 97% rename from matsim/src/main/java/org/matsim/core/router/ArrayFastRouterDelegate.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/ArrayFastRouterDelegate.java index 0a9fd54a41e..d220c3aa769 100644 --- a/matsim/src/main/java/org/matsim/core/router/ArrayFastRouterDelegate.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/ArrayFastRouterDelegate.java @@ -1,62 +1,62 @@ -/* *********************************************************************** * - * project: org.matsim.* - * ArrayFastRouterDelegate.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2012 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import org.matsim.api.core.v01.network.Node; -import org.matsim.core.router.util.ArrayRoutingNetwork; -import org.matsim.core.router.util.ArrayRoutingNetworkNode; -import org.matsim.core.router.util.NodeData; -import org.matsim.core.router.util.NodeDataFactory; - -/*package*/ class ArrayFastRouterDelegate extends AbstractFastRouterDelegate { - - private final ArrayRoutingNetwork network; - private final NodeData[] nodeData; - private boolean isInitialized = false; - - /*package*/ ArrayFastRouterDelegate(final Dijkstra dijkstra, final NodeDataFactory nodeDataFactory, - final ArrayRoutingNetwork network) { - super(dijkstra, nodeDataFactory); - this.network = network; - this.nodeData = new NodeData[network.getNodes().size()]; - } - - @Override - public final void initialize() { - // lazy initialization - if (!isInitialized) { - for (Node node : this.network.getNodes().values()) { - int index = ((ArrayRoutingNetworkNode) node).getArrayIndex(); - this.nodeData[index] = nodeDataFactory.createNodeData(); - } - - this.isInitialized = true; - } - } - - /* - * The NodeData is taken from the array. - */ - public NodeData getData(final Node n) { - ArrayRoutingNetworkNode routingNetworkNode = (ArrayRoutingNetworkNode) n; - return this.nodeData[routingNetworkNode.getArrayIndex()]; - } +/* *********************************************************************** * + * project: org.matsim.* + * ArrayFastRouterDelegate.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2012 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.core.router; + +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.router.util.ArrayRoutingNetwork; +import org.matsim.core.router.util.ArrayRoutingNetworkNode; +import org.matsim.core.router.util.NodeData; +import org.matsim.core.router.util.NodeDataFactory; + +/*package*/ class ArrayFastRouterDelegate extends AbstractFastRouterDelegate { + + private final ArrayRoutingNetwork network; + private final NodeData[] nodeData; + private boolean isInitialized = false; + + /*package*/ ArrayFastRouterDelegate(final Dijkstra dijkstra, final NodeDataFactory nodeDataFactory, + final ArrayRoutingNetwork network) { + super(dijkstra, nodeDataFactory); + this.network = network; + this.nodeData = new NodeData[network.getNodes().size()]; + } + + @Override + public final void initialize() { + // lazy initialization + if (!isInitialized) { + for (Node node : this.network.getNodes().values()) { + int index = ((ArrayRoutingNetworkNode) node).getArrayIndex(); + this.nodeData[index] = nodeDataFactory.createNodeData(); + } + + this.isInitialized = true; + } + } + + /* + * The NodeData is taken from the array. + */ + public NodeData getData(final Node n) { + ArrayRoutingNetworkNode routingNetworkNode = (ArrayRoutingNetworkNode) n; + return this.nodeData[routingNetworkNode.getArrayIndex()]; + } } \ No newline at end of file diff --git a/matsim/src/main/java/org/matsim/core/router/ArrayFastRouterDelegateFactory.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/ArrayFastRouterDelegateFactory.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/ArrayFastRouterDelegateFactory.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/ArrayFastRouterDelegateFactory.java diff --git a/matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java similarity index 95% rename from matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java index c2a1f2716ec..d7b1b420751 100644 --- a/matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/FastMultiNodeDijkstra.java @@ -1,207 +1,207 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastDijkstra.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import java.util.Collection; -import java.util.Iterator; - -import org.matsim.api.core.v01.network.Node; -import org.matsim.api.core.v01.population.Person; -import org.matsim.core.router.priorityqueue.BinaryMinHeap; -import org.matsim.core.router.util.ArrayRoutingNetwork; -import org.matsim.core.router.util.ArrayRoutingNetworkNode; -import org.matsim.core.router.util.DijkstraNodeData; -import org.matsim.core.router.util.DijkstraNodeDataFactory; -import org.matsim.core.router.util.PreProcessDijkstra; -import org.matsim.core.router.util.RoutingNetwork; -import org.matsim.core.router.util.RoutingNetworkNode; -import org.matsim.core.router.util.TravelDisutility; -import org.matsim.core.router.util.TravelTime; -import org.matsim.core.utils.collections.RouterPriorityQueue; -import org.matsim.vehicles.Vehicle; - -/** - *

Performance optimized version of the MultiNodeDijkstra least cost path router - * which uses its own network to route within.

- * - * @see org.matsim.core.router.MultiNodeDijkstra - * @see org.matsim.core.router.util.RoutingNetwork - * @author cdobler - */ -public class FastMultiNodeDijkstra extends MultiNodeDijkstra { - - /*package*/ final RoutingNetwork routingNetwork; - private final FastRouterDelegate fastRouter; - private BinaryMinHeap heap = null; - private int maxSize = -1; - - /* - * Create the routing network here and clear the nodeData map - * which is not used by this implementation. - */ - protected FastMultiNodeDijkstra(final RoutingNetwork routingNetwork, final TravelDisutility costFunction, - final TravelTime timeFunction, final PreProcessDijkstra preProcessData, - final FastRouterDelegateFactory fastRouterFactory, boolean searchAllEndNodes) { - super(routingNetwork, costFunction, timeFunction, preProcessData, searchAllEndNodes); - - this.routingNetwork = routingNetwork; - this.fastRouter = fastRouterFactory.createFastRouterDelegate(this, new DijkstraNodeDataFactory(), routingNetwork); - - this.nodeData.clear(); - } - - /* - * Replace the references to the from and to nodes with their corresponding - * nodes in the routing network. - */ - @Override - public Path calcLeastCostPath(final Node fromNode, final Node toNode, final double startTime, final Person person, final Vehicle vehicle) { - - this.fastRouter.initialize(); - this.routingNetwork.initialize(); - - Node routingNetworkFromNode; - Node routingNetworkToNode; - - if (fromNode instanceof ImaginaryNode) { - Collection initialNodes = ((ImaginaryNode) fromNode).initialNodes; - for (InitialNode initialNode : initialNodes) initialNode.node = routingNetwork.getNodes().get(initialNode.node.getId()); - routingNetworkFromNode = fromNode; - } else routingNetworkFromNode = routingNetwork.getNodes().get(fromNode.getId()); - - if (toNode instanceof ImaginaryNode) { - Collection initialNodes = ((ImaginaryNode) toNode).initialNodes; - for (InitialNode initialNode : initialNodes) initialNode.node = routingNetwork.getNodes().get(initialNode.node.getId()); - routingNetworkToNode = toNode; - } else routingNetworkToNode = routingNetwork.getNodes().get(toNode.getId()); - - return super.calcLeastCostPath(routingNetworkFromNode, routingNetworkToNode, startTime, person, vehicle); - } - - @Override - /*package*/ RouterPriorityQueue createRouterPriorityQueue() { - /* - * Re-use existing BinaryMinHeap instead of creating a new one. For large networks (> 10^6 nodes and links) this reduced - * the computation time by 40%! cdobler, oct'15 - */ - if (this.routingNetwork instanceof ArrayRoutingNetwork) { - int size = this.routingNetwork.getNodes().size(); - if (this.heap == null || this.maxSize != size) { - this.maxSize = size; - this.heap = new BinaryMinHeap<>(maxSize); - return this.heap; - } else { - this.heap.reset(); - return this.heap; - } -// int maxSize = this.routingNetwork.getNodes().size(); -// return new BinaryMinHeap(maxSize); - } else { - return super.createRouterPriorityQueue(); - } - } - - /* - * Constructs the path and replaces the nodes and links from the routing network - * with their corresponding nodes and links from the network. - */ - @Override - protected Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { - /* - * If the fromNode is an imaginaryNode some special treatment is necessary. - * The path returned by the fastRouter also contains the travel time and cost - * from the trips start coordinate to the start node of the path. This information - * is stored in the ImaginaryNode (respectively in its InitialNodes). Therefore, - * we have to store a reference to the imaginary node. - */ - ImaginaryNode imaginaryNode = null; - if (fromNode instanceof ImaginaryNode) imaginaryNode = (ImaginaryNode) fromNode; - - if (!(fromNode instanceof RoutingNetworkNode)) fromNode = this.routingNetwork.getNodes().get(fromNode.getId()); - if (!(toNode instanceof RoutingNetworkNode)) toNode = this.routingNetwork.getNodes().get(toNode.getId()); - - Path path = this.fastRouter.constructPath(fromNode, toNode, startTime, arrivalTime); - - /* - * Here, we correct the path's travel time and cost if necessary. - * To do so, we look for the InitialNode that matches the path's first node. - * The path's travel time and cost are then reduced by the values - * found in the InitialNode. - */ - if (imaginaryNode != null && path != null && path.nodes.size() > 0) { - Node pathFromNode = path.getFromNode(); - double initialCost = 0.0; - double initialTime = 0.0; - - Iterator iter = imaginaryNode.initialNodes.iterator(); - while (iter.hasNext()) { - InitialNode initialNode = iter.next(); - if (initialNode.node.getId().equals(pathFromNode.getId())) { - initialCost = initialNode.initialCost; - initialTime = initialNode.initialTime; - break; - } - } - - return new Path(path.nodes, path.links, path.travelTime - initialTime, path.travelCost - initialCost); - } - - return path; - } - - /* - * Constructs the path and replaces the nodes and links from the routing network - * with their corresponding nodes and links from the network. - */ - @Override - public Path constructPath(Node fromNode, Node toNode, double startTime) { - if (toNode == null || fromNode == null) return null; - if (!(fromNode instanceof RoutingNetworkNode)) fromNode = this.routingNetwork.getNodes().get(fromNode.getId()); - if (!(toNode instanceof RoutingNetworkNode)) toNode = this.routingNetwork.getNodes().get(toNode.getId()); - return super.constructPath(fromNode, toNode, startTime); - } - - /* - * For performance reasons the outgoing links of a node are stored in - * the routing network in an array instead of a map. Therefore we have - * to iterate over an array instead of over a map. - */ - @Override - protected void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { - fastRouter.relaxNode(outNode, toNode, pendingNodes); - } - - /* - * The DijkstraNodeData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected DijkstraNodeData getData(final Node n) { - return (DijkstraNodeData) fastRouter.getData(n); - } - - /* - * The DeadEndData is taken from the RoutingNetworkNode and not from a map. - */ - @Override - protected PreProcessDijkstra.DeadEndData getPreProcessData(final Node n) { - return fastRouter.getPreProcessData(n); - } -} +/* *********************************************************************** * + * project: org.matsim.* + * FastDijkstra.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2011 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.core.router; + +import java.util.Collection; +import java.util.Iterator; + +import org.matsim.api.core.v01.network.Node; +import org.matsim.api.core.v01.population.Person; +import org.matsim.core.router.priorityqueue.BinaryMinHeap; +import org.matsim.core.router.util.ArrayRoutingNetwork; +import org.matsim.core.router.util.ArrayRoutingNetworkNode; +import org.matsim.core.router.util.DijkstraNodeData; +import org.matsim.core.router.util.DijkstraNodeDataFactory; +import org.matsim.core.router.util.PreProcessDijkstra; +import org.matsim.core.router.util.RoutingNetwork; +import org.matsim.core.router.util.RoutingNetworkNode; +import org.matsim.core.router.util.TravelDisutility; +import org.matsim.core.router.util.TravelTime; +import org.matsim.core.utils.collections.RouterPriorityQueue; +import org.matsim.vehicles.Vehicle; + +/** + *

Performance optimized version of the MultiNodeDijkstra least cost path router + * which uses its own network to route within.

+ * + * @see MultiNodeDijkstra + * @see org.matsim.core.router.util.RoutingNetwork + * @author cdobler + */ +public class FastMultiNodeDijkstra extends MultiNodeDijkstra { + + /*package*/ final RoutingNetwork routingNetwork; + private final FastRouterDelegate fastRouter; + private BinaryMinHeap heap = null; + private int maxSize = -1; + + /* + * Create the routing network here and clear the nodeData map + * which is not used by this implementation. + */ + protected FastMultiNodeDijkstra(final RoutingNetwork routingNetwork, final TravelDisutility costFunction, + final TravelTime timeFunction, final PreProcessDijkstra preProcessData, + final FastRouterDelegateFactory fastRouterFactory, boolean searchAllEndNodes) { + super(routingNetwork, costFunction, timeFunction, preProcessData, searchAllEndNodes); + + this.routingNetwork = routingNetwork; + this.fastRouter = fastRouterFactory.createFastRouterDelegate(this, new DijkstraNodeDataFactory(), routingNetwork); + + this.nodeData.clear(); + } + + /* + * Replace the references to the from and to nodes with their corresponding + * nodes in the routing network. + */ + @Override + public Path calcLeastCostPath(final Node fromNode, final Node toNode, final double startTime, final Person person, final Vehicle vehicle) { + + this.fastRouter.initialize(); + this.routingNetwork.initialize(); + + Node routingNetworkFromNode; + Node routingNetworkToNode; + + if (fromNode instanceof ImaginaryNode) { + Collection initialNodes = ((ImaginaryNode) fromNode).initialNodes; + for (InitialNode initialNode : initialNodes) initialNode.node = routingNetwork.getNodes().get(initialNode.node.getId()); + routingNetworkFromNode = fromNode; + } else routingNetworkFromNode = routingNetwork.getNodes().get(fromNode.getId()); + + if (toNode instanceof ImaginaryNode) { + Collection initialNodes = ((ImaginaryNode) toNode).initialNodes; + for (InitialNode initialNode : initialNodes) initialNode.node = routingNetwork.getNodes().get(initialNode.node.getId()); + routingNetworkToNode = toNode; + } else routingNetworkToNode = routingNetwork.getNodes().get(toNode.getId()); + + return super.calcLeastCostPath(routingNetworkFromNode, routingNetworkToNode, startTime, person, vehicle); + } + + @Override + /*package*/ RouterPriorityQueue createRouterPriorityQueue() { + /* + * Re-use existing BinaryMinHeap instead of creating a new one. For large networks (> 10^6 nodes and links) this reduced + * the computation time by 40%! cdobler, oct'15 + */ + if (this.routingNetwork instanceof ArrayRoutingNetwork) { + int size = this.routingNetwork.getNodes().size(); + if (this.heap == null || this.maxSize != size) { + this.maxSize = size; + this.heap = new BinaryMinHeap<>(maxSize); + return this.heap; + } else { + this.heap.reset(); + return this.heap; + } +// int maxSize = this.routingNetwork.getNodes().size(); +// return new BinaryMinHeap(maxSize); + } else { + return super.createRouterPriorityQueue(); + } + } + + /* + * Constructs the path and replaces the nodes and links from the routing network + * with their corresponding nodes and links from the network. + */ + @Override + protected Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { + /* + * If the fromNode is an imaginaryNode some special treatment is necessary. + * The path returned by the fastRouter also contains the travel time and cost + * from the trips start coordinate to the start node of the path. This information + * is stored in the ImaginaryNode (respectively in its InitialNodes). Therefore, + * we have to store a reference to the imaginary node. + */ + ImaginaryNode imaginaryNode = null; + if (fromNode instanceof ImaginaryNode) imaginaryNode = (ImaginaryNode) fromNode; + + if (!(fromNode instanceof RoutingNetworkNode)) fromNode = this.routingNetwork.getNodes().get(fromNode.getId()); + if (!(toNode instanceof RoutingNetworkNode)) toNode = this.routingNetwork.getNodes().get(toNode.getId()); + + Path path = this.fastRouter.constructPath(fromNode, toNode, startTime, arrivalTime); + + /* + * Here, we correct the path's travel time and cost if necessary. + * To do so, we look for the InitialNode that matches the path's first node. + * The path's travel time and cost are then reduced by the values + * found in the InitialNode. + */ + if (imaginaryNode != null && path != null && path.nodes.size() > 0) { + Node pathFromNode = path.getFromNode(); + double initialCost = 0.0; + double initialTime = 0.0; + + Iterator iter = imaginaryNode.initialNodes.iterator(); + while (iter.hasNext()) { + InitialNode initialNode = iter.next(); + if (initialNode.node.getId().equals(pathFromNode.getId())) { + initialCost = initialNode.initialCost; + initialTime = initialNode.initialTime; + break; + } + } + + return new Path(path.nodes, path.links, path.travelTime - initialTime, path.travelCost - initialCost); + } + + return path; + } + + /* + * Constructs the path and replaces the nodes and links from the routing network + * with their corresponding nodes and links from the network. + */ + @Override + public Path constructPath(Node fromNode, Node toNode, double startTime) { + if (toNode == null || fromNode == null) return null; + if (!(fromNode instanceof RoutingNetworkNode)) fromNode = this.routingNetwork.getNodes().get(fromNode.getId()); + if (!(toNode instanceof RoutingNetworkNode)) toNode = this.routingNetwork.getNodes().get(toNode.getId()); + return super.constructPath(fromNode, toNode, startTime); + } + + /* + * For performance reasons the outgoing links of a node are stored in + * the routing network in an array instead of a map. Therefore we have + * to iterate over an array instead of over a map. + */ + @Override + protected void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes) { + fastRouter.relaxNode(outNode, toNode, pendingNodes); + } + + /* + * The DijkstraNodeData is taken from the RoutingNetworkNode and not from a map. + */ + @Override + protected DijkstraNodeData getData(final Node n) { + return (DijkstraNodeData) fastRouter.getData(n); + } + + /* + * The DeadEndData is taken from the RoutingNetworkNode and not from a map. + */ + @Override + protected PreProcessDijkstra.DeadEndData getPreProcessData(final Node n) { + return fastRouter.getPreProcessData(n); + } +} diff --git a/matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstraFactory.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/FastMultiNodeDijkstraFactory.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/FastMultiNodeDijkstraFactory.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/FastMultiNodeDijkstraFactory.java diff --git a/matsim/src/main/java/org/matsim/core/router/FastRouterDelegate.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/FastRouterDelegate.java similarity index 97% rename from matsim/src/main/java/org/matsim/core/router/FastRouterDelegate.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/FastRouterDelegate.java index f1706df5828..1ba4c7acd5b 100644 --- a/matsim/src/main/java/org/matsim/core/router/FastRouterDelegate.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/FastRouterDelegate.java @@ -1,65 +1,65 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastRouterDelegate.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2011 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -import org.matsim.api.core.v01.network.Node; -import org.matsim.core.router.util.LeastCostPathCalculator.Path; -import org.matsim.core.router.util.NodeData; -import org.matsim.core.router.util.PreProcessDijkstra; -import org.matsim.core.utils.collections.RouterPriorityQueue; - -/** - * This class is used by the faster implementations of the Dijkstra, AStarEuclidean and - * AStarLandmarks router. Basically, the methods perform the conversation from the - * Network to the RoutingNetwork where the routing data is stored in the nodes and not - * in maps. - * - * @author cdobler - */ -/*package*/ interface FastRouterDelegate { - - /* - * Some implementations might use this for lazy initialization. - */ - /*package*/ void initialize(); - - /* - * Constructs the path and replaces the nodes and links from the routing network - * with their corresponding nodes and links from the network. - */ - /*package*/ Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime); - /* - * For performance reasons the outgoing links of a node are stored in - * the routing network in an array instead of a map. Therefore we have - * to iterate over an array instead of over a map. - */ - /*package*/ void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes); - - /* - * The NodeData is taken from the RoutingNetworkNode and not from a map. - */ - /*package*/ NodeData getData(final Node n); - - /* - * The DeadEndData is taken from the RoutingNetworkNode and not from a map. - */ - /*package*/ PreProcessDijkstra.DeadEndData getPreProcessData(final Node n); -} +/* *********************************************************************** * + * project: org.matsim.* + * FastRouterDelegate.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2011 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.core.router; + +import org.matsim.api.core.v01.network.Node; +import org.matsim.core.router.util.LeastCostPathCalculator.Path; +import org.matsim.core.router.util.NodeData; +import org.matsim.core.router.util.PreProcessDijkstra; +import org.matsim.core.utils.collections.RouterPriorityQueue; + +/** + * This class is used by the faster implementations of the Dijkstra, AStarEuclidean and + * AStarLandmarks router. Basically, the methods perform the conversation from the + * Network to the RoutingNetwork where the routing data is stored in the nodes and not + * in maps. + * + * @author cdobler + */ +/*package*/ interface FastRouterDelegate { + + /* + * Some implementations might use this for lazy initialization. + */ + /*package*/ void initialize(); + + /* + * Constructs the path and replaces the nodes and links from the routing network + * with their corresponding nodes and links from the network. + */ + /*package*/ Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime); + /* + * For performance reasons the outgoing links of a node are stored in + * the routing network in an array instead of a map. Therefore we have + * to iterate over an array instead of over a map. + */ + /*package*/ void relaxNode(final Node outNode, final Node toNode, final RouterPriorityQueue pendingNodes); + + /* + * The NodeData is taken from the RoutingNetworkNode and not from a map. + */ + /*package*/ NodeData getData(final Node n); + + /* + * The DeadEndData is taken from the RoutingNetworkNode and not from a map. + */ + /*package*/ PreProcessDijkstra.DeadEndData getPreProcessData(final Node n); +} diff --git a/matsim/src/main/java/org/matsim/core/router/FastRouterDelegateFactory.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/FastRouterDelegateFactory.java similarity index 94% rename from matsim/src/main/java/org/matsim/core/router/FastRouterDelegateFactory.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/FastRouterDelegateFactory.java index 807ef17ab22..14d1a98a82c 100644 --- a/matsim/src/main/java/org/matsim/core/router/FastRouterDelegateFactory.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/FastRouterDelegateFactory.java @@ -26,5 +26,5 @@ public interface FastRouterDelegateFactory { public FastRouterDelegate createFastRouterDelegate(Dijkstra dijkstra, - NodeDataFactory nodeDataFactory, RoutingNetwork routingNetwork); + NodeDataFactory nodeDataFactory, RoutingNetwork routingNetwork); } diff --git a/matsim/src/main/java/org/matsim/core/router/ImaginaryNode.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/ImaginaryNode.java similarity index 97% rename from matsim/src/main/java/org/matsim/core/router/ImaginaryNode.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/ImaginaryNode.java index 2bd9d531434..2e299aa6d1c 100644 --- a/matsim/src/main/java/org/matsim/core/router/ImaginaryNode.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/ImaginaryNode.java @@ -30,39 +30,39 @@ import org.matsim.utils.objectattributes.attributable.Attributes; /** - * Used by the MultiNodeDijkstra for backwards compatibility with default Dijkstra. - * + * Used by the MultiNodeDijkstra for backwards compatibility with default Dijkstra. + * * @see org.matsim.core.router.Dijkstra - * @see org.matsim.core.router.MultiNodeDijkstra + * @see MultiNodeDijkstra * @author cdobler */ public class ImaginaryNode implements Node { /*package*/ final Collection initialNodes; /*package*/ final Coord coord; - + ImaginaryNode(Collection initialNodes, Coord coord) { this.initialNodes = initialNodes; this.coord = coord; } - + ImaginaryNode(Collection initialNodes) { this.initialNodes = initialNodes; - + double sumX = 0.0; double sumY = 0.0; - + for (InitialNode initialNode : initialNodes) { sumX += initialNode.node.getCoord().getX(); sumY += initialNode.node.getCoord().getY(); } - + sumX /= initialNodes.size(); sumY /= initialNodes.size(); this.coord = new Coord(sumX, sumY); } - + @Override public Coord getCoord() { return this.coord; @@ -112,4 +112,4 @@ public void setCoord(Coord coord) { public Attributes getAttributes() { throw new UnsupportedOperationException(); } -} \ No newline at end of file +} diff --git a/matsim/src/main/java/org/matsim/core/router/MultiNodeDijkstra.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodeDijkstra.java similarity index 97% rename from matsim/src/main/java/org/matsim/core/router/MultiNodeDijkstra.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodeDijkstra.java index 3a400c5d35b..689c16a85c6 100644 --- a/matsim/src/main/java/org/matsim/core/router/MultiNodeDijkstra.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodeDijkstra.java @@ -43,66 +43,66 @@ /** *

An extended implementation of the Dijkstra algorithm that supports multiple * start- and/or end nodes.

- * + * *

To do so, ImaginaryNodes and IntialNodes are introduced which is required for * backwards compatibility with the default Dijkstra implementation which expects * only single nodes as arguments.

- * + * *

Therefore, ImaginaryNodes are introduced. They contain a collection of * InitialNodes that represents the start or end nodes to be used by the algorithm. * Each InitialNode represents a node from the network. In addition, initial time and * costs can be defined, i.e. the time respectively costs to reach that node.

- * + * *

By default, only the cheapest path between is calculated, i.e. the routing algorithm * will terminate before all end nodes have been reached. This behaviour can be changed * by setting the searchAllEndNodes parameter to true.

- * + * * @see org.matsim.core.router.Dijkstra * @see org.matsim.core.router.InitialNode - * @see org.matsim.core.router.ImaginaryNode - * + * @see ImaginaryNode + * * @author cdobler */ public class MultiNodeDijkstra extends Dijkstra implements MultiNodePathCalculator { - + private final static Logger log = LogManager.getLogger(MultiNodeDijkstra.class); - + /* * If this value is true, the algorithm tries to find routes to all end nodes. * Otherwise only the one(s) with the lowest costs are found. - * + * * When enabling this option, select end nodes with care! If only one of them * is in a not reachable part of the network, the algorithm will process the * entire reachable part of the network before terminating. */ private boolean searchAllEndNodes; - + MultiNodeDijkstra(Network network, TravelDisutility costFunction, TravelTime timeFunction, boolean searchAllEndNodes) { super(network, costFunction, timeFunction); this.searchAllEndNodes = searchAllEndNodes; } - + MultiNodeDijkstra(final Network network, final TravelDisutility costFunction, final TravelTime timeFunction, final PreProcessDijkstra preProcessData, boolean searchAllEndNodes) { super(network, costFunction, timeFunction, preProcessData); this.searchAllEndNodes = searchAllEndNodes; } - + public static ImaginaryNode createImaginaryNode(Collection nodes) { return new ImaginaryNode(nodes); } - + public static ImaginaryNode createImaginaryNode(Collection nodes, Coord coord) { return new ImaginaryNode(nodes, coord); } - + /* * We have to extend this method from the original Dijkstra. The given input nodes might be * ImaginaryNodes which contain multiple start / end nodes for the routing process. Those * nodes should be part of the routing network, therefore we perform the check for each of them. - * + * * Regular nodes are directly passed to the super class. - * + * * cdobler, jun'14 */ /*package*/ @Override @@ -112,29 +112,29 @@ void checkNodeBelongToNetwork(Node node) { for (InitialNode initialNode : imaginaryNode.initialNodes) super.checkNodeBelongToNetwork(initialNode.node); } else super.checkNodeBelongToNetwork(node); } - + @Override /*package*/ Node searchLogic( final Node fromNode, final Node toNode, final RouterPriorityQueue pendingNodes, Person person, Vehicle vehicle ) { - + // If it is an imaginary node... if (toNode instanceof ImaginaryNode) { - + Map, InitialNode> endNodes = new HashMap<>(); - + Collection initialNodes = ((ImaginaryNode) toNode).initialNodes; for (InitialNode initialNode : initialNodes) endNodes.put(initialNode.node.getId(), initialNode); // find out which one is the cheapest end node double minCost = Double.POSITIVE_INFINITY; Node minCostNode = null; - + // continue searching as long as unvisited end nodes are available boolean stillSearching = endNodes.size() > 0; - + while (stillSearching) { Node outNode = pendingNodes.poll(); - + if (outNode == null) { /* * This is not necessarily a problem. Some of the out nodes might be reachable only @@ -154,24 +154,24 @@ void checkNodeBelongToNetwork(Node node) { log.trace(sb.toString()); } } - + if (searchAllEndNodes && endNodes.size() > 0) { for (InitialNode endNode : endNodes.values()) { log.trace("No route was found from node " + fromNode.getId() + " to destination node " + endNode.node.getId() + "."); log.trace( Dijkstra.createInfoMessage( person, vehicle ) ); } } - + endNodes.clear(); stillSearching = false; } else { DijkstraNodeData data = getData(outNode); InitialNode initData = endNodes.remove(outNode.getId()); - + /* * If the node is an end node. - * Note: - * The node was head of the priority queue, i.e. the shortest path to the + * Note: + * The node was head of the priority queue, i.e. the shortest path to the * node has been found. The algorithm will not re-visit the node on another * route! */ @@ -182,7 +182,7 @@ void checkNodeBelongToNetwork(Node node) { minCostNode = outNode; } } - + if (searchAllEndNodes) { relaxNode(outNode, null, pendingNodes); stillSearching = endNodes.size() > 0; @@ -196,13 +196,13 @@ void checkNodeBelongToNetwork(Node node) { } } } - + return minCostNode; - } + } // ... otherwise: default behaviour. else return super.searchLogic(fromNode, toNode, pendingNodes, person, vehicle ); } - + /* * initFromNode -> first Node in pendingNodes -> calcLeastCostPath will relax that node * -> we relax that dummy node here @@ -210,18 +210,18 @@ void checkNodeBelongToNetwork(Node node) { @Override /*package*/ void initFromNode(final Node fromNode, final Node toNode, final double startTime, final RouterPriorityQueue pendingNodes) { - + // If it is an imaginary node, we relax it. - if (fromNode instanceof ImaginaryNode) { + if (fromNode instanceof ImaginaryNode) { relaxImaginaryNode((ImaginaryNode) fromNode, pendingNodes, startTime); } // ... otherwise: default behaviour. else super.initFromNode(fromNode, toNode, startTime, pendingNodes); } - + protected void relaxImaginaryNode(final ImaginaryNode outNode, final RouterPriorityQueue pendingNodes, final double currTime) { - + double currCost = 0.0; // should be 0 for (InitialNode initialNode : outNode.initialNodes) { @@ -229,10 +229,10 @@ protected void relaxImaginaryNode(final ImaginaryNode outNode, final RouterPrior double travelCost = initialNode.initialCost; DijkstraNodeData data = getData(initialNode.node); Link l = null; // fromLink - use a dummy link here?? - visitNode(initialNode.node, data, pendingNodes, currTime + travelTime, currCost + travelCost, l); + visitNode(initialNode.node, data, pendingNodes, currTime + travelTime, currCost + travelCost, l); } - } - + } + @Override protected Path constructPath(Node fromNode, Node toNode, double startTime, double arrivalTime) { ArrayList nodes = new ArrayList(); @@ -240,7 +240,7 @@ protected Path constructPath(Node fromNode, Node toNode, double startTime, doubl nodes.add(0, toNode); Link tmpLink = getData(toNode).getPrevLink(); - + // Only this part has been adapted. Could probably also be changed in the super class. while (tmpLink != null) { links.add(0, tmpLink); @@ -256,12 +256,12 @@ protected Path constructPath(Node fromNode, Node toNode, double startTime, doubl return path; } - + /** * This method should only be called from outside after calcLeastCostPath(...) has * been executed. After that, the paths between the multiple start and/or end nodes * can be constructed using this method. - * + * * Is there a way to check whether this method is called as intended?? * cdobler, oct'13 */ @@ -275,9 +275,9 @@ public Path constructPath(Node fromNode, Node toNode, double startTime) { if (!fromData.isVisited(this.getIterationId())) return null; double arrivalTime = toData.getTime(); - + // now construct and return the path - return constructPath(fromNode, toNode, startTime, arrivalTime); + return constructPath(fromNode, toNode, startTime, arrivalTime); } } diff --git a/matsim/src/main/java/org/matsim/core/router/MultiNodeDijkstraFactory.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodeDijkstraFactory.java similarity index 98% rename from matsim/src/main/java/org/matsim/core/router/MultiNodeDijkstraFactory.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodeDijkstraFactory.java index 0859b794dec..060cb872150 100644 --- a/matsim/src/main/java/org/matsim/core/router/MultiNodeDijkstraFactory.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodeDijkstraFactory.java @@ -35,12 +35,12 @@ public class MultiNodeDijkstraFactory implements LeastCostPathCalculatorFactory private final boolean searchAllEndNodes; private final boolean usePreProcessData; private final Map preProcessData = new HashMap<>(); - + public MultiNodeDijkstraFactory() { this.searchAllEndNodes = false; this.usePreProcessData = false; } - + public MultiNodeDijkstraFactory(final boolean searchAllEndNodes) { this.searchAllEndNodes = searchAllEndNodes; this.usePreProcessData = false; @@ -52,7 +52,7 @@ public MultiNodeDijkstraFactory(final boolean usePreProcessData, final boolean s } @Override - public synchronized LeastCostPathCalculator createPathCalculator(final Network network, final TravelDisutility travelCosts, final TravelTime travelTimes) { + public synchronized LeastCostPathCalculator createPathCalculator(final Network network, final TravelDisutility travelCosts, final TravelTime travelTimes) { if (this.usePreProcessData) { PreProcessDijkstra preProcessDijkstra = this.preProcessData.get(network); if (preProcessDijkstra == null) { @@ -62,7 +62,7 @@ public synchronized LeastCostPathCalculator createPathCalculator(final Network n } return new MultiNodeDijkstra(network, travelCosts, travelTimes, preProcessDijkstra, this.searchAllEndNodes); } - + return new MultiNodeDijkstra(network, travelCosts, travelTimes, this.searchAllEndNodes); } -} \ No newline at end of file +} diff --git a/matsim/src/main/java/org/matsim/core/router/MultiNodePathCalculator.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodePathCalculator.java similarity index 98% rename from matsim/src/main/java/org/matsim/core/router/MultiNodePathCalculator.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodePathCalculator.java index 787dd6c9d86..52abc2a295e 100644 --- a/matsim/src/main/java/org/matsim/core/router/MultiNodePathCalculator.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/MultiNodePathCalculator.java @@ -21,11 +21,12 @@ package org.matsim.core.router; import org.matsim.api.core.v01.network.Node; +import org.matsim.core.router.ImaginaryNode; import org.matsim.core.router.util.LeastCostPathCalculator; /** * Marker interface so that one can program against an interface rather than against a very specific implementation. - * + * * @author nagel */ public interface MultiNodePathCalculator extends LeastCostPathCalculator { diff --git a/matsim/src/main/java/org/matsim/core/router/RoutingNetworkImaginaryNode.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/RoutingNetworkImaginaryNode.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/RoutingNetworkImaginaryNode.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/RoutingNetworkImaginaryNode.java diff --git a/matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetwork.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetwork.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetwork.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetwork.java diff --git a/matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkFactory.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkFactory.java similarity index 99% rename from matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkFactory.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkFactory.java index 954f6ad941f..693353a9e13 100644 --- a/matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkFactory.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkFactory.java @@ -26,7 +26,7 @@ import org.matsim.api.core.v01.network.Node; public abstract class AbstractRoutingNetworkFactory implements RoutingNetworkFactory { - + @Override public Link createLink(final Id id, final Node fromNode, final Node toNode) { throw new RuntimeException("Not supported operation!"); @@ -36,4 +36,4 @@ public Link createLink(final Id id, final Node fromNode, final Node toNode public RoutingNetworkNode createNode(final Id id, final Coord coord) { throw new RuntimeException("Not supported operation!"); } -} \ No newline at end of file +} diff --git a/matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkLink.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkLink.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkLink.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkLink.java diff --git a/matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkNode.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkNode.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkNode.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/AbstractRoutingNetworkNode.java diff --git a/matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetwork.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetwork.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetwork.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetwork.java diff --git a/matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkFactory.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkFactory.java similarity index 98% rename from matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkFactory.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkFactory.java index 5954a97430c..314211e9369 100644 --- a/matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkFactory.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkFactory.java @@ -29,12 +29,11 @@ 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.core.network.LinkFactory; public class ArrayRoutingNetworkFactory extends AbstractRoutingNetworkFactory { - + private final static Logger log = LogManager.getLogger(ArrayRoutingNetworkFactory.class); - + private int nodeArrayIndexCounter; private int linkArrayIndexCounter; @@ -42,9 +41,9 @@ public class ArrayRoutingNetworkFactory extends AbstractRoutingNetworkFactory { public synchronized ArrayRoutingNetwork createRoutingNetwork(final Network network) { this.nodeArrayIndexCounter = 0; this.linkArrayIndexCounter = 0; - + ArrayRoutingNetwork routingNetwork = new ArrayRoutingNetwork(network); - + for (Node node : network.getNodes().values()) { RoutingNetworkNode routingNode = createRoutingNetworkNode(node, node.getOutLinks().size()); routingNetwork.addNode(routingNode); @@ -56,22 +55,22 @@ public synchronized ArrayRoutingNetwork createRoutingNetwork(final Network netwo RoutingNetworkLink dijkstraLink = createRoutingNetworkLink(link, fromNode, toNode); routingLinks.put(dijkstraLink.getId(), dijkstraLink); } - + for (Node node : network.getNodes().values()) { RoutingNetworkLink[] outLinks = new RoutingNetworkLink[node.getOutLinks().size()]; - + int i = 0; for (Link outLink : node.getOutLinks().values()) { outLinks[i] = routingLinks.remove(outLink.getId()); i++; } - + RoutingNetworkNode dijkstraNode = routingNetwork.getNodes().get(node.getId()); dijkstraNode.setOutLinksArray(outLinks); } - + if (routingLinks.size() > 0) log.warn("Not all links have been use in the ArrayRoutingNetwork - check connectivity of input network!"); - + return routingNetwork; } @@ -84,4 +83,4 @@ public ArrayRoutingNetworkNode createRoutingNetworkNode(final Node node, final i public ArrayRoutingNetworkLink createRoutingNetworkLink(final Link link, final RoutingNetworkNode fromNode, final RoutingNetworkNode toNode) { return new ArrayRoutingNetworkLink(link, fromNode, toNode, this.linkArrayIndexCounter++); } -} \ No newline at end of file +} diff --git a/matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkLink.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkLink.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkLink.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkLink.java diff --git a/matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkNode.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkNode.java similarity index 100% rename from matsim/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkNode.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/ArrayRoutingNetworkNode.java diff --git a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetwork.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetwork.java similarity index 97% rename from matsim/src/main/java/org/matsim/core/router/util/RoutingNetwork.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetwork.java index d55be2fa8a2..61a7fac40aa 100644 --- a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetwork.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetwork.java @@ -25,6 +25,7 @@ import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.Node; +import org.matsim.core.router.util.RoutingNetworkNode; /** * A network that is used by FastDijkstra, FastAStarEuclidean and FastAStarLandmarks. diff --git a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkFactory.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkFactory.java similarity index 99% rename from matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkFactory.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkFactory.java index 2eeaf5cb661..604f4e91f72 100644 --- a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkFactory.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkFactory.java @@ -26,10 +26,10 @@ import org.matsim.api.core.v01.network.Node; public interface RoutingNetworkFactory extends NetworkFactory { - + public RoutingNetwork createRoutingNetwork(final Network network); - + public RoutingNetworkNode createRoutingNetworkNode(final Node node, final int numOutLinks); - + public RoutingNetworkLink createRoutingNetworkLink(final Link link, final RoutingNetworkNode fromNode, final RoutingNetworkNode toNode); } diff --git a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkLink.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkLink.java similarity index 98% rename from matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkLink.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkLink.java index 48c7eabad08..ce6a058ae91 100644 --- a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkLink.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkLink.java @@ -28,17 +28,17 @@ public interface RoutingNetworkLink extends Link { // routingNetworkLink.getLink().getFreeSpeed() ; // but NOT // routingNetworkLink.getFreeSpeed() ; - // The current approach seems to be doing both. Why did it add the second way of doing things? + // The current approach seems to be doing both. Why did it add the second way of doing things? // (I can imagine that this came from retrofitting, but a design choice explanation would still be helpful.) kai, may'17 public Link getLink(); - + @Override public Id getId(); - + @Override public RoutingNetworkNode getFromNode(); - + @Override public RoutingNetworkNode getToNode(); -} \ No newline at end of file +} diff --git a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkNode.java b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkNode.java similarity index 99% rename from matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkNode.java rename to contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkNode.java index 298e3cfea88..a62e49e3554 100644 --- a/matsim/src/main/java/org/matsim/core/router/util/RoutingNetworkNode.java +++ b/contribs/locationchoice/src/main/java/org/matsim/core/router/util/RoutingNetworkNode.java @@ -24,14 +24,14 @@ import org.matsim.core.router.util.PreProcessDijkstra.DeadEndData; public interface RoutingNetworkNode extends Node { - + public Node getNode(); public void setOutLinksArray(RoutingNetworkLink[] outLinks); - + public RoutingNetworkLink[] getOutLinksArray(); public void setDeadEndData(DeadEndData deadEndData); - + public DeadEndData getDeadEndData(); -} \ No newline at end of file +} diff --git a/matsim/src/test/java/org/matsim/core/router/FastMultiNodeTest.java b/contribs/locationchoice/src/test/java/org/matsim/core/router/FastMultiNodeTest.java similarity index 100% rename from matsim/src/test/java/org/matsim/core/router/FastMultiNodeTest.java rename to contribs/locationchoice/src/test/java/org/matsim/core/router/FastMultiNodeTest.java diff --git a/matsim/src/test/java/org/matsim/core/router/MultiNodeDijkstraTest.java b/contribs/locationchoice/src/test/java/org/matsim/core/router/MultiNodeDijkstraTest.java similarity index 100% rename from matsim/src/test/java/org/matsim/core/router/MultiNodeDijkstraTest.java rename to contribs/locationchoice/src/test/java/org/matsim/core/router/MultiNodeDijkstraTest.java diff --git a/matsim/src/main/java/org/matsim/core/router/FastRouterType.java b/matsim/src/main/java/org/matsim/core/router/FastRouterType.java deleted file mode 100644 index 0c70498ab49..00000000000 --- a/matsim/src/main/java/org/matsim/core/router/FastRouterType.java +++ /dev/null @@ -1,32 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * FastRouterType.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2012 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.core.router; - -/** - * Type Pointer is no longer supported. For now we still keep it here so we - * can throw an exception if somebody tries to us it. - * - * @author cdobler - */ -public enum FastRouterType { - ARRAY, POINTER -} - diff --git a/matsim/src/main/java/org/matsim/core/router/priorityqueue/HasIndex.java b/matsim/src/main/java/org/matsim/core/router/priorityqueue/HasIndex.java index abb3dc906cc..948012eece4 100644 --- a/matsim/src/main/java/org/matsim/core/router/priorityqueue/HasIndex.java +++ b/matsim/src/main/java/org/matsim/core/router/priorityqueue/HasIndex.java @@ -20,21 +20,17 @@ package org.matsim.core.router.priorityqueue; -import org.matsim.core.router.util.ArrayRoutingNetwork; -import org.matsim.core.router.util.ArrayRoutingNetworkNode; - /** * An interface to mark classes that enumerate their objects. Each index * should be unique and can e.g. be used to lookup values in an array. This feature * is used in some classed due to performance reasons since a lookup in an array * is much faster than in a map. - * - * @see ArrayRoutingNetwork - * @see ArrayRoutingNetworkNode + * * @see BinaryMinHeap - * + * * @author cdobler */ +@Deprecated // Id.index() should be used instead nowadays. public interface HasIndex { public int getArrayIndex(); From 0438fc65e7a3620655a23330f5c9fbe6132e5c88 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Mon, 9 Oct 2023 15:50:27 +0200 Subject: [PATCH 147/258] get rid of FastAStarLandmarks in config files --- .../config_demand.xml | 2 +- .../scenarios/grid/jointDemand_config.xml | 2 +- ...g-in-original-coords-minibus-w-transit.xml | 2 +- .../config_berlin_multimodal.xml | 6 +++--- .../useSignalInput/withLanes/config.xml | 10 +++++----- .../useSignalInput/woLanes/config.xml | 8 ++++---- .../org/matsim/contrib/socnetsim/config.xml | 2 +- .../scenarios/berlin/config_multimodal.xml | 12 +++++------ .../scenarios/berlin/config_withinday.xml | 8 ++++---- .../scenarios/berlin/config_multimodal.xml | 20 +++++++++---------- .../scenarios/berlin/config_withinday.xml | 16 +++++++-------- 11 files changed, 44 insertions(+), 44 deletions(-) diff --git a/contribs/application/test/input/org/matsim/smallScaleCommercialTrafficGeneration/config_demand.xml b/contribs/application/test/input/org/matsim/smallScaleCommercialTrafficGeneration/config_demand.xml index 70e7f37a962..fa6c5559c71 100644 --- a/contribs/application/test/input/org/matsim/smallScaleCommercialTrafficGeneration/config_demand.xml +++ b/contribs/application/test/input/org/matsim/smallScaleCommercialTrafficGeneration/config_demand.xml @@ -47,7 +47,7 @@ - + diff --git a/contribs/commercialTrafficApplications/scenarios/grid/jointDemand_config.xml b/contribs/commercialTrafficApplications/scenarios/grid/jointDemand_config.xml index a50d4e78394..a90fd68807a 100644 --- a/contribs/commercialTrafficApplications/scenarios/grid/jointDemand_config.xml +++ b/contribs/commercialTrafficApplications/scenarios/grid/jointDemand_config.xml @@ -47,7 +47,7 @@ - + diff --git a/contribs/minibus/test/input/org/matsim/contrib/minibus/raptor/TransferAtAccessStopRaptorTest/NPETest/config-in-original-coords-minibus-w-transit.xml b/contribs/minibus/test/input/org/matsim/contrib/minibus/raptor/TransferAtAccessStopRaptorTest/NPETest/config-in-original-coords-minibus-w-transit.xml index 9b4ed1bafe3..f754fa8d91d 100644 --- a/contribs/minibus/test/input/org/matsim/contrib/minibus/raptor/TransferAtAccessStopRaptorTest/NPETest/config-in-original-coords-minibus-w-transit.xml +++ b/contribs/minibus/test/input/org/matsim/contrib/minibus/raptor/TransferAtAccessStopRaptorTest/NPETest/config-in-original-coords-minibus-w-transit.xml @@ -37,7 +37,7 @@ - + diff --git a/contribs/multimodal/src/test/resources/test/input/org/matsim/contrib/multimodal/MultiModalControlerListenerTest/config_berlin_multimodal.xml b/contribs/multimodal/src/test/resources/test/input/org/matsim/contrib/multimodal/MultiModalControlerListenerTest/config_berlin_multimodal.xml index dd9be970d79..81e2b81f0e8 100644 --- a/contribs/multimodal/src/test/resources/test/input/org/matsim/contrib/multimodal/MultiModalControlerListenerTest/config_berlin_multimodal.xml +++ b/contribs/multimodal/src/test/resources/test/input/org/matsim/contrib/multimodal/MultiModalControlerListenerTest/config_berlin_multimodal.xml @@ -25,18 +25,18 @@ - + - - diff --git a/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/withLanes/config.xml b/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/withLanes/config.xml index d77841bccdb..74bbb0b2d06 100644 --- a/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/withLanes/config.xml +++ b/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/withLanes/config.xml @@ -55,7 +55,7 @@ - @@ -65,7 +65,7 @@ - + @@ -234,7 +234,7 @@ - + @@ -650,7 +650,7 @@ - + @@ -663,7 +663,7 @@ - + diff --git a/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/woLanes/config.xml b/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/woLanes/config.xml index cf4e7db82ab..c1779b67f83 100644 --- a/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/woLanes/config.xml +++ b/contribs/signals/examples/tutorial/example90TrafficLights/useSignalInput/woLanes/config.xml @@ -55,7 +55,7 @@ - @@ -65,7 +65,7 @@ - + @@ -234,7 +234,7 @@ - + @@ -249,7 +249,7 @@ - + diff --git a/contribs/socnetsim/test/input/org/matsim/contrib/socnetsim/config.xml b/contribs/socnetsim/test/input/org/matsim/contrib/socnetsim/config.xml index cc618ea1dcc..c62a391cbad 100644 --- a/contribs/socnetsim/test/input/org/matsim/contrib/socnetsim/config.xml +++ b/contribs/socnetsim/test/input/org/matsim/contrib/socnetsim/config.xml @@ -36,7 +36,7 @@ - + diff --git a/examples/scenarios/berlin/config_multimodal.xml b/examples/scenarios/berlin/config_multimodal.xml index 79f0250d871..f95e9776da6 100644 --- a/examples/scenarios/berlin/config_multimodal.xml +++ b/examples/scenarios/berlin/config_multimodal.xml @@ -23,20 +23,20 @@ - + - + - + - + @@ -45,7 +45,7 @@ - + @@ -100,7 +100,7 @@ - + diff --git a/examples/scenarios/berlin/config_withinday.xml b/examples/scenarios/berlin/config_withinday.xml index 0b980b99c8e..3e162fabc93 100644 --- a/examples/scenarios/berlin/config_withinday.xml +++ b/examples/scenarios/berlin/config_withinday.xml @@ -23,16 +23,16 @@ - + - + - + @@ -87,7 +87,7 @@ - + diff --git a/matsim/test/input/scenarios/berlin/config_multimodal.xml b/matsim/test/input/scenarios/berlin/config_multimodal.xml index 24f9ce13cbe..b2c4374bbe9 100644 --- a/matsim/test/input/scenarios/berlin/config_multimodal.xml +++ b/matsim/test/input/scenarios/berlin/config_multimodal.xml @@ -23,20 +23,20 @@ - + - + - + - + @@ -45,7 +45,7 @@ - + @@ -55,12 +55,12 @@ - + - + @@ -69,12 +69,12 @@ - + - + @@ -83,7 +83,7 @@ - + diff --git a/matsim/test/input/scenarios/berlin/config_withinday.xml b/matsim/test/input/scenarios/berlin/config_withinday.xml index 68f37a118b0..7e6074c74d0 100644 --- a/matsim/test/input/scenarios/berlin/config_withinday.xml +++ b/matsim/test/input/scenarios/berlin/config_withinday.xml @@ -23,16 +23,16 @@ - + - + - + @@ -46,12 +46,12 @@ - + - + @@ -60,12 +60,12 @@ - + - + @@ -74,7 +74,7 @@ - + From df66cd3968348b39045c68048f376e6696ad2d09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:46:58 +0000 Subject: [PATCH 148/258] build(deps): bump the maven group with 2 updates Bumps the maven group with 2 updates: [org.mockito:mockito-core](https://github.com/mockito/mockito) and [net.bytebuddy:byte-buddy](https://github.com/raphw/byte-buddy). Updates `org.mockito:mockito-core` from 5.5.0 to 5.6.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.5.0...v5.6.0) Updates `net.bytebuddy:byte-buddy` from 1.14.8 to 1.14.9 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.8...byte-buddy-1.14.9) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-type: direct:production update-type: version-update:semver-minor dependency-group: maven - dependency-name: net.bytebuddy:byte-buddy dependency-type: direct:development update-type: version-update:semver-patch dependency-group: maven ... Signed-off-by: dependabot[bot] --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 867da53e42b..7560172417b 100644 --- a/pom.xml +++ b/pom.xml @@ -289,7 +289,7 @@ org.mockito mockito-core - 5.5.0 + 5.6.0 test @@ -308,7 +308,7 @@ net.bytebuddy byte-buddy - 1.14.8 + 1.14.9 test From 676064968c2d961991f44fb847f1fe6fad6dc327 Mon Sep 17 00:00:00 2001 From: Marcel Rieser Date: Mon, 9 Oct 2023 17:12:43 +0200 Subject: [PATCH 149/258] WIP. Getting rid of old transit router. --- .../RandomizingTransitRouterIT.java | 48 +- .../examples/TestSiouxFalls.java | 1 - .../minibus/integration/SubsidyTestIT.java | 29 +- .../contrib/pseudosimulation/RunPSimTest.java | 14 +- .../andreas/mzilske/bvg09/DataPrepare.java | 71 +- .../vsp/pt/ptdisturbances/EditTripsTest.java | 3 - .../matsim/pt/config/TransitConfigGroup.java | 14 +- .../pt/router/TransitLeastCostPathTree.java | 6 +- .../pt/router/TransitRouterImplFactory.java | 73 -- .../matsim/pt/router/TransitRouterModule.java | 3 +- .../matsim/examples/simple/PtScoringTest.java | 106 +-- ...ransitRouterNetworkTravelTimeCostTest.java | 217 ----- .../router/TransitRouterCustomDataTest.java | 209 ----- .../pt/router/TransitRouterImplTest.java | 869 ------------------ .../pt/router/TransitRouterModuleTest.java | 24 +- ...ransitRouterNetworkTravelTimeCostTest.java | 167 ---- .../TransitScheduleReprojectionIOTest.java | 24 +- 17 files changed, 158 insertions(+), 1720 deletions(-) delete mode 100644 matsim/src/main/java/org/matsim/pt/router/TransitRouterImplFactory.java delete mode 100644 matsim/src/test/java/org/matsim/pt/router/AdaptedTransitRouterNetworkTravelTimeCostTest.java delete mode 100644 matsim/src/test/java/org/matsim/pt/router/TransitRouterCustomDataTest.java delete mode 100644 matsim/src/test/java/org/matsim/pt/router/TransitRouterImplTest.java delete mode 100644 matsim/src/test/java/org/matsim/pt/router/TransitRouterNetworkTravelTimeCostTest.java diff --git a/contribs/common/src/test/java/org/matsim/contrib/common/randomizingtransitrouter/RandomizingTransitRouterIT.java b/contribs/common/src/test/java/org/matsim/contrib/common/randomizingtransitrouter/RandomizingTransitRouterIT.java index e703c6c4af6..7e506d5e265 100644 --- a/contribs/common/src/test/java/org/matsim/contrib/common/randomizingtransitrouter/RandomizingTransitRouterIT.java +++ b/contribs/common/src/test/java/org/matsim/contrib/common/randomizingtransitrouter/RandomizingTransitRouterIT.java @@ -54,11 +54,11 @@ */ public class RandomizingTransitRouterIT { private static final Logger log = LogManager.getLogger( RandomizingTransitRouterIT.class ) ; - + private static final class MyObserver implements PersonEntersVehicleEventHandler { // private enum ObservedVehicle{ pt_1009_1 /*direct, fast, with wait*/, pt_2009_1 /*direct, slow*/, pt_3009_1 /*with interchange*/} ; - - Map,Double> cnts = new HashMap<>() ; + + Map,Double> cnts = new HashMap<>() ; @Override public void reset(int iteration) { cnts.clear(); @@ -67,13 +67,13 @@ private static final class MyObserver implements PersonEntersVehicleEventHandler @Override public void handleEvent(PersonEntersVehicleEvent event) { cnts.merge( event.getVehicleId() , 1. , Double::sum ); } - + void printCounts() { for ( Entry, Double> entry : cnts.entrySet() ) { log.info( "Vehicle id: " + entry.getKey() + "; number of boards: " + entry.getValue() ) ; } } - + Map< Id, Double> getCounts() { return this.cnts ; } @@ -87,22 +87,22 @@ public final void test() { String outputDir = utils.getOutputDirectory() ; Config config = utils.createConfigWithPackageInputResourcePathAsContext(); - + config.network().setInputFile("network.xml"); config.plans().setInputFile("population.xml"); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); + config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.SwissRailRaptor); config.transit().setTransitScheduleFile("transitschedule.xml"); config.transit().setVehiclesFile("transitVehicles.xml"); config.transit().setUseTransit(true); - + config.controler().setOutputDirectory( outputDir ); config.controler().setLastIteration(20); config.controler().setCreateGraphs(false); config.controler().setDumpDataAtEnd(false); - + config.global().setNumberOfThreads(1); - + config.planCalcScore().addActivityParams( new ActivityParams("home").setTypicalDuration( 6*3600. ) ); config.planCalcScore().addActivityParams( new ActivityParams("education_100").setTypicalDuration( 6*3600. ) ); @@ -113,7 +113,7 @@ public final void test() { // yy changing the above (= no longer using createAvailableStrategyId) changes the results. :-( :-( :-( config.qsim().setEndTime(18.*3600.); - + config.timeAllocationMutator().setMutationRange(7200); config.timeAllocationMutator().setAffectingDuration(false); config.plans().setRemovingUnneccessaryPlanAttributes(true); @@ -125,39 +125,39 @@ public final void test() { // * The implicit activity coordinates may be elsewhere. // * The "fudged" walk distances may be different. // * It uses getNearestLinkEXACTLY, and thus activities may be attached to other links. - + config.vspExperimental().setWritingOutputEvents(true); config.vspExperimental().setVspDefaultsCheckingLevel( VspDefaultsCheckingLevel.warn ); - + // --- - + Scenario scenario = ScenarioUtils.loadScenario( config ) ; - + // --- - + Controler controler = new Controler( scenario ) ; controler.addOverridingModule( new RandomizingTransitRouterModule() ); final MyObserver observer = new MyObserver(); controler.getEvents().addHandler(observer); - + controler.run(); - + // --- - - observer.printCounts(); - + + observer.printCounts(); + // yyyy the following is just a regression test, making sure that results remain stable. In general, the randomized transit router // could be improved, for example along the lines of the randomized regular router, which uses a (hopefully unbiased) lognormal // distribution rather than a biased uniform distribution as is used here. kai, jul'15 - + Assert.assertEquals(36., observer.getCounts().get( Id.create("1009", Vehicle.class) ), 0.1 ); Assert.assertEquals( 8. /*6.*/ , observer.getCounts().get( Id.create("1012", Vehicle.class) ) , 0.1 ); Assert.assertEquals(22. /*21.*/, observer.getCounts().get( Id.create("2009", Vehicle.class) ) , 0.1 ); Assert.assertEquals(36., observer.getCounts().get( Id.create("3009", Vehicle.class) ) , 0.1 ); - - + + } } diff --git a/contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/examples/TestSiouxFalls.java b/contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/examples/TestSiouxFalls.java index 9d931e70283..c9e6a2610c2 100644 --- a/contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/examples/TestSiouxFalls.java +++ b/contribs/discrete_mode_choice/src/test/java/org/matsim/contrib/discrete_mode_choice/examples/TestSiouxFalls.java @@ -28,7 +28,6 @@ public void testSiouxFallsWithSubtourModeChoiceReplacement() { URL scenarioURL = ExamplesUtils.getTestScenarioURL("siouxfalls-2014"); Config config = ConfigUtils.loadConfig(IOUtils.extendUrl(scenarioURL, "config_default.xml")); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); DiscreteModeChoiceConfigurator.configureAsSubtourModeChoiceReplacement(config); config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); diff --git a/contribs/minibus/src/test/java/org/matsim/contrib/minibus/integration/SubsidyTestIT.java b/contribs/minibus/src/test/java/org/matsim/contrib/minibus/integration/SubsidyTestIT.java index 10116f29b0c..b148eea6270 100644 --- a/contribs/minibus/src/test/java/org/matsim/contrib/minibus/integration/SubsidyTestIT.java +++ b/contribs/minibus/src/test/java/org/matsim/contrib/minibus/integration/SubsidyTestIT.java @@ -42,23 +42,22 @@ import org.matsim.testcases.MatsimTestUtils; /** - * + * * Tests the functionality of adding subsidies to the operator's score. - * + * * @author ikaddoura */ public class SubsidyTestIT implements TabularFileHandler { - + @Rule public MatsimTestUtils utils = new MatsimTestUtils(); private final ArrayList pStatsResults = new ArrayList<>(); @Test public final void testSubsidyPControler() { - + Config config = ConfigUtils.loadConfig( utils.getClassInputDirectory() + "config.xml", new PConfigGroup() ) ; - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); PConfigGroup pConfig = (PConfigGroup) config.getModules().get(PConfigGroup.GROUP_NAME); pConfig.setSubsidyApproach("perPassenger"); @@ -69,18 +68,18 @@ public final void testSubsidyPControler() { config.plans().setInputFile(gridScenarioDirectory + "population_1000_per_hour_each_from_6_to_10.xml.gz"); config.controler().setOutputDirectory(utils.getOutputDirectory()); config.controler().setWriteEventsInterval(0); - Scenario scenario = ScenarioUtils.loadScenario(config); + Scenario scenario = ScenarioUtils.loadScenario(config); Controler controler = new Controler(scenario); - + controler.getConfig().controler().setCreateGraphs(true); - + controler.getConfig().controler().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists ); - + controler.addOverridingModule(new PModule()) ; controler.run(); - - // Check standard output files - + + // Check standard output files + List filesToCheckFor = new LinkedList<>(); filesToCheckFor.add(utils.getOutputDirectory() + "0.actsFromParatransitUsers.txt"); filesToCheckFor.add(utils.getOutputDirectory() + "pOperatorLogger.txt"); @@ -107,11 +106,11 @@ public final void testSubsidyPControler() { // Check final iteration Assert.assertEquals("Number of budget (final iteration)", "202319997.4909444700", this.pStatsResults.get(2)[9]); } - + @Override public void startRow(String[] row) { - this.pStatsResults.add(row); + this.pStatsResults.add(row); } - + } diff --git a/contribs/pseudosimulation/src/test/java/org/matsim/contrib/pseudosimulation/RunPSimTest.java b/contribs/pseudosimulation/src/test/java/org/matsim/contrib/pseudosimulation/RunPSimTest.java index 02d4125191c..d337a156715 100644 --- a/contribs/pseudosimulation/src/test/java/org/matsim/contrib/pseudosimulation/RunPSimTest.java +++ b/contribs/pseudosimulation/src/test/java/org/matsim/contrib/pseudosimulation/RunPSimTest.java @@ -47,13 +47,12 @@ public class RunPSimTest { */ @Test public void testA() { - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); config.controler().setCreateGraphs(false); PSimConfigGroup pSimConfigGroup = new PSimConfigGroup(); config.addModule(pSimConfigGroup); pSimConfigGroup.setIterationsPerCycle(20); - + config.plansCalcRoute().setRoutingRandomness(0.); //identify selector strategies @@ -95,12 +94,12 @@ public void testA() { ((Controler) runPSim.getMatsimControler()).addOverridingModule(new AbstractModule() { @Override - public void install() { + public void install() { this.bind(TransitEmulator.class).to(NoTransitEmulator.class); } }); - - + + runPSim.run(); double psimScore = execScoreTracker.executedScore; logger.info("RunPSim score was " + psimScore); @@ -117,14 +116,13 @@ public void install() { /** * For comparison run 2 normal qsim iterations. Psim score should be slightly higher than default Controler score. - * + * * Prior to implementing routing mode RunPSimTest tested only that psimScore outperformed default Controler on this * test for executed score by a margin > 1%. In the last commit in matsim master where the test ran, the psim score * in testA() was 134.52369453719413 and qsim score in testB was 131.84309487251033). */ @Test public void testB() { - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); config.controler().setOutputDirectory(utils.getOutputDirectory()); config.controler().setLastIteration(2); config.controler().setCreateGraphs(false); @@ -134,7 +132,7 @@ public void testB() { ExecScoreTracker execScoreTracker = new ExecScoreTracker(controler); controler.addControlerListener(execScoreTracker); controler.run(); - + double qsimScore = execScoreTracker.executedScore; logger.info("Default controler score was " + qsimScore ); // Assert.assertEquals("Default controler score changed.", 131.84309487251033d, qsimScore, MatsimTestUtils.EPSILON); diff --git a/contribs/vsp/src/main/java/playground/vsp/andreas/mzilske/bvg09/DataPrepare.java b/contribs/vsp/src/main/java/playground/vsp/andreas/mzilske/bvg09/DataPrepare.java index d380efe7262..b19ef6e3f5a 100644 --- a/contribs/vsp/src/main/java/playground/vsp/andreas/mzilske/bvg09/DataPrepare.java +++ b/contribs/vsp/src/main/java/playground/vsp/andreas/mzilske/bvg09/DataPrepare.java @@ -53,7 +53,6 @@ import org.matsim.pt.UmlaufInterpolator; import org.matsim.pt.config.TransitConfigGroup; import org.matsim.pt.router.TransitRouterConfig; -import org.matsim.pt.router.TransitRouterImpl; import org.matsim.pt.transitSchedule.TransitScheduleWriterV1; import org.matsim.pt.transitSchedule.api.TransitLine; import org.matsim.pt.transitSchedule.api.TransitScheduleWriter; @@ -184,39 +183,43 @@ protected void routePopulation() { } protected void visualizeRouterNetwork() { - - TransitRouterConfig transitRouterConfig = new TransitRouterConfig(this.scenario.getConfig().planCalcScore() - , this.scenario.getConfig().plansCalcRoute(), this.scenario.getConfig().transitRouter(), - this.scenario.getConfig().vspExperimental()); - - TransitRouterImpl router = new TransitRouterImpl(transitRouterConfig, this.scenario.getTransitSchedule() ); - Network routerNet = router.getTransitRouterNetwork(); - - log.info("create vis network"); - MutableScenario visScenario = (MutableScenario) ScenarioUtils.createScenario(ConfigUtils.createConfig()); - Network visNet = visScenario.getNetwork(); - - for (Node node : routerNet.getNodes().values()) { - visNet.getFactory().createNode(node.getId(), node.getCoord()); - visNet.addNode(node); - } - for (Link link : routerNet.getLinks().values()) { - Link l = visNet.getFactory().createLink(link.getId(), visNet.getNodes().get(link.getFromNode().getId()), visNet.getNodes().get(link.getToNode().getId())); - l.setLength(link.getLength()); - l.setFreespeed(link.getFreespeed()); - l.setCapacity(link.getCapacity()); - l.setNumberOfLanes(link.getNumberOfLanes()); - } - - log.info("write routerNet.xml"); - new NetworkWriter(visNet).write("visNet.xml"); - - log.info("start visualizer"); - EventsManager events = EventsUtils.createEventsManager(); - QSim otfVisQSim = new QSimBuilder(visScenario.getConfig()).useDefaults().build(visScenario, events); - OnTheFlyServer server = OTFVis.startServerAndRegisterWithQSim(visScenario.getConfig(), visScenario, events, otfVisQSim); - OTFClientLive.run(visScenario.getConfig(), server); - otfVisQSim.run(); + throw new RuntimeException("this works no longer, sorry."); + + /* I commented this code out, as it directly refers to the old and inefficient TransitRouterImpl + which got removed at the Code Sprint 2023 in Berlin. // mrieser, 2023oct09 + */ + +// TransitRouterConfig transitRouterConfig = new TransitRouterConfig(this.scenario.getConfig().planCalcScore() +// , this.scenario.getConfig().plansCalcRoute(), this.scenario.getConfig().transitRouter(), +// this.scenario.getConfig().vspExperimental()); +// TransitRouterImpl router = new TransitRouterImpl(transitRouterConfig, this.scenario.getTransitSchedule() ); +// Network routerNet = router.getTransitRouterNetwork(); +// +// log.info("create vis network"); +// MutableScenario visScenario = (MutableScenario) ScenarioUtils.createScenario(ConfigUtils.createConfig()); +// Network visNet = visScenario.getNetwork(); +// +// for (Node node : routerNet.getNodes().values()) { +// visNet.getFactory().createNode(node.getId(), node.getCoord()); +// visNet.addNode(node); +// } +// for (Link link : routerNet.getLinks().values()) { +// Link l = visNet.getFactory().createLink(link.getId(), visNet.getNodes().get(link.getFromNode().getId()), visNet.getNodes().get(link.getToNode().getId())); +// l.setLength(link.getLength()); +// l.setFreespeed(link.getFreespeed()); +// l.setCapacity(link.getCapacity()); +// l.setNumberOfLanes(link.getNumberOfLanes()); +// } +// +// log.info("write routerNet.xml"); +// new NetworkWriter(visNet).write("visNet.xml"); +// +// log.info("start visualizer"); +// EventsManager events = EventsUtils.createEventsManager(); +// QSim otfVisQSim = new QSimBuilder(visScenario.getConfig()).useDefaults().build(visScenario, events); +// OnTheFlyServer server = OTFVis.startServerAndRegisterWithQSim(visScenario.getConfig(), visScenario, events, otfVisQSim); +// OTFClientLive.run(visScenario.getConfig(), server); +// otfVisQSim.run(); } private void buildUmlaeufe() { diff --git a/contribs/vsp/src/test/java/playground/vsp/pt/ptdisturbances/EditTripsTest.java b/contribs/vsp/src/test/java/playground/vsp/pt/ptdisturbances/EditTripsTest.java index d82509af72c..f5e1b690f99 100644 --- a/contribs/vsp/src/test/java/playground/vsp/pt/ptdisturbances/EditTripsTest.java +++ b/contribs/vsp/src/test/java/playground/vsp/pt/ptdisturbances/EditTripsTest.java @@ -158,7 +158,6 @@ public void testAgentLeavesStop() { HashMap, Double> arrivalTimes = new HashMap<>(); HashMap, List> trips = new HashMap<>(); Config config = ConfigUtils.loadConfig(configURL); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); String outputDirectory = utils.getOutputDirectory(); config.controler().setOutputDirectory(outputDirectory); Scenario scenario = ScenarioUtils.loadScenario(config); @@ -277,7 +276,6 @@ public void testAgentIsAtTeleportLegAndLeavesStop() { HashMap, Double> arrivalTimes = new HashMap<>(); HashMap, List> trips = new HashMap<>(); Config config = ConfigUtils.loadConfig(configURL); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); String outputDirectory = utils.getOutputDirectory(); config.controler().setOutputDirectory(outputDirectory); Scenario scenario = ScenarioUtils.loadScenario(config); @@ -356,7 +354,6 @@ public void testAgentIsAtTeleportLegAndWaitsAtStop() { HashMap, Double> arrivalTimes = new HashMap<>(); HashMap, List> trips = new HashMap<>(); Config config = ConfigUtils.loadConfig(configURL); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); String outputDirectory = utils.getOutputDirectory(); config.controler().setOutputDirectory(outputDirectory); Scenario scenario = ScenarioUtils.loadScenario(config); diff --git a/matsim/src/main/java/org/matsim/pt/config/TransitConfigGroup.java b/matsim/src/main/java/org/matsim/pt/config/TransitConfigGroup.java index 742ef7d945a..fa2be75da28 100644 --- a/matsim/src/main/java/org/matsim/pt/config/TransitConfigGroup.java +++ b/matsim/src/main/java/org/matsim/pt/config/TransitConfigGroup.java @@ -51,7 +51,7 @@ public class TransitConfigGroup extends ReflectiveConfigGroup { private static final String INSISTING_ON_USING_DEPRECATED_ATTRIBUTE_FILE = "insistingOnUsingDeprecatedAttributeFiles" ; private static final String USING_TRANSIT_IN_MOBSIM = "usingTransitInMobsim" ; - public enum TransitRoutingAlgorithmType {DijkstraBased, SwissRailRaptor} + public enum TransitRoutingAlgorithmType {@Deprecated DijkstraBased, SwissRailRaptor} public static final String TRANSIT_ATTRIBUTES_DEPRECATION_MESSAGE = "using the separate transit stops and lines attribute files is deprecated." + " Add the information directly into each stop or line, using " + @@ -67,7 +67,7 @@ public enum TransitRoutingAlgorithmType {DijkstraBased, SwissRailRaptor} private Set transitModes; private TransitRoutingAlgorithmType routingAlgorithmType = TransitRoutingAlgorithmType.SwissRailRaptor; - + // --- private static final String USE_TRANSIT = "useTransit"; private boolean useTransit = false; @@ -159,7 +159,7 @@ public Set getTransitModes() { public String getTransitLinesAttributesFile() { return transitLinesAttributesFile; } - + @StringSetter( TRANSIT_LINES_ATTRIBUTES ) public void setTransitLinesAttributesFile(final String transitLinesAttributesFile) { this.transitLinesAttributesFile = transitLinesAttributesFile; @@ -172,12 +172,12 @@ public String getTransitStopsAttributesFile() { public URL getTransitStopsAttributesFileURL(URL context) { return ConfigGroup.getInputFileURL(context, getTransitStopsAttributesFile()) ; } - + @StringSetter( TRANSIT_STOPS_ATTRIBUTES ) public void setTransitStopsAttributesFile(final String transitStopsAttributesFile) { this.transitStopsAttributesFile = transitStopsAttributesFile; } - + @StringGetter( USE_TRANSIT ) public boolean isUseTransit() { return this.useTransit; @@ -207,7 +207,7 @@ public String getInputScheduleCRS() { public void setInputScheduleCRS(String inputScheduleCRS) { this.inputScheduleCRS = inputScheduleCRS; } - + public static final String BOARDING_ACCEPTANCE_CMT="under which conditions agent boards transit vehicle" ; public enum BoardingAcceptance { checkLineAndStop, checkStopOnly } private BoardingAcceptance boardingAcceptance = BoardingAcceptance.checkLineAndStop ; @@ -217,7 +217,7 @@ public BoardingAcceptance getBoardingAcceptance() { public void setBoardingAcceptance(BoardingAcceptance boardingAcceptance) { this.boardingAcceptance = boardingAcceptance; } - + private boolean usingTransitInMobsim = true ; @StringSetter( USING_TRANSIT_IN_MOBSIM ) public final void setUsingTransitInMobsim( boolean val ) { diff --git a/matsim/src/main/java/org/matsim/pt/router/TransitLeastCostPathTree.java b/matsim/src/main/java/org/matsim/pt/router/TransitLeastCostPathTree.java index 92a8f0202e2..19bfbe2c2f1 100644 --- a/matsim/src/main/java/org/matsim/pt/router/TransitLeastCostPathTree.java +++ b/matsim/src/main/java/org/matsim/pt/router/TransitLeastCostPathTree.java @@ -47,9 +47,9 @@ import org.matsim.vehicles.Vehicle; /** - * This class is based on and similar to org.matsim.pt.router.MultiNodeDijkstra + * This class is based on and similar to org.matsim.contrib.eventsBasedPTRouter.MultiNodeDijkstra * - * In contrast to org.matsim.pt.router.MultiNodeDijkstra, however, it stores the last + * In contrast to org.matsim.contrib.eventsBasedPTRouter.MultiNodeDijkstra, however, it stores the last * LeastCostPathTree. It is, therefore, much faster than it, in cases where many routes * starting with the same fromNode are calculated subsequently as every route from that * fromCoord can be retrieved now without having to compute the tree again. @@ -531,4 +531,4 @@ protected DijkstraNodeData getData(final Node n) { return r; } -} \ No newline at end of file +} diff --git a/matsim/src/main/java/org/matsim/pt/router/TransitRouterImplFactory.java b/matsim/src/main/java/org/matsim/pt/router/TransitRouterImplFactory.java deleted file mode 100644 index 9842e1b09ac..00000000000 --- a/matsim/src/main/java/org/matsim/pt/router/TransitRouterImplFactory.java +++ /dev/null @@ -1,73 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * TransitRouterImplFactory.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2010 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.pt.router; - -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.config.Config; -import org.matsim.pt.transitSchedule.api.TransitSchedule; - -import jakarta.inject.Inject; -import jakarta.inject.Provider; -import jakarta.inject.Singleton; - -/** - * @author mrieser - */ -@Singleton -public class TransitRouterImplFactory implements Provider { - - private final TransitRouterConfig config; - private final TransitSchedule transitSchedule; - private TransitRouterNetwork routerNetwork; - private PreparedTransitSchedule preparedTransitSchedule; - - @Inject - TransitRouterImplFactory(final TransitSchedule schedule, final EventsManager events, final Config config) { - this(schedule, new TransitRouterConfig( - config.planCalcScore(), - config.plansCalcRoute(), - config.transitRouter(), - config.vspExperimental())); - events.addHandler((TransitScheduleChangedEventHandler) event -> { - routerNetwork = null; - preparedTransitSchedule = null; - }); - } - - public TransitRouterImplFactory(final TransitSchedule schedule, final TransitRouterConfig config) { - this.config = config; - this.transitSchedule = schedule; - } - - @Override - public TransitRouter get() { - if (this.routerNetwork == null) { - this.routerNetwork = TransitRouterNetwork.createFromSchedule(transitSchedule, this.config.getBeelineWalkConnectionDistance()); - } - if (this.preparedTransitSchedule == null) { - this.preparedTransitSchedule = new PreparedTransitSchedule(transitSchedule); - } - - TransitRouterNetworkTravelTimeAndDisutility ttCalculator = new TransitRouterNetworkTravelTimeAndDisutility(this.config, this.preparedTransitSchedule); - return new TransitRouterImpl(this.config, this.preparedTransitSchedule, this.routerNetwork, ttCalculator, ttCalculator); - } - -} diff --git a/matsim/src/main/java/org/matsim/pt/router/TransitRouterModule.java b/matsim/src/main/java/org/matsim/pt/router/TransitRouterModule.java index ea6102825c8..95b724568f6 100644 --- a/matsim/src/main/java/org/matsim/pt/router/TransitRouterModule.java +++ b/matsim/src/main/java/org/matsim/pt/router/TransitRouterModule.java @@ -32,8 +32,7 @@ public void install() { if (getConfig().transit().isUseTransit()) { switch (getConfig().transit().getRoutingAlgorithmType()) { case DijkstraBased: - bind(TransitRouter.class).toProvider(TransitRouterImplFactory.class); - break; + throw new RuntimeException("'DijkstraBased' is no longer supported as a transit routing algorithm. Use 'SwissRailRaptor' instead."); case SwissRailRaptor: install(new SwissRailRaptorModule()); break; diff --git a/matsim/src/test/java/org/matsim/examples/simple/PtScoringTest.java b/matsim/src/test/java/org/matsim/examples/simple/PtScoringTest.java index b7a7551dfbd..90f70bfd755 100644 --- a/matsim/src/test/java/org/matsim/examples/simple/PtScoringTest.java +++ b/matsim/src/test/java/org/matsim/examples/simple/PtScoringTest.java @@ -18,7 +18,7 @@ * *********************************************************************** */ /** - * + * */ package org.matsim.examples.simple; @@ -58,18 +58,18 @@ public class PtScoringTest { @Parameter public TypicalDurationScoreComputation typicalDurationScoreComputation; - + @Parameterized.Parameters public static Object[] testParameters() { - return new Object[] {TypicalDurationScoreComputation.relative,TypicalDurationScoreComputation.uniform}; + return new Object[] {TypicalDurationScoreComputation.relative, TypicalDurationScoreComputation.uniform}; } - + @Rule public MatsimTestUtils utils = new MatsimTestUtils(); @Test public void test_PtScoringLineswitch() { Config config = this.utils.loadConfig(IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("pt-simple-lineswitch"), "config.xml")); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); + config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.SwissRailRaptor); PlanCalcScoreConfigGroup pcs = config.planCalcScore() ; if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)){ @@ -77,7 +77,7 @@ public void test_PtScoringLineswitch() { params.setTypicalDurationScoreComputation(typicalDurationScoreComputation); } } - + pcs.setWriteExperiencedPlans(true); Controler controler = new Controler(config); @@ -99,7 +99,7 @@ public void test_PtScoringLineswitch() { double typicalDuration_s = pcs.getActivityParams("home").getTypicalDuration().seconds(); double priority = 1. ; - + // double zeroUtilityDurationHome_s = CharyparNagelScoringUtils.computeZeroUtilityDuration_s(priority, typicalDuration_s); ActivityUtilityParameters.Builder builder = new ActivityUtilityParameters.Builder( pcs.getActivityParams("home") ) ; ActivityUtilityParameters params = builder.build() ; @@ -130,7 +130,7 @@ public void test_PtScoringLineswitch() { double score = pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (stop1Arr-homeAct1End)/3600. ; System.out.println("score after walk: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt interact: " + score ) ; // yyyy wait is not separately scored!! @@ -141,14 +141,14 @@ public void test_PtScoringLineswitch() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (leaveVeh-enterVeh)/3600. ; System.out.println("score after travel pt: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt interact: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (home2Arr-ptIA2ActEnd)/3600. ; System.out.println("score after walk: " + score ) ; final double duration = homeAct2End-home2Arr; - double tmpScore = (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s + double tmpScore = (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s * Math.log(duration/zeroUtilityDurationHome_s) ; if ( tmpScore < 0 ) { System.out.println("home2score< 0; replacing ... ") ; @@ -163,7 +163,7 @@ public void test_PtScoringLineswitch() { score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (stop2Arr-homeAct2End)/3600. ; System.out.println("score after walk: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (enterVeh2-ptIA3ActEnd)/3600. ; @@ -172,7 +172,7 @@ public void test_PtScoringLineswitch() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (leaveVeh2-enterVeh2)/3600. ; System.out.println("score after travel pt: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (stop3Arr-ptIA4ActEnd)/3600. ; @@ -183,7 +183,7 @@ public void test_PtScoringLineswitch() { // ------ - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (enterVeh3-ptIA5ActEnd)/3600. ; @@ -192,13 +192,13 @@ public void test_PtScoringLineswitch() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (leaveVeh3-enterVeh3)/3600. ; System.out.println("score after travel pt: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (home3Arr-ptIA6ActEnd)/3600. ; System.out.println("score after walk: " + score ) ; - score += (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s + score += (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s * Math.log((homeAct1End-home3Arr+24.*3600)/zeroUtilityDurationHome_s) ; System.out.println("score after home act: " + score ) ; @@ -208,28 +208,28 @@ public void test_PtScoringLineswitch() { // (there is only one person, but we need to get it) System.out.println(" score: " + pp.getSelectedPlan().getScore() ) ; - + if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)){ - Assert.assertEquals(-21.28929580072052, pp.getSelectedPlan().getScore(), MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(-21.280962467387187, pp.getSelectedPlan().getScore(), MatsimTestUtils.EPSILON ) ; } else{ - Assert.assertEquals(27.46011565686209, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(27.468448990195423, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; } - + } } @Test public void test_PtScoringLineswitchAndPtConstant() { Config config = this.utils.loadConfig(IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("pt-simple-lineswitch"), "config.xml")); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); + config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.SwissRailRaptor); PlanCalcScoreConfigGroup pcs = config.planCalcScore() ; if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)) for(ActivityParams params : pcs.getActivityParams()){ params.setTypicalDurationScoreComputation(typicalDurationScoreComputation); } - + pcs.setWriteExperiencedPlans(true); pcs.getModes().get(TransportMode.pt).setConstant(1.); @@ -247,7 +247,7 @@ public void test_PtScoringLineswitchAndPtConstant() { for (Event event : allEvents) { System.out.println(event.toString()); } - + double typicalDuration_s = pcs.getActivityParams("home").getTypicalDuration().seconds(); double priority = 1. ; @@ -280,7 +280,7 @@ public void test_PtScoringLineswitchAndPtConstant() { double score = pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (stop1Arr-homeAct1End)/3600. ; System.out.println("score after walk: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt interact: " + score ) ; score += pcs.getModes().get(TransportMode.pt).getConstant(); @@ -294,13 +294,13 @@ public void test_PtScoringLineswitchAndPtConstant() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (leaveVeh-enterVeh)/3600. ; System.out.println("score after travel pt: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt interact: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (home2Arr-ptIA2ActEnd)/3600. ; System.out.println("score after walk: " + score ) ; - double tmpScore = (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s + double tmpScore = (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s * Math.log((homeAct2End-home2Arr)/zeroUtilityDurationHome_s) ; if ( tmpScore < 0 ) { System.out.println("home2score< 0; replacing ... ") ; @@ -315,7 +315,7 @@ public void test_PtScoringLineswitchAndPtConstant() { score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (stop2Arr-homeAct2End)/3600. ; System.out.println("score after walk: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.pt).getConstant(); @@ -327,7 +327,7 @@ public void test_PtScoringLineswitchAndPtConstant() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (leaveVeh2-enterVeh2)/3600. ; System.out.println("score after travel pt: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (stop3Arr-ptIA4ActEnd)/3600. ; @@ -338,7 +338,7 @@ public void test_PtScoringLineswitchAndPtConstant() { // ------ - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (enterVeh3-ptIA5ActEnd)/3600. ; @@ -347,13 +347,13 @@ public void test_PtScoringLineswitchAndPtConstant() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * (leaveVeh3-enterVeh3)/3600. ; System.out.println("score after travel pt: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score after pt int act: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (home3Arr-ptIA6ActEnd)/3600. ; System.out.println("score after walk: " + score ) ; - score += (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s + score += (pcs.getPerforming_utils_hr()/3600.) * typicalDuration_s * Math.log((homeAct1End-home3Arr+24.*3600)/zeroUtilityDurationHome_s) ; System.out.println("score after home act: " + score ) ; @@ -363,33 +363,33 @@ public void test_PtScoringLineswitchAndPtConstant() { // (there is only one person, but we need to get it) System.out.println(" score: " + pp.getSelectedPlan().getScore() ) ; - + if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)){ // Assert.assertEquals(89.14608279715044, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; - Assert.assertEquals(-19.28929580072052, pp.getSelectedPlan().getScore(), MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(-19.280962467387187, pp.getSelectedPlan().getScore(), MatsimTestUtils.EPSILON ) ; } else{ - Assert.assertEquals(29.46011565686209, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(29.468448990195423, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; } - + } } @Test public void test_PtScoring_Wait() { Config config = this.utils.loadConfig(IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("pt-simple"), "config.xml")); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); + config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.SwissRailRaptor); PlanCalcScoreConfigGroup pcs = config.planCalcScore() ; - + if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)){ for(ActivityParams params : pcs.getActivityParams()){ params.setTypicalDurationScoreComputation(typicalDurationScoreComputation); } } - + pcs.setWriteExperiencedPlans(true); pcs.setMarginalUtlOfWaitingPt_utils_hr(-18.0) ; - + Controler controler = new Controler(config); controler.getConfig().controler().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.overwriteExistingFiles); controler.getConfig().controler().setCreateGraphs(false); @@ -404,7 +404,7 @@ public void test_PtScoring_Wait() { for (Event event : allEvents) { System.out.println(event.toString()); } - + double typicalDuration_s = pcs.getActivityParams("home").getTypicalDuration().seconds(); double priority = 1. ; @@ -423,7 +423,7 @@ public void test_PtScoring_Wait() { double score = pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (timeTransitWalk/3600.) ; System.out.println("score: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score: " + score ) ; System.out.println("marginalUtlOfWaitPt: " + pcs.getMarginalUtlOfWaitingPt_utils_hr() ) ; @@ -436,7 +436,7 @@ public void test_PtScoring_Wait() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * timeTransitInVeh/3600. ; System.out.println("score: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * timeTransitWalk2/3600. ; @@ -451,12 +451,12 @@ public void test_PtScoring_Wait() { // (there is only one person, but we need to get it) System.out.println("agent score: " + pp.getSelectedPlan().getScore() ) ; - + if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)){ - Assert.assertEquals(89.14608279715044, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(89.13108279715044, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; } else{ - Assert.assertEquals(137.14608279715043, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(137.1310827971504, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; } } @@ -465,14 +465,14 @@ public void test_PtScoring_Wait() { @Test public void test_PtScoring() { Config config = this.utils.loadConfig(IOUtils.extendUrl(ExamplesUtils.getTestScenarioURL("pt-simple"), "config.xml")); - config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.DijkstraBased); + config.transit().setRoutingAlgorithmType(TransitRoutingAlgorithmType.SwissRailRaptor); PlanCalcScoreConfigGroup pcs = config.planCalcScore() ; if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)) for(ActivityParams params : pcs.getActivityParams()){ params.setTypicalDurationScoreComputation(typicalDurationScoreComputation); } - + pcs.setWriteExperiencedPlans(true); Controler controler = new Controler(config); @@ -508,7 +508,7 @@ public void test_PtScoring() { double score = pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * (timeTransitWalk/3600.) ; System.out.println("score: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score: " + score ) ; // score += pcs.getMarginalUtlOfWaitingPt_utils_hr() * timeTransitWait/3600. ; @@ -519,7 +519,7 @@ public void test_PtScoring() { score += pcs.getModes().get(TransportMode.pt).getMarginalUtilityOfTraveling() * timeTransitInVeh/3600. ; System.out.println("score: " + score ) ; - // (pt interaction activity) + // (pt interaction activity) System.out.println("score: " + score ) ; score += pcs.getModes().get(TransportMode.walk).getMarginalUtilityOfTraveling() * timeTransitWalk2/3600. ; @@ -534,15 +534,15 @@ public void test_PtScoring() { // (there is only one person, but we need to get it) System.out.println(" score: " + pp.getSelectedPlan().getScore() ) ; - + if(this.typicalDurationScoreComputation.equals(TypicalDurationScoreComputation.uniform)){ - Assert.assertEquals(89.85608279715044, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(89.87441613048377, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; } else{ - Assert.assertEquals(137.85608279715044, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; + Assert.assertEquals(137.87441613048375, pp.getSelectedPlan().getScore(),MatsimTestUtils.EPSILON ) ; } - - + + } } diff --git a/matsim/src/test/java/org/matsim/pt/router/AdaptedTransitRouterNetworkTravelTimeCostTest.java b/matsim/src/test/java/org/matsim/pt/router/AdaptedTransitRouterNetworkTravelTimeCostTest.java deleted file mode 100644 index f4eacd02b10..00000000000 --- a/matsim/src/test/java/org/matsim/pt/router/AdaptedTransitRouterNetworkTravelTimeCostTest.java +++ /dev/null @@ -1,217 +0,0 @@ -/* *********************************************************************** * - * project: org.matsim.* - * TransitRouterNetworkTravelTimeCostTest.java - * * - * *********************************************************************** * - * * - * copyright : (C) 2009 by the members listed in the COPYING, * - * LICENSE and WARRANTY file. * - * email : info at matsim dot org * - * * - * *********************************************************************** * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * See also COPYING, LICENSE and WARRANTY file * - * * - * *********************************************************************** */ - -package org.matsim.pt.router; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.matsim.pt.router.TransitRouterNetwork.TransitRouterNetworkLink; -import org.matsim.testcases.MatsimTestUtils; - -/** - * Tests that specifically look at the computation of the out-of-vehicle wait time. There is overlap with the original tests; it is most - * probably possible to combine/condense them. - *

- * Comments:

- * An event holding the time when the vehicle is ready again for a departure from a stop link or a depot. - * - * @param vehicle the vehicle. - * @param time the time when the vehicle is ready again. - * @author Merlin Unterfinger - */ -record VehicleReadyEvent(Vehicle vehicle, double time) { - -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java deleted file mode 100644 index 3f22acc56a0..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/infrastructure/DefaultInfrastructureRepository.java +++ /dev/null @@ -1,74 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.infrastructure; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.DepotInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.InfrastructureRepository; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.supply.SectionPartInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.SectionSegmentInfo; -import ch.sbb.matsim.contrib.railsim.prototype.supply.StopInfo; -import org.matsim.api.core.v01.Coord; -import org.matsim.api.core.v01.Scenario; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.network.NetworkUtils; - -/** - * Default implementation of the infrastructure repository - * - * @author Merlin Unterfinger - */ -public class DefaultInfrastructureRepository implements InfrastructureRepository { - - private final int stopCapacity; - private final double stopSpeedLimit; - private final double stopLinkLength; - private final int depotCapacity; - private final int depotInOutCapacity; - private final double depotLinkLength; - private final double depotSpeedLimit; - private final double depotOffset; - private final int routeTrainCapacity; - private final double routeSpeedLimit; - private final double routeEuclideanDistanceFactor; - - public DefaultInfrastructureRepository(Scenario scenario) { - var config = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); - stopCapacity = config.getStopTrainCapacity(); - stopSpeedLimit = config.getStopSpeedLimit(); - stopLinkLength = config.getStopLinkLength(); - routeTrainCapacity = config.getRouteTrainCapacity(); - routeSpeedLimit = config.getRouteSpeedLimit(); - routeEuclideanDistanceFactor = config.getRouteEuclideanDistanceFactor(); - depotCapacity = config.getDepotTrainCapacity(); - depotInOutCapacity = config.getDepotInOutCapacity(); - depotLinkLength = config.getDepotLinkLength(); - depotSpeedLimit = config.getDepotSpeedLimit(); - depotOffset = config.getDepotOffset(); - } - - @Override - public StopInfo getStop(String stopId, double x, double y) { - var stopInfo = new StopInfo(stopId, new Coord(x, y), stopLinkLength); - InfrastructureRepository.addRailsimAttributes(stopInfo, stopCapacity, stopSpeedLimit, 0.); - return stopInfo; - } - - @Override - public DepotInfo getDepot(StopInfo stopInfo) { - var depotInfo = new DepotInfo(stopInfo.getId(), new Coord(stopInfo.getCoord().getX(), stopInfo.getCoord().getY() - depotOffset), depotLinkLength, depotOffset, depotOffset, depotCapacity); - // add in link attributes - InfrastructureRepository.addRailsimAttributes(depotInfo, depotCapacity, depotInOutCapacity, depotSpeedLimit, 0.); - return depotInfo; - } - - @Override - public SectionPartInfo getSectionPart(StopInfo fromStop, StopInfo toStop) { - final double length = routeEuclideanDistanceFactor * NetworkUtils.getEuclideanDistance(fromStop.getCoord(), toStop.getCoord()); - var sectionPartInfo = new SectionPartInfo(fromStop.getId(), toStop.getId()); - // add one segment for th section - var sectionSegmentInfo = new SectionSegmentInfo(fromStop.getCoord(), toStop.getCoord(), length); - sectionPartInfo.addSegment(sectionSegmentInfo); - // add link attributes - InfrastructureRepository.addRailsimAttributes(sectionSegmentInfo, routeTrainCapacity, routeSpeedLimit, 0.); - return sectionPartInfo; - } -} diff --git a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java b/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java deleted file mode 100644 index 6b04bce0c92..00000000000 --- a/contribs/railsim/src/main/java/ch/sbb/matsim/contrib/railsim/prototype/supply/rollingstock/DefaultRollingStockRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.rollingstock; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RollingStockRepository; -import ch.sbb.matsim.contrib.railsim.prototype.supply.VehicleTypeInfo; -import org.matsim.api.core.v01.Scenario; -import org.matsim.core.config.ConfigUtils; - -/** - * Default implementation of the rolling stock repository - * - * @author Merlin Unterfinger - */ -public class DefaultRollingStockRepository implements RollingStockRepository { - - private final int passengerCapacity; - private final double length; - private final double maxVelocity; - private final double maxAcceleration; - private final double maxDeceleration; - private final double turnaroundTime; - - public DefaultRollingStockRepository(Scenario scenario) { - var config = ConfigUtils.addOrGetModule(scenario.getConfig(), RailsimSupplyConfigGroup.class); - passengerCapacity = config.getVehiclePassengerCapacity(); - length = config.getVehicleLength(); - maxVelocity = config.getVehicleMaxVelocity(); - maxAcceleration = config.getVehicleMaxAcceleration(); - maxDeceleration = config.getVehicleMaxDeceleration(); - turnaroundTime = config.getVehicleTurnaroundTime(); - } - - @Override - public VehicleTypeInfo getVehicleType(String vehicleTypeId) { - var vehicleTypeInfo = new VehicleTypeInfo(vehicleTypeId, passengerCapacity, length, maxVelocity, turnaroundTime); - RollingStockRepository.addRailsimAttributes(vehicleTypeInfo, maxAcceleration, maxDeceleration); - return vehicleTypeInfo; - } -} diff --git a/contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java b/contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java deleted file mode 100644 index 1f2c61a93bd..00000000000 --- a/contribs/railsim/src/main/java/org/matsim/core/mobsim/qsim/qnetsimengine/QRailsimSignalsNetworkFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.matsim.core.mobsim.qsim.qnetsimengine; - -import com.google.inject.Inject; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Node; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimLinkSpeedCalculator; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.mobsim.framework.MobsimTimer; -import org.matsim.core.mobsim.qsim.interfaces.AgentCounter; -import org.matsim.core.mobsim.qsim.qnetsimengine.QNetsimEngineI.NetsimInternalInterface; -import org.matsim.vis.snapshotwriters.SnapshotLinkWidthCalculator; - -public final class QRailsimSignalsNetworkFactory implements QNetworkFactory { - private final QNetworkFactory delegate; - private RailsimLinkSpeedCalculator linkSpeedCalculator; - private final Scenario scenario; - private final EventsManager events; - private NetsimEngineContext context; - private NetsimInternalInterface netsimEngine; - - @Inject - public QRailsimSignalsNetworkFactory(Scenario scenario, EventsManager events) { - this.scenario = scenario; - this.events = events; - - // TODO throw runtime exception if lanes is switched on... - this.delegate = new QSignalsNetworkFactory(scenario, events); - } - - @Override - public void initializeFactory(AgentCounter agentCounter, MobsimTimer mobsimTimer, NetsimInternalInterface simEngine1) { - SnapshotLinkWidthCalculator linkWidthCalculator = new SnapshotLinkWidthCalculator(); - linkWidthCalculator.setLinkWidthForVis(scenario.getConfig().qsim().getLinkWidthForVis()); - linkWidthCalculator.setLaneWidth(scenario.getNetwork().getEffectiveLaneWidth()); - - AbstractAgentSnapshotInfoBuilder agentSnapshotInfoBuilder = AbstractQNetsimEngine.createAgentSnapshotInfoBuilder(scenario, linkWidthCalculator); - - this.netsimEngine = simEngine1; - this.context = new NetsimEngineContext(events, scenario.getNetwork().getEffectiveCellSize(), agentCounter, agentSnapshotInfoBuilder, scenario.getConfig().qsim(), mobsimTimer, linkWidthCalculator); - - delegate.initializeFactory(agentCounter, mobsimTimer, simEngine1); - } - - @Override - public QNodeI createNetsimNode(Node node) { - return delegate.createNetsimNode(node); - } - - @Override - public QLinkI createNetsimLink(Link link, QNodeI queueNode) { - QLinkImpl.Builder linkBuilder = new QLinkImpl.Builder(context, netsimEngine); - linkBuilder.setLinkSpeedCalculator(this.linkSpeedCalculator); - return linkBuilder.build(link, queueNode); - } - - /** - * @param calculator - */ - public void setLinkSpeedCalculator(RailsimLinkSpeedCalculator calculator) { - this.linkSpeedCalculator = calculator; - } - -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java deleted file mode 100644 index 386075f3b7e..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; -import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; -import org.apache.logging.log4j.LogManager; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainAccelerationApproach; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainSpeedApproach; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; -import org.matsim.core.config.Config; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.controler.Controler; -import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; -import org.matsim.testcases.MatsimTestUtils; - -/** - * @author Ihab Kaddoura - */ -public class RunRailsimAdvancedCorridorTest { - - @Rule - public MatsimTestUtils utils = new MatsimTestUtils(); - - @Test - public final void test0() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // one direction - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setSplitLinks(true); - rscfg.setSplitLinksLength(200.); - rscfg.setTrainAccelerationApproach(TrainAccelerationApproach.speedOnPreviousLink); - rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachVehicleType); - - Scenario scenario = RunRailsim.prepareScenario(config); - - // set minimum time for one link - scenario.getNetwork().getLinks().get(Id.createLinkId("15")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 30 * 60.); - - // set different maximum speed levels for different train types - for (Link link : scenario.getNetwork().getLinks().values()) { - link.getAttributes().putAttribute("Express", 160. / 3.6); - link.getAttributes().putAttribute("Regio", 160. / 3.6); - link.getAttributes().putAttribute("Cargo", 80. / 3.6); - } - -// List linksToAdd = new ArrayList<>(); -// for (Link link : scenario.getNetwork().getLinks().values()) { -// Link linkR = scenario.getNetwork().getFactory().createLink(Id.createLinkId(link.getId().toString() + "_r"), link.getToNode(), link.getFromNode()); -// linkR.setAllowedModes(link.getAllowedModes()); -// linkR.setCapacity(link.getCapacity()); -// linkR.setLength(link.getLength()); -// linkR.setFreespeed(link.getFreespeed()); -// linkR.setNumberOfLanes(link.getNumberOfLanes()); -// for (String attribute : link.getAttributes().getAsMap().keySet()) { -// linkR.getAttributes().putAttribute(attribute, link.getAttributes().getAttribute(attribute)); -// } -// -// linksToAdd.add(linkR); -// } -// for (Link linkR : linksToAdd) { -// scenario.getNetwork().addLink(linkR); -// } - - // add some one direction links -// scenario.getNetwork().getLinks().get(Id.createLinkId("20")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "20_r"); -// scenario.getNetwork().getLinks().get(Id.createLinkId("20_r")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "20"); - -// scenario.getNetwork().getLinks().get(Id.createLinkId("36")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "36_r"); -// scenario.getNetwork().getLinks().get(Id.createLinkId("36_r")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "36"); - -// scenario.getNetwork().getLinks().get(Id.createLinkId("DUG")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "DUG_r"); -// scenario.getNetwork().getLinks().get(Id.createLinkId("DUG_r")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_OPPOSITE_DIRECTION, "DUG"); - - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - } catch (Exception ee) { - ee.printStackTrace(); - LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java deleted file mode 100644 index 330860e5ea6..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest.java +++ /dev/null @@ -1,1066 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainAccelerationApproach; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainSpeedApproach; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.TransitEventHandler; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.config.Config; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; -import org.matsim.core.controler.Controler; -import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; -import org.matsim.core.events.EventsUtils; -import org.matsim.core.events.MatsimEventsReader; -import org.matsim.testcases.MatsimTestUtils; - -import java.util.stream.Collectors; - -/** - * @author Ihab Kaddoura - */ -public class RunRailsimTest { - - private static final Logger log = LogManager.getLogger(RunRailsimTest.class); - - @Rule - public MatsimTestUtils utils = new MatsimTestUtils(); - - @Test - public final void test0() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // one direction - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.qsim().setNumberOfThreads(1); - config.global().setNumberOfThreads(1); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - // double totalTT = 0.; - // for (Double tt : transitEventHandler.getVehicle2totalTraveltime().values()) { - // totalTT += tt; - // } - - // Ist immer noch nicht deterministisch... :-( - - // Assert.assertEquals("Total travel time has changed.", 433780., totalTT, MatsimTestUtils.EPSILON); - // - // Assert.assertEquals("Arrival time has changed.", 43337., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train10---t2").getFirst(), MatsimTestUtils.EPSILON); - // Assert.assertEquals("Arrival time has changed.", 50610., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train10---t3").getFirst(), MatsimTestUtils.EPSILON); - // Assert.assertEquals("Arrival time has changed.", 43337., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train10---t2").getFirst(), MatsimTestUtils.EPSILON); - - } catch (Exception ee) { - ee.printStackTrace(); - LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test1() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // two directions, two trains in same time step - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setSplitLinks(true); - rscfg.setSplitLinksLength(1000.); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 32473., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t2_A-B").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 32620., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t2_B-A").getFirst(), MatsimTestUtils.EPSILON); - - // also make sure ethe vehicles arrive at the end of the route - Assert.assertEquals("Arrival time has changed.", 36146., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); - Assert.assertEquals("Arrival time has changed.", 28800., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t3_B-A").getFirst(), MatsimTestUtils.EPSILON); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test2() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // two directions, several trains in different time steps - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setSplitLinks(true); - rscfg.setSplitLinksLength(1000.); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - Assert.assertEquals("Arrival time has changed.", 36147., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); - Assert.assertEquals("Arrival time has changed.", 36269., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); - Assert.assertEquals("Arrival time has changed.", 36389., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); - Assert.assertEquals("Arrival time has changed.", 36509., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train4---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); - Assert.assertEquals("Arrival time has changed.", 36629., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train5---t3_A-B").getFirst(), MatsimTestUtils.EPSILON); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test3() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // T shaped network - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setAccelerationGlobalDefault(Double.MAX_VALUE); - rscfg.setDecelerationGlobalDefault(Double.MAX_VALUE); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 32474., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t2_A-B").getFirst(), 1.); - Assert.assertEquals("Arrival time has changed.", 32473., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---t2_A-B").getFirst(), 1.); - Assert.assertEquals("Arrival time has changed.", 32547., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---t2_A-B").getFirst(), 1.); - Assert.assertEquals("Arrival time has changed.", 32547., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---t2_A-B").getFirst(), 1.); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test4() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // Genf-Bern - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - System.out.println(transitEventHandler.getVehicleFacilityArrival2time2delay().keySet().stream().sorted().collect(Collectors.toList())); - Assert.assertEquals("Arrival time has changed.", 4121., transitEventHandler.getVehicleFacilityArrival2time2delay().get("Expresszug_GE_BE_train_0---lausanne").getFirst(), 8.0); - Assert.assertEquals("Arrival time has changed.", 48614., transitEventHandler.getVehicleFacilityArrival2time2delay().get("Expresszug_BE_GE_train_8---genf").getFirst(), 8.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test5() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // short links, long train blocking beyond several links - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setAccelerationGlobalDefault(Double.MAX_VALUE); - rscfg.setDecelerationGlobalDefault(Double.MAX_VALUE); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 36257., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---t3_A-B").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test6() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // short links, long train blocking beyond several links - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 30086., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---19-20").getFirst(), MatsimTestUtils.EPSILON); - Assert.assertEquals("Arrival time has changed.", 29977., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---12-13").getFirst(), MatsimTestUtils.EPSILON); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test7() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simple corridor, small links - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29547., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29593., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - // with very slow acceleration - @Test - public final void test7a() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getClassInputDirectory() + "test7/config.xml"}; // simple corridor, small links - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setAccelerationGlobalDefault(0.1); - rscfg.setDecelerationGlobalDefault(0.1); - rscfg.setTrainAccelerationApproach(TrainAccelerationApproach.euclideanDistanceBetweenStops); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 31187., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 5.0); - Assert.assertEquals("Arrival time has changed.", 31290., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 5.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - // with vehicle-specific values provided in the link attributes - @Test - public final void test7b() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getClassInputDirectory() + "test7/config.xml"}; // simple corridor, small links - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachVehicleType); - - Scenario scenario = RunRailsim.prepareScenario(config); - - scenario.getNetwork().getLinks().get(Id.createLinkId("10-11")).getAttributes().putAttribute("trainType1", 1.2345); - scenario.getNetwork().getLinks().get(Id.createLinkId("10-11")).getAttributes().putAttribute("trainType2", 9.8765); - - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29626., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29671., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - // with vehicle-specific values provided in the link attributes - @Test - public final void test7c() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getClassInputDirectory() + "test7/config.xml"}; // simple corridor, small links - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachLine); - - Scenario scenario = RunRailsim.prepareScenario(config); - - scenario.getNetwork().getLinks().get(Id.createLinkId("10-11")).getAttributes().putAttribute("line1", 1.111); - - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29635., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---20-21").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29761., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---20-21").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test8() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // complex intersection - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 30600., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29258., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103224_9").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - /** - * Similar to test8 but with modification of the network: adding a link with a limited zugfolgezeit... - */ - @Test - public final void test8a() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getClassInputDirectory() + "test8/config.xml"}; // complex intersection - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - scenario.getNetwork().getLinks().get(Id.createLinkId("-103251_14")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); - scenario.getNetwork().getLinks().get(Id.createLinkId("-103224_8")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); - - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 30600., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 2.0); - Assert.assertEquals("Arrival time has changed.", 29325., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103224_9").getFirst(), 2.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test9() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // complex intersection, kreuzende Fahrwege ohne gleiche Kanten - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29638., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2----103224_19").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 30852., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 2.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - /** - * Similar to test9 but with modified network: adding Zugfolgezeit attribute. - */ - @Test - public final void test9a() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getClassInputDirectory() + "test9/config.xml"}; // complex intersection, kreuzende Fahrwege ohne gleiche Kanten - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - scenario.getNetwork().getLinks().get(Id.createLinkId("-103251_14")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); - scenario.getNetwork().getLinks().get(Id.createLinkId("-103224_8")).getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 240.); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29638., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2----103224_19").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 31035., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1----103248_19").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test10() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simple intersection, kreuzende Fahrwege ohne gleiche Kanten - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29601., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---B0").getFirst(), MatsimTestUtils.EPSILON); - Assert.assertEquals("Arrival time has changed.", 28801., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---C0").getFirst(), MatsimTestUtils.EPSILON); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test11() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simplified BN-GE situation - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29100., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---B").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29348., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---X").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29468., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---A").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29366., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---A").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test12() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // simplified BN-GE situation - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 29100., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---B").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29402., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---X").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29468., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---A").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 29950., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---A").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test13() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // passingQ - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - Assert.assertEquals("Arrival time has changed.", 39827., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---7-8").getFirst(), 1.0); - Assert.assertEquals("Arrival time has changed.", 35825., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---7-8").getFirst(), 1.0); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - @Test - public final void test14() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // Strecke mti Depot - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - // Assert.assertEquals("Arrival time has changed.", 29100., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---B").getFirst(), MatsimTestUtils.EPSILON); - // Assert.assertEquals("Arrival time has changed.", 29584., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train3---X").getFirst(), MatsimTestUtils.EPSILON); - // Assert.assertEquals("Arrival time has changed.", 29468., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train1---A").getFirst(), MatsimTestUtils.EPSILON); - // Assert.assertEquals("Arrival time has changed.", 29650., transitEventHandler.getVehicleFacilityArrival2time2delay().get("train2---A").getFirst(), MatsimTestUtils.EPSILON); - - } catch (Exception ee) { - ee.printStackTrace(); - log.fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java deleted file mode 100644 index 23f4cb92689..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/AdjustNetworkToScheduleTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.prepare; - -import ch.sbb.matsim.contrib.railsim.prototype.prepare.AdjustNetworkToSchedule; -import org.apache.logging.log4j.LogManager; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.matsim.api.core.v01.Scenario; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimConfigGroup.TrainSpeedApproach; -import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.TransitEventHandler; -import org.matsim.core.api.experimental.events.EventsManager; -import org.matsim.core.config.Config; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; -import org.matsim.core.controler.Controler; -import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; -import org.matsim.core.events.EventsUtils; -import org.matsim.core.events.MatsimEventsReader; -import org.matsim.testcases.MatsimTestUtils; - -/** - * @author Ihab Kaddoura - */ -public class AdjustNetworkToScheduleTest { - - @Rule - public MatsimTestUtils utils = new MatsimTestUtils(); - - @Test - public final void test0() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.network().setInputFile("transitNetwork.xml.gz"); - config.transit().setTransitScheduleFile("transitSchedule.xml.gz"); - config.transit().setVehiclesFile("transitVehicles.xml.gz"); - - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.qsim().setNumberOfThreads(1); - config.global().setNumberOfThreads(1); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - RailsimConfigGroup rscfg = ConfigUtils.addOrGetModule(config, RailsimConfigGroup.class); - rscfg.setTrainSpeedApproach(TrainSpeedApproach.fromLinkAttributesForEachLineAndRoute); - - Scenario scenario = RunRailsim.prepareScenario(config); - - AdjustNetworkToSchedule adjust = new AdjustNetworkToSchedule(scenario); - adjust.run(); - - Controler controler = RunRailsim.prepareControler(scenario); - - controler.run(); - - // read events - String eventsFile = config.controler().getOutputDirectory() + "/" + config.controler().getRunId() + ".output_events.xml.gz"; - - EventsManager events = EventsUtils.createEventsManager(); - TransitEventHandler transitEventHandler = new TransitEventHandler(); - events.addHandler(transitEventHandler); - events.initProcessing(); - new MatsimEventsReader(events).readFile(eventsFile); - events.finishProcessing(); - - transitEventHandler.printResults(config.controler().getOutputDirectory(), config.controler().getRunId()); - - // visualize trains in addition to train path - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(config.controler().getRunId(), utils.getOutputDirectory()); - - } catch (Exception ee) { - ee.printStackTrace(); - LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - - -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java deleted file mode 100644 index 239867b7670..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/DistributeCapacitiesTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.prepare; - -import ch.sbb.matsim.contrib.railsim.prototype.prepare.DistributeCapacities; -import org.apache.logging.log4j.LogManager; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.matsim.api.core.v01.Scenario; -import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; -import org.matsim.core.config.Config; -import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; -import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; -import org.matsim.testcases.MatsimTestUtils; - -/** - * @author Ihab Kaddoura - */ -public class DistributeCapacitiesTest { - - @Rule - public MatsimTestUtils utils = new MatsimTestUtils(); - - @Test - public final void test0() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.network().setInputFile("transitNetwork.xml.gz"); - config.transit().setTransitScheduleFile("transitSchedule.xml.gz"); - config.transit().setVehiclesFile("transitVehicles.xml.gz"); - - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.qsim().setNumberOfThreads(1); - config.global().setNumberOfThreads(1); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = RunRailsim.prepareScenario(config); - - DistributeCapacities adjust = new DistributeCapacities(scenario); - adjust.run(); - - } catch (Exception ee) { - ee.printStackTrace(); - LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java deleted file mode 100644 index 84c922def99..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/prepare/SplitTransitLinksTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.prepare; - -import ch.sbb.matsim.contrib.railsim.prototype.prepare.SplitTransitLinks; -import org.apache.logging.log4j.LogManager; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.matsim.api.core.v01.Scenario; -import org.matsim.api.core.v01.network.Link; -import ch.sbb.matsim.contrib.railsim.prototype.RailsimUtils; -import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; -import org.matsim.core.config.Config; -import org.matsim.core.config.groups.QSimConfigGroup.SnapshotStyle; -import org.matsim.core.controler.OutputDirectoryHierarchy.OverwriteFileSetting; -import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.testcases.MatsimTestUtils; - -import static org.junit.Assert.assertEquals; - -/** - * @author Ihab Kaddoura - */ -public class SplitTransitLinksTest { - - @Rule - public MatsimTestUtils utils = new MatsimTestUtils(); - - @Test - public final void test0() { - - try { - - System.setProperty("matsim.preferLocalDtds", "true"); - - String[] args0 = {utils.getInputDirectory() + "config.xml"}; // one direction - - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.network().setInputFile("trainNetwork.xml"); - config.transit().setTransitScheduleFile("transitSchedule.xml"); - config.transit().setVehiclesFile("transitVehicles.xml"); - - config.qsim().setSnapshotStyle(SnapshotStyle.queue); - config.qsim().setNumberOfThreads(1); - config.global().setNumberOfThreads(1); - config.controler().setOutputDirectory(utils.getOutputDirectory()); - config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists); - - Scenario scenario = ScenarioUtils.loadScenario(config); - - assertEquals(6, scenario.getNetwork().getNodes().size()); - assertEquals(5, scenario.getNetwork().getLinks().size()); - - double distanceBefore = 0.; - for (Link link : scenario.getNetwork().getLinks().values()) { - distanceBefore += link.getLength(); - - if (link.getId().toString().startsWith("t2_OUT-t3_IN")) { - link.getAttributes().putAttribute(RailsimUtils.LINK_ATTRIBUTE_MINIMUM_TIME, 120.); - } - } - - SplitTransitLinks splitTransitLinks = new SplitTransitLinks(scenario); - splitTransitLinks.run(1000.); - - double distanceAfter = 0.; - for (Link link : scenario.getNetwork().getLinks().values()) { - distanceAfter += link.getLength(); - - if (link.getId().toString().startsWith("t2_OUT-t3_IN")) { - double time = RailsimUtils.getMinimumTrainHeadwayTime(link); - assertEquals("minimum time attribute has not been passed to split links", time, 120., MatsimTestUtils.EPSILON); - } - } - assertEquals("distance has changed after splitting the links", distanceBefore, distanceAfter, MatsimTestUtils.EPSILON); - - assertEquals(104, scenario.getNetwork().getNodes().size()); - assertEquals(103, scenario.getNetwork().getLinks().size()); - - } catch (Exception ee) { - ee.printStackTrace(); - LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); - - // if one catches an exception, then one needs to explicitly fail the test: - Assert.fail(); - } - } - -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java deleted file mode 100644 index 31963df79c8..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/RailsimSupplyBuilderTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply; - -import ch.sbb.matsim.contrib.railsim.prototype.RunRailsim; -import ch.sbb.matsim.contrib.railsim.prototype.analysis.ConvertTrainEventsToDefaultEvents; -import ch.sbb.matsim.contrib.railsim.prototype.prepare.SplitTransitLinks; -import org.apache.logging.log4j.LogManager; -import org.junit.Assert; -import org.junit.Before; -import org.junit.FixMethodOrder; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runners.MethodSorters; -import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.Scenario; -import org.matsim.core.config.Config; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.config.groups.QSimConfigGroup; -import org.matsim.core.controler.Controler; -import org.matsim.core.controler.OutputDirectoryHierarchy; -import org.matsim.core.network.io.NetworkWriter; -import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.pt.transitSchedule.api.Departure; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; -import org.matsim.pt.transitSchedule.api.TransitScheduleWriter; -import org.matsim.testcases.MatsimTestUtils; -import org.matsim.vehicles.MatsimVehicleWriter; - -import java.nio.file.Paths; -import java.util.stream.Stream; - -import static org.junit.Assert.*; - -/** - * Test for supply generation using railsim supply builder. - * - * @author Merlin Unterfinger - */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class RailsimSupplyBuilderTest { - - private static final double TOLERANCE_DELTA = 0.001; - private static final String CONFIG_FILE = "config.xml"; - private static final String NETWORK_FILE = "transitNetwork.xml"; - private static final String SCHEDULE_FILE = "transitSchedule.xml"; - private static final String VEHICLE_FILE = "transitVehicles.xml"; - private static final String RUN_ID = "test"; - private Scenario scenario; - private RailsimSupplyBuilder supply; - private double waitingTime; - - @Rule - public MatsimTestUtils utils = new MatsimTestUtils(); - - @Before - public void setUp() { - Config config = ConfigUtils.createConfig(); - config.global().setCoordinateSystem("CH1903plus_LV95"); - scenario = ScenarioUtils.loadScenario(config); - supply = new RailsimSupplyBuilder(scenario); - waitingTime = 3. * 60; - } - - @Test - public void testBuild() { - supply.addStop("genf", 2499965., 1119074.); - supply.addStop("versoix", 2501905., 1126194.); - supply.addStop("coppet", 2503642., 1130305.); - supply.addStop("nyon", 2507480., 1137714.); - // add transit line: IR - final var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); - ir.addStop("versoix", 10 * 60., waitingTime); - ir.addStop("coppet", 30 * 60., waitingTime); - ir.addStop("nyon", 20 * 60., waitingTime); - Stream.of(10 * 3600., 11 * 3600., 12 * 3600., 13 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.FORWARD, departureTime)); - Stream.of(10.25 * 3600., 11.25 * 3600., 12.25 * 3600., 13.25 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.REVERSE, departureTime)); - // add transit line: IC - final var ic = supply.addTransitLine("IC", "IC", "genf", waitingTime); - ic.addPass("versoix"); - ic.addPass("coppet"); - ic.addStop("nyon", 45 * 60., waitingTime); - Stream.of(10.5 * 3600., 11.5 * 3600., 12.5 * 3600., 13.5 * 3600.).forEach(departureTime -> ic.addDeparture(RouteDirection.FORWARD, departureTime)); - Stream.of(10.5 * 3600., 11.5 * 3600., 12.5 * 3600., 13.5 * 3600.).forEach(departureTime -> ic.addDeparture(RouteDirection.REVERSE, departureTime)); - // build transit schedule and network - supply.build(); - // check generated transit schedule - // network - assertEquals(12, scenario.getNetwork().getNodes().size()); - assertEquals(16, scenario.getNetwork().getLinks().size()); - // vehicles - assertEquals(2, scenario.getTransitVehicles().getVehicleTypes().size()); - assertEquals(5, scenario.getTransitVehicles().getVehicles().size()); - // schedule - assertEquals(6, scenario.getTransitSchedule().getFacilities().size()); - assertEquals(2, scenario.getTransitSchedule().getTransitLines().size()); - assertEquals(10, scenario.getTransitSchedule().getTransitLines().values().stream().mapToInt(l -> l.getRoutes().size()).sum()); - // line - final String id = "IC"; - var transitLine = scenario.getTransitSchedule().getTransitLines().get(Id.create(id, TransitLine.class)); - assertEquals(id, transitLine.getId().toString()); - assertNull(transitLine.getName()); - assertEquals(6, transitLine.getRoutes().size()); - // route - var transitRoute = transitLine.getRoutes().get(Id.create("IC_F_STATION_TO_STATION", TransitRoute.class)); - assertNull(transitRoute.getDescription()); - assertEquals(2, transitRoute.getDepartures().size()); - assertEquals(2, transitRoute.getStops().size()); - assertEquals(41400., transitRoute.getDepartures().get(Id.create("0", Departure.class)).getDepartureTime(), TOLERANCE_DELTA); - assertEquals(45000., transitRoute.getDepartures().get(Id.create("1", Departure.class)).getDepartureTime(), TOLERANCE_DELTA); - var firstStop = transitRoute.getStops().get(0); - assertTrue(firstStop.getArrivalOffset().isUndefined()); - assertEquals(0., firstStop.getDepartureOffset().seconds(), TOLERANCE_DELTA); - var lastStop = transitRoute.getStops().get(transitRoute.getStops().size() - 1); - assertEquals(2700., lastStop.getArrivalOffset().seconds(), TOLERANCE_DELTA); - assertTrue(lastStop.getDepartureOffset().isUndefined()); - // write files - String outputDir = utils.getOutputDirectory(); - new NetworkWriter(scenario.getNetwork()).write(outputDir + NETWORK_FILE); - new TransitScheduleWriter(scenario.getTransitSchedule()).writeFile(outputDir + SCHEDULE_FILE); - new MatsimVehicleWriter(scenario.getTransitVehicles()).writeFile(outputDir + VEHICLE_FILE); - } - - @Test - public final void testRunRailsim() { - System.setProperty("matsim.preferLocalDtds", "true"); - String inputDir = Paths.get("").toAbsolutePath() + "/" + utils.getOutputDirectory().replace("testRunRailsim/", "testBuild/"); - String outputDir = utils.getOutputDirectory(); - String[] args0 = {utils.getInputDirectory() + CONFIG_FILE}; - try { - // setup - Config config = RunRailsim.prepareConfig(args0); - config.controler().setLastIteration(0); - config.network().setInputFile(inputDir + NETWORK_FILE); - config.transit().setTransitScheduleFile(inputDir + SCHEDULE_FILE); - config.transit().setVehiclesFile(inputDir + VEHICLE_FILE); - config.qsim().setSnapshotStyle(QSimConfigGroup.SnapshotStyle.queue); - config.controler().setOutputDirectory(outputDir); - config.controler().setOverwriteFileSetting(OutputDirectoryHierarchy.OverwriteFileSetting.deleteDirectoryIfExists); - // split links - Scenario scenario = RunRailsim.prepareScenario(config); - SplitTransitLinks splitTransitLinks = new SplitTransitLinks(scenario); - splitTransitLinks.run(100.); - assertEquals(412, scenario.getNetwork().getNodes().size()); - assertEquals(416, scenario.getNetwork().getLinks().size()); - // run one iteration - Controler controler = RunRailsim.prepareControler(scenario); - controler.run(); - // convert train events - ConvertTrainEventsToDefaultEvents analysis = new ConvertTrainEventsToDefaultEvents(); - analysis.run(RUN_ID, outputDir); - } catch (Exception ee) { - ee.printStackTrace(); - LogManager.getLogger(this.getClass()).fatal("there was an exception: \n" + ee); - Assert.fail(); - } - } -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java deleted file mode 100644 index 417a5768bdc..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/DefaultVehicleCircuitsPlannerTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyBuilder; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; -import org.junit.Before; -import org.junit.Test; -import org.matsim.api.core.v01.Id; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.junit.Assert.assertTrue; - -/** - * Testing default vehicle circuits planner approach - * - * @author Merlin Unterfinger - */ -public class DefaultVehicleCircuitsPlannerTest { - private RailsimSupplyBuilder supply; - - @Before - public void setUp() { - // configure - var config = ConfigUtils.createConfig(); - config.global().setCoordinateSystem("CH1903plus_LV95"); - var railsimConfigGroup = ConfigUtils.addOrGetModule(config, RailsimSupplyConfigGroup.class); - railsimConfigGroup.setCircuitPlanningApproach(RailsimSupplyConfigGroup.CircuitPlanningApproach.DEFAULT); - var scenario = ScenarioUtils.loadScenario(config); - // setup supply builder - supply = new RailsimSupplyBuilder(scenario); - // add transit stops - supply.addStop("genf", 2499965., 1119074.); - supply.addStop("nyon", 2507480., 1137714.); - supply.addStop("morges", 2527501., 1151542.); - supply.addStop("lausanne", 2507480., 1137714.); - // add transit line: IR - double waitingTime = 3 * 60.; - var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); - ir.addPass("nyon"); - ir.addPass("morges"); - ir.addStop("lausanne", 55 * 60., waitingTime); - Stream.of(0. * 3600, 1 * 3600., 2 * 3600., 10 * 3600., 11 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.FORWARD, departureTime)); - Stream.of(0.25 * 3600., 1.25 * 3600., 2.25 * 3600., 11.25 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.REVERSE, departureTime)); - // add transit line: S - var s = supply.addTransitLine("S", "S", "genf", waitingTime); - s.addStop("nyon", 25 * 60 * 2., waitingTime); - s.addStop("morges", 15 * 60 + 2., waitingTime); - s.addStop("lausanne", 15 * 60 * 2., waitingTime); - Stream.of(0. * 3600, 0.25 * 3600., 0.5 * 3600., 0.75 * 3600., 1 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.FORWARD, departureTime)); - Stream.of(0.10 * 3600., 0.35 * 3600., 0.60 * 3600., 0.85 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.REVERSE, departureTime)); - } - - @Test - public void plan() { - // let supply builder create transit lines and plan circuits - supply.build(); - // check - var schedule = supply.getScenario().getTransitSchedule(); - // IR - String lineId = "IR"; - assertTrue(checkVehicleIds(List.of("IR_1", "IR_2"), lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_DEPOT)); - assertTrue(checkVehicleIds(List.of("IR_0", "IR_2", "IR_0"), lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_STATION)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.STATION_TO_DEPOT)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.STATION_TO_STATION)); - assertTrue(checkVehicleIds(List.of("IR_1"), lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_DEPOT)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_STATION)); - assertTrue(checkVehicleIds(List.of("IR_0", "IR_2", "IR_0"), lineId, RouteDirection.REVERSE, RouteType.STATION_TO_DEPOT)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.STATION_TO_STATION)); - } - - private boolean checkVehicleIds(List vehicleIds, String lineId, RouteDirection routeDirection, RouteType routeType) { - var line = supply.getScenario().getTransitSchedule().getTransitLines().get(Id.create(lineId, TransitLine.class)); - var route = line.getRoutes().get(createRouteId(lineId, routeDirection, routeType)); - if (route != null) { - var allocatedVehicleIds = route.getDepartures().values().stream().map(d -> d.getVehicleId().toString()).collect(Collectors.toList()); - if (vehicleIds.equals(allocatedVehicleIds)) { - return true; - } - System.out.printf("Got %s instead of %s\n", allocatedVehicleIds, vehicleIds); - } - return route == null && vehicleIds == null; - } - - private Id createRouteId(String lineId, RouteDirection routeDirection, RouteType routeType) { - return Id.create(String.format("%s_%s_%s", lineId, routeDirection.getAbbreviation(), routeType.name()), TransitRoute.class); - } - -} diff --git a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java b/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java deleted file mode 100644 index 03cfedaa68b..00000000000 --- a/contribs/railsim/src/test/java/ch/sbb/matsim/contrib/railsim/prototype/supply/circuits/NoVehicleCircuitsPlannerTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package ch.sbb.matsim.contrib.railsim.prototype.supply.circuits; - -import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyBuilder; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RailsimSupplyConfigGroup; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteDirection; -import ch.sbb.matsim.contrib.railsim.prototype.supply.RouteType; -import org.junit.Before; -import org.junit.Test; -import org.matsim.api.core.v01.Id; -import org.matsim.core.config.ConfigUtils; -import org.matsim.core.scenario.ScenarioUtils; -import org.matsim.pt.transitSchedule.api.TransitLine; -import org.matsim.pt.transitSchedule.api.TransitRoute; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.junit.Assert.assertTrue; - -/** - * Testing no vehicle circuits planner approach - * - * @author Merlin Unterfinger - */ -public class NoVehicleCircuitsPlannerTest { - - private RailsimSupplyBuilder supply; - - @Before - public void setUp() { - // configure - var config = ConfigUtils.createConfig(); - config.global().setCoordinateSystem("CH1903plus_LV95"); - var railsimConfigGroup = ConfigUtils.addOrGetModule(config, RailsimSupplyConfigGroup.class); - railsimConfigGroup.setCircuitPlanningApproach(RailsimSupplyConfigGroup.CircuitPlanningApproach.NONE); - var scenario = ScenarioUtils.loadScenario(config); - // setup supply builder - supply = new RailsimSupplyBuilder(scenario); - // add transit stops - supply.addStop("genf", 2499965., 1119074.); - supply.addStop("nyon", 2507480., 1137714.); - supply.addStop("morges", 2527501., 1151542.); - supply.addStop("lausanne", 2507480., 1137714.); - // add transit line: IR - double waitingTime = 3 * 60.; - var ir = supply.addTransitLine("IR", "IR", "genf", waitingTime); - ir.addPass("nyon"); - ir.addPass("morges"); - ir.addStop("lausanne", 55 * 60., waitingTime); - Stream.of(0. * 3600, 1 * 3600., 2 * 3600., 10 * 3600., 11 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.FORWARD, departureTime)); - Stream.of(0.25 * 3600., 1.25 * 3600., 2.25 * 3600., 11.25 * 3600.).forEach(departureTime -> ir.addDeparture(RouteDirection.REVERSE, departureTime)); - // add transit line: S - var s = supply.addTransitLine("S", "S", "genf", waitingTime); - s.addStop("nyon", 25 * 60 * 2., waitingTime); - s.addStop("morges", 15 * 60 + 2., waitingTime); - s.addStop("lausanne", 15 * 60 * 2., waitingTime); - Stream.of(0. * 3600, 0.25 * 3600., 0.5 * 3600., 0.75 * 3600., 1 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.FORWARD, departureTime)); - Stream.of(0.10 * 3600., 0.35 * 3600., 0.60 * 3600., 0.85 * 3600.).forEach(departureTime -> s.addDeparture(RouteDirection.REVERSE, departureTime)); - } - - @Test - public void plan() { - // let supply builder create transit lines and plan circuits - supply.build(); - // check - var schedule = supply.getScenario().getTransitSchedule(); - // IR - String lineId = "IR"; - assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_DEPOT)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.DEPOT_TO_STATION)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.FORWARD, RouteType.STATION_TO_DEPOT)); - assertTrue(checkVehicleIds(List.of("IR_0", "IR_1", "IR_2", "IR_3", "IR_4"), lineId, RouteDirection.FORWARD, RouteType.STATION_TO_STATION)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_DEPOT)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.DEPOT_TO_STATION)); - assertTrue(checkVehicleIds(null, lineId, RouteDirection.REVERSE, RouteType.STATION_TO_DEPOT)); - assertTrue(checkVehicleIds(List.of("IR_5", "IR_6", "IR_7", "IR_8"), lineId, RouteDirection.REVERSE, RouteType.STATION_TO_STATION)); - } - - private boolean checkVehicleIds(List vehicleIds, String lineId, RouteDirection routeDirection, RouteType routeType) { - var line = supply.getScenario().getTransitSchedule().getTransitLines().get(Id.create(lineId, TransitLine.class)); - var route = line.getRoutes().get(createRouteId(lineId, routeDirection, routeType)); - if (route != null) { - var allocatedVehicleIds = route.getDepartures().values().stream().map(d -> d.getVehicleId().toString()).collect(Collectors.toList()); - if (vehicleIds.equals(allocatedVehicleIds)) { - return true; - } - System.out.printf("Got %s instead of %s\n", allocatedVehicleIds, vehicleIds); - } - return route == null && vehicleIds == null; - } - - private Id createRouteId(String lineId, RouteDirection routeDirection, RouteType routeType) { - return Id.create(String.format("%s_%s_%s", lineId, routeDirection.getAbbreviation(), routeType.name()), TransitRoute.class); - } -} diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml deleted file mode 100644 index 21e8e2c86eb..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/config.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml deleted file mode 100644 index 163fdf58571..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml +++ /dev/null @@ -1,329 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/network_trainCorridor.xml.gz deleted file mode 100644 index 471c58c8ec947f89a288eda607014fcb47ce7380..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4838 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 M0D%P=-7^OP0Aw - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml deleted file mode 100644 index 0aa82636212..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimAdvancedCorridorTest/test0/transitVehicles.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - 0.7 - 0.7 - - - - - - - - - - 0.7 - 0.7 - - - - - - - - - - 0.1 - 0.1 - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml deleted file mode 100644 index 80d11a1d7ab..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml deleted file mode 100644 index 89d2e617067..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/trainNetwork.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - 999 - - - - - 2 - - - - - 999 - - - - - 5 - - - - - 999 - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml deleted file mode 100644 index be9b4aedcc4..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitSchedule.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml deleted file mode 100644 index 790b6b25dae..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test0/transitVehicles.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml deleted file mode 100644 index 53a93be2cb1..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/trainNetwork.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - t2_B-t2_A - - - - - 1 - t2_A-t2_B - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml deleted file mode 100644 index cc3851daffe..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitSchedule.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml deleted file mode 100644 index 2a9ef4bcb89..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test1/transitVehicles.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml deleted file mode 100644 index 192e9e16abc..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/config.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml deleted file mode 100644 index 8435acece8d..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/trainNetwork.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - 2 - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml deleted file mode 100644 index d1c6aa9a2fc..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitSchedule.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml deleted file mode 100644 index c86eff16d18..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test10/transitVehicles.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - 10.0 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml deleted file mode 100644 index 81b97846d66..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/trainNetwork.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - 999 - - - - - 999 - - - - - - 999 - - - - - 999 - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml deleted file mode 100644 index 5f1443a7b3a..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitSchedule.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml deleted file mode 100644 index 103d0251dad..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test11/transitVehicles.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - 1.0 - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml deleted file mode 100644 index 41b33e143d5..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/trainNetwork.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - 999 - - - - - 999 - - - - - - 999 - - - - - 999 - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml deleted file mode 100644 index 5f1443a7b3a..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitSchedule.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml deleted file mode 100644 index 103d0251dad..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test12/transitVehicles.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - 1.0 - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml deleted file mode 100644 index 23df0c093d0..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml deleted file mode 100644 index 224e09e269a..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/trainNetwork.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 999 - - - - - - 1 - - - - - 1 - - - - - - 999 - - - - - - 1 - - - - - 1 - - - - - - 3 - - - - - - 1 - - - - - - 999 - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml deleted file mode 100644 index fd97728b28d..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitSchedule.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml deleted file mode 100644 index a2056088fd3..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test13/transitVehicles.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml deleted file mode 100644 index 5075a48d790..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/trainNetwork.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - 2 - - - - - - - - - 2 - - - - - - - - - 2 - - - - - - - - - 2 - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml deleted file mode 100644 index 17d4fe0d429..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitSchedule.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml deleted file mode 100644 index de64a5a9958..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test14/transitVehicles.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml deleted file mode 100644 index c5050d6d5f7..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml deleted file mode 100644 index 56fa13496fd..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/trainNetwork.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 5 - - - - - 5 - - - - - - 1 - t2_B-t2_A - - - - - 1 - t2_A-t2_B - - - - - - 5 - - - - - 5 - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml deleted file mode 100644 index 5d12af79465..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitSchedule.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml deleted file mode 100644 index af93d6d5830..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test2/transitVehicles.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml deleted file mode 100644 index 23df0c093d0..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/config.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml deleted file mode 100644 index 9bd8fcb2a63..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/trainNetwork.xml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - Atlantis - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 999 - - - - - 999 - - - - - - 1 - - - - - 999 - - - - - - 999 - - - - - 999 - - - - - - 999 - - - - - 999 - - - - - - 999 - - - - - 999 - - - - - - 2 - - - - - 1 - - - - - - 999 - - - - - 999 - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml deleted file mode 100644 index 92e5e3b25ae..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitSchedule.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - - rail - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml deleted file mode 100644 index 2a9ef4bcb89..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test3/transitVehicles.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - 5.0 - serial - 5.0 - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml deleted file mode 100644 index 0de681bb7b0..00000000000 --- a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/config.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/trainNetwork.xml.gz b/contribs/railsim/test/input/ch/sbb/matsim/contrib/railsim/prototype/RunRailsimTest/test4/trainNetwork.xml.gz deleted file mode 100644 index 22bff2fe04fd3fea4f9b7b5530299185c0586463..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159748 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