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