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