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