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