View Javadoc
1   package org.opentrafficsim.demo;
2   
3   import java.io.BufferedWriter;
4   import java.io.IOException;
5   import java.rmi.RemoteException;
6   import java.util.ArrayList;
7   import java.util.LinkedHashMap;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.djunits.unit.FrequencyUnit;
13  import org.djunits.unit.SpeedUnit;
14  import org.djunits.unit.TimeUnit;
15  import org.djunits.value.storage.StorageType;
16  import org.djunits.value.vdouble.scalar.Acceleration;
17  import org.djunits.value.vdouble.scalar.Direction;
18  import org.djunits.value.vdouble.scalar.Duration;
19  import org.djunits.value.vdouble.scalar.Length;
20  import org.djunits.value.vdouble.scalar.Speed;
21  import org.djunits.value.vdouble.scalar.Time;
22  import org.djunits.value.vdouble.vector.FrequencyVector;
23  import org.djunits.value.vdouble.vector.TimeVector;
24  import org.djunits.value.vdouble.vector.base.DoubleVector;
25  import org.djutils.cli.CliUtil;
26  import org.djutils.data.csv.CsvData;
27  import org.djutils.data.serialization.TextSerializationException;
28  import org.djutils.event.Event;
29  import org.djutils.exceptions.Throw;
30  import org.djutils.exceptions.Try;
31  import org.djutils.io.CompressedFileWriter;
32  import org.opentrafficsim.base.parameters.ParameterException;
33  import org.opentrafficsim.base.parameters.ParameterSet;
34  import org.opentrafficsim.base.parameters.ParameterTypeDuration;
35  import org.opentrafficsim.base.parameters.ParameterTypes;
36  import org.opentrafficsim.base.parameters.Parameters;
37  import org.opentrafficsim.base.parameters.constraint.NumericConstraint;
38  import org.opentrafficsim.core.animation.gtu.colorer.AccelerationGtuColorer;
39  import org.opentrafficsim.core.animation.gtu.colorer.GtuColorer;
40  import org.opentrafficsim.core.animation.gtu.colorer.IdGtuColorer;
41  import org.opentrafficsim.core.animation.gtu.colorer.SpeedGtuColorer;
42  import org.opentrafficsim.core.animation.gtu.colorer.SwitchableGtuColorer;
43  import org.opentrafficsim.core.definitions.Defaults;
44  import org.opentrafficsim.core.definitions.DefaultsNl;
45  import org.opentrafficsim.core.definitions.Definitions;
46  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
47  import org.opentrafficsim.core.geometry.DirectedPoint;
48  import org.opentrafficsim.core.geometry.OtsPoint3d;
49  import org.opentrafficsim.core.gtu.Gtu;
50  import org.opentrafficsim.core.gtu.GtuCharacteristics;
51  import org.opentrafficsim.core.gtu.GtuException;
52  import org.opentrafficsim.core.gtu.GtuType;
53  import org.opentrafficsim.core.gtu.perception.DirectEgoPerception;
54  import org.opentrafficsim.core.gtu.perception.EgoPerception;
55  import org.opentrafficsim.core.gtu.perception.Perception;
56  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
57  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
58  import org.opentrafficsim.core.network.LateralDirectionality;
59  import org.opentrafficsim.core.network.LinkType;
60  import org.opentrafficsim.core.network.NetworkException;
61  import org.opentrafficsim.core.network.Node;
62  import org.opentrafficsim.core.network.Network;
63  import org.opentrafficsim.core.network.route.Route;
64  import org.opentrafficsim.core.parameters.ParameterFactoryByType;
65  import org.opentrafficsim.road.definitions.DefaultsRoadNl;
66  import org.opentrafficsim.road.gtu.colorer.GtuTypeColorer;
67  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBias;
68  import org.opentrafficsim.road.gtu.generator.GeneratorPositions.LaneBiases;
69  import org.opentrafficsim.road.gtu.generator.characteristics.DefaultLaneBasedGtuCharacteristicsGeneratorOd;
70  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristics;
71  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGtuCharacteristicsGeneratorOd;
72  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
73  import org.opentrafficsim.road.gtu.lane.VehicleModel;
74  import org.opentrafficsim.road.gtu.lane.perception.CategoricalLanePerception;
75  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
76  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
77  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
78  import org.opentrafficsim.road.gtu.lane.perception.categories.AnticipationTrafficPerception;
79  import org.opentrafficsim.road.gtu.lane.perception.categories.DirectInfrastructurePerception;
80  import org.opentrafficsim.road.gtu.lane.perception.categories.DirectIntersectionPerception;
81  import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
82  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.DirectNeighborsPerception;
83  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.HeadwayGtuType;
84  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
85  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGtu;
86  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneChange;
87  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneOperationalPlanBuilder;
88  import org.opentrafficsim.road.gtu.lane.plan.operational.SimpleOperationalPlan;
89  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
90  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlannerFactory;
91  import org.opentrafficsim.road.gtu.lane.tactical.following.AbstractIdm;
92  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
93  import org.opentrafficsim.road.gtu.lane.tactical.following.IdmPlus;
94  import org.opentrafficsim.road.gtu.lane.tactical.following.IdmPlusFactory;
95  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.AbstractIncentivesTacticalPlanner;
96  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.AccelerationIncentive;
97  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.DefaultLmrsPerceptionFactory;
98  import org.opentrafficsim.road.gtu.lane.tactical.lmrs.LmrsFactory;
99  import org.opentrafficsim.road.gtu.lane.tactical.util.CarFollowingUtil;
100 import org.opentrafficsim.road.gtu.lane.tactical.util.TrafficLightUtil;
101 import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.Cooperation;
102 import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.Desire;
103 import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.Incentive;
104 import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.LmrsParameters;
105 import org.opentrafficsim.road.gtu.lane.tactical.util.lmrs.LmrsUtil;
106 import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlannerFactory;
107 import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalRoutePlannerFactory;
108 import org.opentrafficsim.road.network.RoadNetwork;
109 import org.opentrafficsim.road.network.control.rampmetering.CycleTimeLightController;
110 import org.opentrafficsim.road.network.control.rampmetering.RampMetering;
111 import org.opentrafficsim.road.network.control.rampmetering.RampMeteringLightController;
112 import org.opentrafficsim.road.network.control.rampmetering.RampMeteringSwitch;
113 import org.opentrafficsim.road.network.control.rampmetering.RwsSwitch;
114 import org.opentrafficsim.road.network.factory.LaneFactory;
115 import org.opentrafficsim.road.network.lane.Lane;
116 import org.opentrafficsim.road.network.lane.LaneType;
117 import org.opentrafficsim.road.network.lane.Stripe;
118 import org.opentrafficsim.road.network.lane.Stripe.Type;
119 import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
120 import org.opentrafficsim.road.network.lane.object.detector.LoopDetector;
121 import org.opentrafficsim.road.network.lane.object.detector.SinkDetector;
122 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
123 import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
124 import org.opentrafficsim.road.network.speed.SpeedLimitProspect;
125 import org.opentrafficsim.road.od.Categorization;
126 import org.opentrafficsim.road.od.Category;
127 import org.opentrafficsim.road.od.Interpolation;
128 import org.opentrafficsim.road.od.OdApplier;
129 import org.opentrafficsim.road.od.OdMatrix;
130 import org.opentrafficsim.road.od.OdOptions;
131 import org.opentrafficsim.swing.script.AbstractSimulationScript;
132 
133 import nl.tudelft.simulation.jstats.distributions.DistNormal;
134 import nl.tudelft.simulation.jstats.streams.StreamInterface;
135 import picocli.CommandLine.Option;
136 
137 /**
138  * <p>
139  * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
140  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
141  * </p>
142  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
143  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
144  * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
145  */
146 public class RampMeteringDemo extends AbstractSimulationScript
147 {
148 
149     /** Controlled car GTU type id. */
150     private static final String CONTROLLED_CAR_ID = "controlledCar";
151 
152     /** Parameter factory. */
153     private ParameterFactoryByType parameterFactory = new ParameterFactoryByType();
154 
155     /** Ramp metering. */
156     @Option(names = {"-r", "--rampMetering"}, description = "Ramp metering on or off", defaultValue = "true")
157     private boolean rampMetering;
158 
159     /** Whether to generate output. */
160     @Option(names = "--output", description = "Generate output.", negatable = true, defaultValue = "false")
161     private boolean output;
162 
163     /** Accepted gap. */
164     @Option(names = "--acceptedGap", description = "Accepted gap.", defaultValue = "0.5s")
165     private Duration acceptedGap;
166 
167     /** Main demand. */
168     private FrequencyVector mainDemand;
169 
170     /** Main demand string. */
171     @Option(names = "--mainDemand", description = "Main demand in veh/h.", defaultValue = "2000,3000,3900,3900,3000")
172     private String mainDemandString;
173 
174     /** Ramp demand. */
175     private FrequencyVector rampDemand;
176 
177     /** Ramp demand string. */
178     @Option(names = "--rampDemand", description = "Ramp demand in veh/h.", defaultValue = "500,500,500,500,500")
179     private String rampDemandString;
180 
181     /** Demand time. */
182     private TimeVector demandTime;
183 
184     /** Demand time string. */
185     @Option(names = "--demandTime", description = "Demand time in min.", defaultValue = "0,10,40,50,70")
186     private String demandTimeString;
187 
188     /** Scenario. */
189     @Option(names = "--scenario", description = "Scenario name.", defaultValue = "test")
190     private String scenario;
191 
192     /** GTUs in simulation. */
193     private Map<String, Double> gtusInSimulation = new LinkedHashMap<>();
194 
195     /** Total travel time, accumulated. */
196     private double totalTravelTime = 0.0;
197 
198     /** Total travel time delay, accumulated. */
199     private double totalTravelTimeDelay = 0.0;
200 
201     /** Stores defintions such as GtuTypes. */
202     private Definitions definitions = new Definitions();
203 
204     /**
205      * Constructor.
206      */
207     protected RampMeteringDemo()
208     {
209         super("Ramp metering 1", "Ramp metering 2");
210     }
211 
212     /**
213      * @param args String[] command line arguments
214      * @throws Exception any exception
215      */
216     public static void main(final String[] args) throws Exception
217     {
218         RampMeteringDemo demo = new RampMeteringDemo();
219         CliUtil.changeOptionDefault(demo, "simulationTime", "4200s");
220         CliUtil.execute(demo, args);
221         demo.mainDemand =
222                 DoubleVector.instantiate(arrayFromString(demo.mainDemandString), FrequencyUnit.PER_HOUR, StorageType.DENSE);
223         demo.rampDemand =
224                 DoubleVector.instantiate(arrayFromString(demo.rampDemandString), FrequencyUnit.PER_HOUR, StorageType.DENSE);
225         demo.demandTime =
226                 DoubleVector.instantiate(arrayFromString(demo.demandTimeString), TimeUnit.BASE_MINUTE, StorageType.DENSE);
227         demo.start();
228     }
229 
230     /**
231      * Returns an array from a String.
232      * @param str String; string
233      * @return double[] array
234      */
235     private static double[] arrayFromString(final String str)
236     {
237         int n = 0;
238         for (String part : str.split(","))
239         {
240             n++;
241         }
242         double[] out = new double[n];
243         int i = 0;
244         for (String part : str.split(","))
245         {
246             out[i] = Double.valueOf(part);
247             i++;
248         }
249         return out;
250     }
251 
252     /** {@inheritDoc} */
253     @Override
254     protected RoadNetwork setupSimulation(final OtsSimulatorInterface sim) throws Exception
255     {
256         RoadNetwork network = new RoadNetwork("RampMetering", sim);
257         if (this.output)
258         {
259             network.addListener(this, Network.GTU_ADD_EVENT);
260             network.addListener(this, Network.GTU_REMOVE_EVENT);
261         }
262         GtuType car = DefaultsNl.CAR;
263         GtuType.registerTemplateSupplier(car, Defaults.NL);
264         GtuType controlledCar = new GtuType(CONTROLLED_CAR_ID, car);
265         this.definitions.add(GtuType.class, car);
266         this.definitions.add(GtuType.class, controlledCar);
267 
268         GtuColorer[] colorers =
269                 new GtuColorer[] {new IdGtuColorer(), new SpeedGtuColorer(new Speed(150, SpeedUnit.KM_PER_HOUR)),
270                         new AccelerationGtuColorer(Acceleration.instantiateSI(-6.0), Acceleration.instantiateSI(2)),
271                         new GtuTypeColorer().add(car).add(controlledCar)};
272         SwitchableGtuColorer colorer = new SwitchableGtuColorer(0, colorers);
273         setGtuColorer(colorer);
274 
275         // parameters
276         StreamInterface stream = sim.getModel().getStream("generation");
277         this.parameterFactory.addParameter(ParameterTypes.FSPEED, new DistNormal(stream, 123.7 / 120.0, 12.0 / 1200));
278 
279         Node nodeA = new Node(network, "A", new OtsPoint3d(0, 0), Direction.ZERO);
280         Node nodeB = new Node(network, "B", new OtsPoint3d(3000, 0), Direction.ZERO);
281         Node nodeC = new Node(network, "C", new OtsPoint3d(3250, 0), Direction.ZERO);
282         Node nodeD = new Node(network, "D", new OtsPoint3d(6000, 0), Direction.ZERO);
283         Node nodeE = new Node(network, "E", new OtsPoint3d(2000, -25), Direction.ZERO);
284         Node nodeF = new Node(network, "F", new OtsPoint3d(2750, 0.0), Direction.ZERO);
285 
286         LinkType freeway = DefaultsNl.FREEWAY;
287         LaneKeepingPolicy policy = LaneKeepingPolicy.KEEPRIGHT;
288         Length laneWidth = Length.instantiateSI(3.6);
289         LaneType freewayLane = DefaultsRoadNl.FREEWAY;
290         Speed speedLimit = new Speed(120, SpeedUnit.KM_PER_HOUR);
291         Speed rampSpeedLimit = new Speed(70, SpeedUnit.KM_PER_HOUR);
292         List<Lane> lanesAB = new LaneFactory(network, nodeA, nodeB, freeway, sim, policy, DefaultsNl.VEHICLE)
293                 .leftToRight(1.0, laneWidth, freewayLane, speedLimit).addLanes(Type.DASHED).getLanes();
294         List<Stripe> stripes = new ArrayList<>();
295         List<Lane> lanesBC = new LaneFactory(network, nodeB, nodeC, freeway, sim, policy, DefaultsNl.VEHICLE)
296                 .leftToRight(1.0, laneWidth, freewayLane, speedLimit).addLanes(stripes, Type.DASHED, Type.BLOCK).getLanes();
297         stripes.get(2).addPermeability(car, LateralDirectionality.LEFT); // prevent right lane changes over block stripe
298         List<Lane> lanesCD = new LaneFactory(network, nodeC, nodeD, freeway, sim, policy, DefaultsNl.VEHICLE)
299                 .leftToRight(1.0, laneWidth, freewayLane, speedLimit).addLanes(Type.DASHED).getLanes();
300         List<Lane> lanesEF = new LaneFactory(network, nodeE, nodeF, freeway, sim, policy, DefaultsNl.VEHICLE)
301                 .setOffsetEnd(laneWidth.times(1.5).neg()).leftToRight(0.5, laneWidth, freewayLane, rampSpeedLimit).addLanes()
302                 .getLanes();
303         List<Lane> lanesFB = new LaneFactory(network, nodeF, nodeB, freeway, sim, policy, DefaultsNl.VEHICLE)
304                 .setOffsetStart(laneWidth.times(1.5).neg()).setOffsetEnd(laneWidth.times(1.5).neg())
305                 .leftToRight(0.5, laneWidth, freewayLane, speedLimit).addLanes().getLanes();
306         for (Lane lane : lanesCD)
307         {
308             new SinkDetector(lane, lane.getLength().minus(Length.instantiateSI(50)), sim, DefaultsRoadNl.ROAD_USERS);
309         }
310         // detectors
311         Duration agg = Duration.instantiateSI(60.0);
312         // TODO: detector length affects occupancy, which length to use?
313         Length detectorLength = Length.ZERO;
314         LoopDetector det1 = new LoopDetector("1", lanesAB.get(0), Length.instantiateSI(2900), detectorLength,
315                 DefaultsRoadNl.LOOP_DETECTOR, sim, agg, LoopDetector.MEAN_SPEED, LoopDetector.OCCUPANCY);
316         LoopDetector det2 = new LoopDetector("2", lanesAB.get(1), Length.instantiateSI(2900), detectorLength,
317                 DefaultsRoadNl.LOOP_DETECTOR, sim, agg, LoopDetector.MEAN_SPEED, LoopDetector.OCCUPANCY);
318         LoopDetector det3 = new LoopDetector("3", lanesCD.get(0), Length.instantiateSI(100), detectorLength,
319                 DefaultsRoadNl.LOOP_DETECTOR, sim, agg, LoopDetector.MEAN_SPEED, LoopDetector.OCCUPANCY);
320         LoopDetector det4 = new LoopDetector("4", lanesCD.get(1), Length.instantiateSI(100), detectorLength,
321                 DefaultsRoadNl.LOOP_DETECTOR, sim, agg, LoopDetector.MEAN_SPEED, LoopDetector.OCCUPANCY);
322         List<LoopDetector> detectors12 = new ArrayList<>();
323         detectors12.add(det1);
324         detectors12.add(det2);
325         List<LoopDetector> detectors34 = new ArrayList<>();
326         detectors34.add(det3);
327         detectors34.add(det4);
328         if (this.rampMetering)
329         {
330             // traffic light
331             TrafficLight light = new TrafficLight("light", lanesEF.get(0), lanesEF.get(0).getLength(), sim);
332             List<TrafficLight> lightList = new ArrayList<>();
333             lightList.add(light);
334             // ramp metering
335             RampMeteringSwitch rampSwitch = new RwsSwitch(detectors12);
336             RampMeteringLightController rampLightController =
337                     new CycleTimeLightController(sim, lightList, DefaultsRoadNl.LOOP_DETECTOR);
338             new RampMetering(sim, rampSwitch, rampLightController);
339         }
340 
341         // OD
342         List<Node> origins = new ArrayList<>();
343         origins.add(nodeA);
344         origins.add(nodeE);
345         List<Node> destinations = new ArrayList<>();
346         destinations.add(nodeD);
347         Categorization categorization = new Categorization("cat", GtuType.class);// , Lane.class);
348         Interpolation globalInterpolation = Interpolation.LINEAR;
349         OdMatrix od = new OdMatrix("rampMetering", origins, destinations, categorization, this.demandTime, globalInterpolation);
350         // Category carCatMainLeft = new Category(categorization, car, lanesAB.get(0));
351         // Category carCatMainRight = new Category(categorization, car, lanesAB.get(1));
352         Category carCatRamp = new Category(categorization, car);// , lanesEB.get(0));
353         Category controlledCarCat = new Category(categorization, controlledCar);
354         // double fLeft = 0.6;
355         od.putDemandVector(nodeA, nodeD, carCatRamp, this.mainDemand, 0.6);
356         od.putDemandVector(nodeA, nodeD, controlledCarCat, this.mainDemand, 0.4);
357         // od.putDemandVector(nodeA, nodeD, carCatMainLeft, mainDemand, fLeft);
358         // od.putDemandVector(nodeA, nodeD, carCatMainRight, mainDemand, 1.0 - fLeft);
359         od.putDemandVector(nodeE, nodeD, carCatRamp, this.rampDemand, 0.6);
360         od.putDemandVector(nodeE, nodeD, controlledCarCat, this.rampDemand, 0.4);
361         OdOptions odOptions = new OdOptions();
362         DefaultLaneBasedGtuCharacteristicsGeneratorOd.Factory factory =
363                 new DefaultLaneBasedGtuCharacteristicsGeneratorOd.Factory(new LaneBasedStrategicalRoutePlannerFactory(
364                         new LmrsFactory(new IdmPlusFactory(stream), new DefaultLmrsPerceptionFactory())));
365         odOptions.set(OdOptions.GTU_TYPE, new ControlledStrategicalPlannerGenerator(factory.create()));
366         odOptions.set(OdOptions.INSTANT_LC, true);
367         odOptions.set(OdOptions.LANE_BIAS, new LaneBiases().addBias(car, LaneBias.WEAK_LEFT));
368         odOptions.set(OdOptions.NO_LC_DIST, Length.instantiateSI(300));
369         OdApplier.applyOd(network, od, odOptions, DefaultsRoadNl.ROAD_USERS);
370 
371         return network;
372     }
373 
374     /**
375      * Returns the parameter factory.
376      * @return ParameterFactoryByType; parameter factory
377      */
378     final ParameterFactoryByType getParameterFactory()
379     {
380         return this.parameterFactory;
381     }
382 
383     /** {@inheritDoc} */
384     @Override
385     public void notify(final Event event) throws RemoteException
386     {
387         if (event.getType().equals(Network.GTU_ADD_EVENT))
388         {
389             this.gtusInSimulation.put((String) event.getContent(), getSimulator().getSimulatorTime().si);
390         }
391         else if (event.getType().equals(Network.GTU_REMOVE_EVENT))
392         {
393             measureTravelTime((String) event.getContent());
394         }
395         else
396         {
397             super.notify(event);
398         }
399     }
400 
401     /**
402      * Adds travel time and delay for a single GTU.
403      * @param id String; id of the GTU
404      */
405     private void measureTravelTime(final String id)
406     {
407         double tt = getSimulator().getSimulatorTime().si - this.gtusInSimulation.get(id);
408         double x = getNetwork().getGTU(id).getOdometer().si;
409         // TODO: we assume 120km/h everywhere, including the slower ramps
410         double ttd = tt - (x / (120 / 3.6));
411         this.totalTravelTime += tt;
412         this.totalTravelTimeDelay += ttd;
413         this.gtusInSimulation.remove(id);
414     }
415 
416     /** {@inheritDoc} */
417     @Override
418     protected void onSimulationEnd()
419     {
420         if (this.output)
421         {
422             // detector data
423             String file = String.format("%s_%02d_detectors.csv", this.scenario, getSeed());
424             try
425             {
426                 CsvData.writeData(file, file + ".header", LoopDetector.asTablePeriodicData(getNetwork()));
427             }
428             catch (IOException | TextSerializationException exception)
429             {
430                 throw new RuntimeException(exception);
431             }
432 
433             // travel time data
434             for (Gtu gtu : getNetwork().getGTUs())
435             {
436                 measureTravelTime(gtu.getId());
437             }
438             Throw.when(!this.gtusInSimulation.isEmpty(), RuntimeException.class,
439                     "GTUs remain in simulation that are not measured.");
440             file = String.format("%s_%02d_time.txt", this.scenario, getSeed());
441             BufferedWriter bw = null;
442             try
443             {
444                 bw = CompressedFileWriter.create(file, false);
445                 bw.write(String.format("Total travel time: %.3fs", this.totalTravelTime));
446                 bw.newLine();
447                 bw.write(String.format("Total travel time delay: %.3fs", this.totalTravelTimeDelay));
448                 bw.close();
449             }
450             catch (IOException exception)
451             {
452                 throw new RuntimeException(exception);
453             }
454             finally
455             {
456                 try
457                 {
458                     if (bw != null)
459                     {
460                         bw.close();
461                     }
462                 }
463                 catch (IOException ex)
464                 {
465                     throw new RuntimeException(ex);
466                 }
467             }
468         }
469     }
470 
471     /**
472      * Strategical planner generator. This class can be used as input in {@code OdOptions} to generate the right models with
473      * different GTU types.
474      */
475     private class ControlledStrategicalPlannerGenerator implements LaneBasedGtuCharacteristicsGeneratorOd
476     {
477 
478         /** Default generator. */
479         private final DefaultLaneBasedGtuCharacteristicsGeneratorOd defaultGenerator;
480 
481         /** Controlled planner factory. */
482         private LaneBasedStrategicalPlannerFactory<?> controlledPlannerFactory;
483 
484         /**
485          * Constructor.
486          * @param defaultGenerator LaneBasedGtuCharacteristicsGenerator; generator for non-controlled GTU's
487          */
488         ControlledStrategicalPlannerGenerator(final DefaultLaneBasedGtuCharacteristicsGeneratorOd defaultGenerator)
489         {
490             this.defaultGenerator = defaultGenerator;
491             // anonymous factory to create tactical planners for controlled GTU's
492             LaneBasedTacticalPlannerFactory<?> tacticalPlannerFactory =
493                     new LaneBasedTacticalPlannerFactory<LaneBasedTacticalPlanner>()
494                     {
495                         @Override
496                         public Parameters getParameters() throws ParameterException
497                         {
498                             ParameterSet set = new ParameterSet();
499                             set.setDefaultParameter(ParameterTypes.PERCEPTION);
500                             set.setDefaultParameter(ParameterTypes.LOOKBACK);
501                             set.setDefaultParameter(ParameterTypes.LOOKAHEAD);
502                             set.setDefaultParameter(ParameterTypes.S0);
503                             set.setDefaultParameter(ParameterTypes.TMIN);
504                             set.setDefaultParameter(ParameterTypes.TMAX);
505                             set.setDefaultParameter(ParameterTypes.DT);
506                             set.setDefaultParameter(ParameterTypes.VCONG);
507                             set.setDefaultParameter(ParameterTypes.T0);
508                             set.setDefaultParameter(TrafficLightUtil.B_YELLOW);
509                             set.setDefaultParameters(LmrsParameters.class);
510                             set.setDefaultParameters(AbstractIdm.class);
511                             return set;
512                         }
513 
514                         @SuppressWarnings("synthetic-access")
515                         @Override
516                         public LaneBasedTacticalPlanner create(final LaneBasedGtu gtu) throws GtuException
517                         {
518                             // here the lateral control system is initiated
519                             ParameterSet settings = new ParameterSet();
520                             try
521                             {
522                                 // system operation settings
523                                 settings.setParameter(SyncAndAccept.SYNCTIME, Duration.instantiateSI(1.0));
524                                 settings.setParameter(SyncAndAccept.COOPTIME, Duration.instantiateSI(2.0));
525                                 // parameters used in car-following model for gap-acceptance
526                                 settings.setParameter(AbstractIdm.DELTA, 1.0);
527                                 settings.setParameter(ParameterTypes.S0, Length.instantiateSI(3.0));
528                                 settings.setParameter(ParameterTypes.A, Acceleration.instantiateSI(2.0));
529                                 settings.setParameter(ParameterTypes.B, Acceleration.instantiateSI(2.0));
530                                 settings.setParameter(ParameterTypes.T, RampMeteringDemo.this.acceptedGap);
531                                 settings.setParameter(ParameterTypes.FSPEED, 1.0);
532                                 settings.setParameter(ParameterTypes.B0, Acceleration.instantiateSI(0.5));
533                                 settings.setParameter(ParameterTypes.VCONG, new Speed(60, SpeedUnit.KM_PER_HOUR));
534                             }
535                             catch (ParameterException exception)
536                             {
537                                 throw new GtuException(exception);
538                             }
539                             return new ControlledTacticalPlanner(gtu, new SyncAndAccept(gtu, new IdmPlus(), settings));
540                         }
541                     };
542             // standard strategical planner factory using the tactical factory and the simulation-wide parameter factory
543             this.controlledPlannerFactory = new LaneBasedStrategicalRoutePlannerFactory(tacticalPlannerFactory,
544                     RampMeteringDemo.this.getParameterFactory());
545         }
546 
547         /** {@inheritDoc} */
548         @Override
549         public LaneBasedGtuCharacteristics draw(final Node origin, final Node destination, final Category category,
550                 final StreamInterface randomStream) throws GtuException
551         {
552             GtuType gtuType = category.get(GtuType.class);
553             // if GTU type is a controlled car, create characteristics for a controlled car
554             if (gtuType.equals(RampMeteringDemo.this.definitions.get(GtuType.class, CONTROLLED_CAR_ID)))
555             {
556                 Route route = null;
557                 VehicleModel vehicleModel = VehicleModel.MINMAX;
558                 GtuCharacteristics gtuCharacteristics =
559                         GtuType.defaultCharacteristics(gtuType, origin.getNetwork(), randomStream);
560                 return new LaneBasedGtuCharacteristics(gtuCharacteristics, this.controlledPlannerFactory, route, origin,
561                         destination, vehicleModel);
562             }
563             // otherwise generate default characteristics
564             return this.defaultGenerator.draw(origin, destination, category, randomStream);
565         }
566 
567     }
568 
569     /** Tactical planner. */
570     private static class ControlledTacticalPlanner extends AbstractIncentivesTacticalPlanner
571     {
572         /** */
573         private static final long serialVersionUID = 20190731L;
574 
575         /** Lane change system. */
576         private AutomaticLaneChangeSystem laneChangeSystem;
577 
578         /** Lane change status. */
579         private final LaneChange laneChange;
580 
581         /** Map that {@code getLaneChangeDesire} writes current desires in. This is not used here. */
582         private Map<Class<? extends Incentive>, Desire> dummyMap = new LinkedHashMap<>();
583 
584         /**
585          * Constructor.
586          * @param gtu LaneBasedGtu; gtu
587          * @param laneChangeSystem AutomaticLaneChangeSystem; lane change system
588          */
589         ControlledTacticalPlanner(final LaneBasedGtu gtu, final AutomaticLaneChangeSystem laneChangeSystem)
590         {
591             super(new IdmPlus(), gtu, generatePerception(gtu));
592             setDefaultIncentives();
593             this.laneChangeSystem = laneChangeSystem;
594             this.laneChange = Try.assign(() -> new LaneChange(gtu), "Parameter LCDUR is required.", GtuException.class);
595         }
596 
597         /**
598          * Helper method to create perception.
599          * @param gtu LaneBasedGtu; gtu
600          * @return LanePerception lane perception
601          */
602         private static LanePerception generatePerception(final LaneBasedGtu gtu)
603         {
604             CategoricalLanePerception perception = new CategoricalLanePerception(gtu);
605             perception.addPerceptionCategory(new DirectEgoPerception<LaneBasedGtu, Perception<LaneBasedGtu>>(perception));
606             perception.addPerceptionCategory(new DirectInfrastructurePerception(perception));
607             // TODO: perceived GTUs as first type
608             perception.addPerceptionCategory(new DirectNeighborsPerception(perception, HeadwayGtuType.WRAP));
609             perception.addPerceptionCategory(new AnticipationTrafficPerception(perception));
610             perception.addPerceptionCategory(new DirectIntersectionPerception(perception, HeadwayGtuType.WRAP));
611             return perception;
612         }
613 
614         /** {@inheritDoc} */
615         @Override
616         public OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
617                 throws OperationalPlanException, GtuException, NetworkException, ParameterException
618         {
619             // get some general input
620             Speed speed = getPerception().getPerceptionCategory(EgoPerception.class).getSpeed();
621             SpeedLimitProspect slp = getPerception().getPerceptionCategory(InfrastructurePerception.class)
622                     .getSpeedLimitProspect(RelativeLane.CURRENT);
623             SpeedLimitInfo sli = slp.getSpeedLimitInfo(Length.ZERO);
624 
625             // LMRS desire
626             Desire desire = LmrsUtil.getLaneChangeDesire(getGtu().getParameters(), getPerception(), getCarFollowingModel(),
627                     getMandatoryIncentives(), getVoluntaryIncentives(), this.dummyMap);
628 
629             // other vehicles respond to these 'interpreted' levels of lane change desire
630             getGtu().getParameters().setParameter(LmrsParameters.DLEFT, desire.getLeft());
631             getGtu().getParameters().setParameter(LmrsParameters.DRIGHT, desire.getRight());
632 
633             // car-following
634             Acceleration a = getGtu().getCarFollowingAcceleration();
635 
636             // cooperation
637             Acceleration aCoop = Cooperation.PASSIVE.cooperate(getPerception(), getGtu().getParameters(), sli,
638                     getCarFollowingModel(), LateralDirectionality.LEFT, desire);
639             a = Acceleration.min(a, aCoop);
640             aCoop = Cooperation.PASSIVE.cooperate(getPerception(), getGtu().getParameters(), sli, getCarFollowingModel(),
641                     LateralDirectionality.RIGHT, desire);
642             a = Acceleration.min(a, aCoop);
643 
644             // compose human plan
645             SimpleOperationalPlan simplePlan =
646                     new SimpleOperationalPlan(a, getGtu().getParameters().getParameter(ParameterTypes.DT));
647             for (AccelerationIncentive incentive : getAccelerationIncentives())
648             {
649                 incentive.accelerate(simplePlan, RelativeLane.CURRENT, Length.ZERO, getGtu(), getPerception(),
650                         getCarFollowingModel(), speed, getGtu().getParameters(), sli);
651             }
652 
653             // add lane change control
654             if (!this.laneChange.isChangingLane())
655             {
656                 double dFree = getGtu().getParameters().getParameter(LmrsParameters.DFREE);
657                 if (this.laneChangeSystem.initiatedLaneChange().isNone())
658                 {
659                     if (desire.leftIsLargerOrEqual() && desire.getLeft() > dFree)
660                     {
661                         this.laneChangeSystem.initiateLaneChange(LateralDirectionality.LEFT);
662                     }
663                     else if (desire.getRight() > dFree)
664                     {
665                         this.laneChangeSystem.initiateLaneChange(LateralDirectionality.RIGHT);
666                     }
667                 }
668                 else
669                 {
670                     if ((this.laneChangeSystem.initiatedLaneChange().isLeft() && desire.getLeft() < dFree)
671                             || (this.laneChangeSystem.initiatedLaneChange().isRight() && desire.getRight() < dFree))
672                     {
673                         this.laneChangeSystem.initiateLaneChange(LateralDirectionality.NONE);
674                     }
675                 }
676             }
677             simplePlan = this.laneChangeSystem.operate(simplePlan, getGtu().getParameters());
678             simplePlan.setTurnIndicator(getGtu());
679 
680             // create plan
681             return LaneOperationalPlanBuilder.buildPlanFromSimplePlan(getGtu(), startTime, simplePlan, this.laneChange);
682         }
683     }
684 
685     /** Interface allowing tactical planners to use an automatic lane change system. */
686     private interface AutomaticLaneChangeSystem
687     {
688 
689         /**
690          * Update operational plan with actions to change lane. This method should be called by the tactical planner always.
691          * @param simplePlan SimpleOperationalPlan; plan
692          * @param parameters Parameters; parameters
693          * @return SimpleOperationalPlan; adapted plan
694          * @throws OperationalPlanException if the system runs in to an error
695          * @throws ParameterException if a parameter is missing
696          */
697         SimpleOperationalPlan operate(SimpleOperationalPlan simplePlan, Parameters parameters)
698                 throws OperationalPlanException, ParameterException;
699 
700         /**
701          * Returns the direction in which the system was initiated to perform a lane change.
702          * @return LateralDirectionality; direction in which the system was initiated to perform a lane change, {@code NONE} if
703          *         none
704          */
705         LateralDirectionality initiatedLaneChange();
706 
707         /**
708          * Initiate a lane change.
709          * @param dir LateralDirectionality; direction, use {@code NONE} to cancel
710          */
711         void initiateLaneChange(LateralDirectionality dir);
712 
713     }
714 
715     /** Implementation of an automatic lane change system. */
716     private static class SyncAndAccept implements AutomaticLaneChangeSystem
717     {
718         /** Parameter of time after lane change command when the system will start synchronization. */
719         public static final ParameterTypeDuration SYNCTIME = new ParameterTypeDuration("tSync",
720                 "Time after which synchronization starts.", Duration.instantiateSI(1.0), NumericConstraint.POSITIVE);
721 
722         /** Parameter of time after lane change command when the system will start cooperation (indicator). */
723         public static final ParameterTypeDuration COOPTIME = new ParameterTypeDuration("tCoop",
724                 "Time after which cooperation starts (indicator).", Duration.instantiateSI(2.0), NumericConstraint.POSITIVE);
725 
726         /** GTU. */
727         private final LaneBasedGtu gtu;
728 
729         /** Car-following model for gap-acceptance. */
730         private final CarFollowingModel carFollowingModel;
731 
732         /** Parameters containing the system settings. */
733         private final Parameters settings;
734 
735         /** Initiated lane change direction. */
736         private LateralDirectionality direction = LateralDirectionality.NONE;
737 
738         /** Time when the lane change was initiated. */
739         private Time initiationTime;
740 
741         /**
742          * Constructor.
743          * @param gtu LaneBasedGtu; GTU
744          * @param carFollowingModel CarFollowingModel; car-following model
745          * @param settings Parameters; system settings
746          */
747         SyncAndAccept(final LaneBasedGtu gtu, final CarFollowingModel carFollowingModel, final Parameters settings)
748         {
749             this.gtu = gtu;
750             this.carFollowingModel = carFollowingModel;
751             this.settings = settings;
752         }
753 
754         /** {@inheritDoc} */
755         @Override
756         public SimpleOperationalPlan operate(final SimpleOperationalPlan simplePlan, final Parameters parameters)
757                 throws OperationalPlanException, ParameterException
758         {
759             // active?
760             if (this.direction.isNone())
761             {
762                 return simplePlan;
763             }
764 
765             // check gap
766             InfrastructurePerception infra =
767                     this.gtu.getTacticalPlanner().getPerception().getPerceptionCategory(InfrastructurePerception.class);
768             SpeedLimitInfo sli = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
769             NeighborsPerception neighbors =
770                     this.gtu.getTacticalPlanner().getPerception().getPerceptionCategory(NeighborsPerception.class);
771             if (infra.getLegalLaneChangePossibility(RelativeLane.CURRENT, this.direction).gt0()
772                     && !neighbors.isGtuAlongside(this.direction)
773                     && acceptGap(neighbors.getFirstFollowers(this.direction), sli, false)
774                     && acceptGap(neighbors.getFirstLeaders(this.direction), sli, true))
775             {
776                 // gaps accepted, start lane change
777                 SimpleOperationalPlan plan =
778                         new SimpleOperationalPlan(simplePlan.getAcceleration(), simplePlan.getDuration(), this.direction);
779                 this.direction = LateralDirectionality.NONE;
780                 this.initiationTime = null;
781                 return plan;
782             }
783 
784             // synchronization
785             Duration since = this.gtu.getSimulator().getSimulatorAbsTime().minus(this.initiationTime);
786             if (since.gt(this.settings.getParameter(SYNCTIME))
787                     || this.gtu.getSpeed().lt(this.settings.getParameter(ParameterTypes.VCONG)))
788             {
789                 PerceptionCollectable<HeadwayGtu, LaneBasedGtu> leaders =
790                         neighbors.getLeaders(new RelativeLane(this.direction, 1));
791                 if (!leaders.isEmpty())
792                 {
793                     HeadwayGtu leader = leaders.first();
794                     Acceleration a = CarFollowingUtil.followSingleLeader(this.carFollowingModel, this.settings,
795                             this.gtu.getSpeed(), sli, leader);
796                     a = Acceleration.max(a, this.settings.getParameter(ParameterTypes.B).neg());
797                     simplePlan.minimizeAcceleration(a);
798                 }
799             }
800 
801             // cooperation
802             if (since.gt(this.settings.getParameter(COOPTIME))
803                     || this.gtu.getSpeed().lt(this.settings.getParameter(ParameterTypes.VCONG)))
804             {
805                 if (this.direction.isLeft())
806                 {
807                     simplePlan.setIndicatorIntentLeft();
808                 }
809                 else
810                 {
811                     simplePlan.setIndicatorIntentRight();
812                 }
813             }
814 
815             // return
816             return simplePlan;
817         }
818 
819         /**
820          * Checks whether a gap can be accepted.
821          * @param neighbors Set&lt;HeadwayGtu&gt;; neighbors
822          * @param sli SpeedLimitInfo; speed limit info
823          * @param leaders boolean; whether we are dealing with leaders, or followers
824          * @return boolean; whether the gap is accepted
825          * @throws ParameterException if a parameter is not defined
826          */
827         private boolean acceptGap(final Set<HeadwayGtu> neighbors, final SpeedLimitInfo sli, final boolean leaders)
828                 throws ParameterException
829         {
830             for (HeadwayGtu neighbor : neighbors)
831             {
832                 Acceleration a = CarFollowingUtil.followSingleLeader(this.carFollowingModel, this.settings,
833                         leaders ? this.gtu.getSpeed() : neighbor.getSpeed(), sli, neighbor.getDistance(),
834                         leaders ? neighbor.getSpeed() : this.gtu.getSpeed());
835                 if (a.lt(this.settings.getParameter(ParameterTypes.B).neg()))
836                 {
837                     return false;
838                 }
839             }
840             return true;
841         }
842 
843         /** {@inheritDoc} */
844         @Override
845         public LateralDirectionality initiatedLaneChange()
846         {
847             return this.direction;
848         }
849 
850         /** {@inheritDoc} */
851         @Override
852         public void initiateLaneChange(final LateralDirectionality dir)
853         {
854             this.direction = dir;
855             if (!dir.isNone())
856             {
857                 this.initiationTime = this.gtu.getSimulator().getSimulatorAbsTime();
858             }
859             else
860             {
861                 this.initiationTime = null;
862             }
863         }
864     }
865 
866 }