View Javadoc
1   package org.opentrafficsim.demo.sdm;
2   
3   import java.awt.Color;
4   import java.io.BufferedWriter;
5   import java.io.IOException;
6   import java.io.OutputStreamWriter;
7   import java.util.ArrayList;
8   import java.util.List;
9   
10  import org.djunits.unit.FrequencyUnit;
11  import org.djunits.unit.SpeedUnit;
12  import org.djunits.unit.TimeUnit;
13  import org.djunits.value.StorageType;
14  import org.djunits.value.ValueException;
15  import org.djunits.value.vdouble.scalar.Acceleration;
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.FrequencyVector;
21  import org.djunits.value.vdouble.vector.TimeVector;
22  import org.djunits.value.vfloat.scalar.FloatDuration;
23  import org.opentrafficsim.base.compressedfiles.CompressionType;
24  import org.opentrafficsim.base.compressedfiles.Writer;
25  import org.opentrafficsim.core.animation.gtu.colorer.AccelerationGTUColorer;
26  import org.opentrafficsim.core.animation.gtu.colorer.SpeedGTUColorer;
27  import org.opentrafficsim.core.animation.gtu.colorer.SwitchableGTUColorer;
28  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
29  import org.opentrafficsim.core.geometry.OTSPoint3D;
30  import org.opentrafficsim.core.gtu.GTUDirectionality;
31  import org.opentrafficsim.core.gtu.GTUType;
32  import org.opentrafficsim.core.network.LinkType;
33  import org.opentrafficsim.core.network.NetworkException;
34  import org.opentrafficsim.core.network.OTSNetwork;
35  import org.opentrafficsim.core.network.OTSNode;
36  import org.opentrafficsim.core.perception.HistoryManagerDEVS;
37  import org.opentrafficsim.draw.graphs.ContourDataSource;
38  import org.opentrafficsim.draw.graphs.ContourPlotSpeed;
39  import org.opentrafficsim.draw.graphs.GraphPath;
40  import org.opentrafficsim.draw.graphs.road.GraphLaneUtil;
41  import org.opentrafficsim.kpi.sampling.KpiGtuDirectionality;
42  import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
43  import org.opentrafficsim.kpi.sampling.SamplingException;
44  import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
45  import org.opentrafficsim.kpi.sampling.Trajectory;
46  import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
47  import org.opentrafficsim.road.gtu.colorer.DesiredHeadwayColorer;
48  import org.opentrafficsim.road.gtu.colorer.DistractionColorer;
49  import org.opentrafficsim.road.gtu.colorer.FixedColor;
50  import org.opentrafficsim.road.gtu.colorer.SynchronizationColorer;
51  import org.opentrafficsim.road.gtu.colorer.TaskSaturationColorer;
52  import org.opentrafficsim.road.gtu.generator.od.DefaultGTUCharacteristicsGeneratorOD;
53  import org.opentrafficsim.road.gtu.generator.od.ODApplier;
54  import org.opentrafficsim.road.gtu.generator.od.ODOptions;
55  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
56  import org.opentrafficsim.road.gtu.lane.perception.mental.AdaptationSituationalAwareness;
57  import org.opentrafficsim.road.gtu.lane.perception.mental.ExponentialTask;
58  import org.opentrafficsim.road.gtu.lane.perception.mental.Task;
59  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.DefaultDistraction;
60  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.DistractionFactory;
61  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.StochasticDistractionModel;
62  import org.opentrafficsim.road.gtu.lane.perception.mental.sdm.TaskSupplier;
63  import org.opentrafficsim.road.gtu.strategical.od.Categorization;
64  import org.opentrafficsim.road.gtu.strategical.od.Category;
65  import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
66  import org.opentrafficsim.road.gtu.strategical.od.ODMatrix;
67  import org.opentrafficsim.road.network.factory.LaneFactory;
68  import org.opentrafficsim.road.network.lane.CrossSectionLink;
69  import org.opentrafficsim.road.network.lane.Lane;
70  import org.opentrafficsim.road.network.lane.LaneDirection;
71  import org.opentrafficsim.road.network.lane.LaneType;
72  import org.opentrafficsim.road.network.lane.Stripe.Permeable;
73  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
74  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
75  import org.opentrafficsim.road.network.sampling.LaneData;
76  import org.opentrafficsim.road.network.sampling.RoadSampler;
77  import org.opentrafficsim.road.network.sampling.data.TimeToCollision;
78  import org.opentrafficsim.swing.gui.OTSSimulationApplication;
79  import org.opentrafficsim.swing.script.AbstractSimulationScript;
80  
81  import nl.tudelft.simulation.dsol.swing.gui.TablePanel;
82  import nl.tudelft.simulation.jstats.streams.StreamInterface;
83  
84  /**
85   * Script to run a simulation with the Stochastic Distraction Model.
86   * <p>
87   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
88   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
89   * <p>
90   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 5 nov. 2018 <br>
91   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
92   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
93   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
94   */
95  public class SdmSimulation extends AbstractSimulationScript
96  {
97      /** Network. */
98      private OTSNetwork network;
99  
100     /** Sampler for statistics. */
101     private RoadSampler sampler;
102 
103     /** Time to collision data type. */
104     private final TimeToCollision ttc = new TimeToCollision();
105 
106     /** Space time regions to sample traffic on. */
107     private final List<SpaceTimeRegion> regions = new ArrayList<>();
108 
109     /**
110      * Constructor.
111      * @param properties String[]; command line arguments
112      */
113     protected SdmSimulation(final String[] properties)
114     {
115         super("SDM simulation", "Simulations using the Stochastic Distraction Model", properties);
116         // set GTU colorers to use
117         setGtuColorer(SwitchableGTUColorer.builder().addActiveColorer(new FixedColor(Color.BLUE, "Blue"))
118                 .addColorer(new SynchronizationColorer())
119                 .addColorer(new DistractionColorer(DefaultDistraction.ANSWERING_CELL_PHONE, DefaultDistraction.CONVERSING,
120                         DefaultDistraction.MANIPULATING_AUDIO_CONTROLS, DefaultDistraction.EXTERNAL_DISTRACTION))
121                 .addColorer(new SpeedGTUColorer(new Speed(150, SpeedUnit.KM_PER_HOUR)))
122                 .addColorer(new AccelerationGTUColorer(Acceleration.createSI(-6.0), Acceleration.createSI(2)))
123                 .addColorer(new DesiredHeadwayColorer(Duration.createSI(0.56), Duration.createSI(2.4)))
124                 .addColorer(new TaskSaturationColorer()).build());
125     }
126 
127     /**
128      * Start a simulation.
129      * @param args String...; command line arguments
130      */
131     public static void main(final String... args)
132     {
133         new SdmSimulation(args).start();
134     }
135 
136     /** {@inheritDoc} */
137     @Override
138     protected void setDefaultProperties()
139     {
140         // output
141         setProperty("outputFile", "output.txt");
142         setProperty("output", true);
143         setProperty("plots", false);
144 
145         // traffic
146         setProperty("startTime", "0");
147         setProperty("warmupTime", "300");
148         setProperty("simulationTime", "3900");
149         setProperty("truckFraction", 0.05);
150         setProperty("leftDemand", 3600);
151         setProperty("rightDemand", 3600);
152         setProperty("startDemandFactor", 0.45);
153 
154         // distractions
155         setProperty("allowMultiTasking", "false");
156         // 1=TALKING_CELL_PHONE,12=CONVERSING,5=MANIPULATING_AUDIO_CONTROLS,16=EXTERNAL_DISTRACTION
157         setProperty("distractions", "1,12,5,16");
158         setProperty("phoneInit", 1.0);
159         setProperty("phoneFinal", 0.3);
160         setProperty("phoneTau", 10.0);
161         setProperty("conversing", 0.3);
162         setProperty("audio", 0.3);
163         setProperty("externalBase", 0.2);
164         setProperty("externalVar", 0.3);
165 
166         // basic behavioral parameters
167         setProperty("DT", 0.5);
168         setProperty("TMIN", 0.56);
169         setProperty("TMAX", 1.2);
170         setProperty("A_CAR", 1.25);
171         setProperty("A_TRUCK", 0.8);
172         setProperty("B", 2.09);
173 
174         // human factors
175         setProperty("SA_MIN", 0.5);
176         setProperty("SA_MAX", 1.0);
177         setProperty("TR_MAX", 2.0);
178         setProperty("TC", 1.0);
179         setProperty("TS_CRIT", 0.8);
180         setProperty("TS_MAX", 2.0);
181         setProperty("BETA_T", 1.0);
182 
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     protected OTSNetwork setupSimulation(final OTSSimulatorInterface sim) throws Exception
188     {
189         // manager of historic information to allow a reaction time
190         sim.getReplication().setHistoryManager(
191                 new HistoryManagerDEVS(sim, AdaptationSituationalAwareness.TR_MAX.getDefaultValue(), Duration.createSI(10.0)));
192 
193         // Network
194         this.network = new OTSNetwork("SDM");
195         OTSPoint3D pointA = new OTSPoint3D(0.0, 0.0);
196         OTSPoint3D pointB = new OTSPoint3D(0.0, -20.0);
197         OTSPoint3D pointC = new OTSPoint3D(1600.0, -20.0);
198         OTSPoint3D pointD = new OTSPoint3D(2000.0, 0.0);
199         OTSPoint3D pointE = new OTSPoint3D(2500.0, 0.0);
200         OTSPoint3D pointF = new OTSPoint3D(3500.0, 0.0);
201         OTSNode nodeA = new OTSNode(this.network, "A", pointA);
202         OTSNode nodeB = new OTSNode(this.network, "B", pointB);
203         OTSNode nodeC = new OTSNode(this.network, "C", pointC);
204         OTSNode nodeD = new OTSNode(this.network, "D", pointD);
205         OTSNode nodeE = new OTSNode(this.network, "E", pointE);
206         OTSNode nodeF = new OTSNode(this.network, "F", pointF);
207         LinkType type = LinkType.FREEWAY;
208         LaneKeepingPolicy policy = LaneKeepingPolicy.KEEPRIGHT;
209         Length laneWidth = Length.createSI(3.5);
210         LaneType laneType = LaneType.FREEWAY;
211         Speed speedLimit = new Speed(120.0, SpeedUnit.KM_PER_HOUR);
212         List<Lane> allLanes = new ArrayList<>();
213         allLanes.addAll(new LaneFactory(this.network, nodeA, nodeD, type, sim, policy)
214                 .leftToRight(2.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH).getLanes());
215         allLanes.addAll(new LaneFactory(this.network, nodeB, nodeC, type, sim, policy)
216                 .leftToRight(0.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH).getLanes());
217         allLanes.addAll(new LaneFactory(this.network, nodeC, nodeD, type, sim, policy)
218                 .leftToRight(0.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH).getLanes());
219         allLanes.addAll(
220                 new LaneFactory(this.network, nodeD, nodeE, type, sim, policy).leftToRight(2.0, laneWidth, laneType, speedLimit)
221                         .addLanes(Permeable.BOTH, Permeable.BOTH, Permeable.BOTH).getLanes());
222         List<Lane> lanesEF = new LaneFactory(this.network, nodeE, nodeF, type, sim, policy)
223                 .leftToRight(1.0, laneWidth, laneType, speedLimit).addLanes(Permeable.BOTH, Permeable.BOTH).getLanes();
224         allLanes.addAll(lanesEF);
225         for (Lane lane : lanesEF)
226         {
227             new SinkSensor(lane, lane.getLength().minus(Length.createSI(50)), sim);
228         }
229 
230         // OD
231         List<OTSNode> origins = new ArrayList<>();
232         origins.add(nodeA);
233         origins.add(nodeB);
234         List<OTSNode> destinations = new ArrayList<>();
235         destinations.add(nodeF);
236         double wut = sim.getReplication().getTreatment().getWarmupPeriod().si;
237         double rl = sim.getReplication().getTreatment().getRunLength().si;
238         TimeVector timeVector =
239                 new TimeVector(new double[] { 0.0, wut, wut + (rl - wut) * 0.5, rl }, TimeUnit.BASE, StorageType.DENSE);
240         Interpolation interpolation = Interpolation.LINEAR;
241         Categorization categorization = new Categorization("GTU categorization", GTUType.class);
242         ODMatrix odMatrix = new ODMatrix("OD", origins, destinations, categorization, timeVector, interpolation);
243         Category carCategory = new Category(categorization, GTUType.CAR);
244         Category truCategory = new Category(categorization, GTUType.TRUCK);
245         double f1 = getDoubleProperty("truckFraction");
246         double f2 = 1.0 - f1;
247         double left2 = getDoubleProperty("leftDemand");
248         double right2 = getDoubleProperty("rightDemand");
249         double startDemandFactor = getDoubleProperty("startDemandFactor");
250         double left1 = left2 * startDemandFactor;
251         double right1 = right2 * startDemandFactor;
252         odMatrix.putDemandVector(nodeA, nodeF, carCategory, freq(new double[] { f2 * left1, f2 * left1, f2 * left2, 0.0 }));
253         odMatrix.putDemandVector(nodeA, nodeF, truCategory, freq(new double[] { f1 * left1, f1 * left1, f1 * left2, 0.0 }));
254         odMatrix.putDemandVector(nodeB, nodeF, carCategory, freq(new double[] { f2 * right1, f2 * right1, f2 * right2, 0.0 }));
255         odMatrix.putDemandVector(nodeB, nodeF, truCategory, freq(new double[] { f1 * right1, f1 * right1, f1 * right2, 0.0 }));
256         ODOptions odOptions = new ODOptions().set(ODOptions.NO_LC_DIST, Length.createSI(200)).set(ODOptions.GTU_TYPE,
257                 new DefaultGTUCharacteristicsGeneratorOD(
258                         new SdmStrategicalPlannerFactory(sim.getReplication().getStream("generation"), this)));
259         ODApplier.applyOD(this.network, odMatrix, sim, odOptions);
260 
261         // setup the SDM
262         DistractionFactory distFactory = new DistractionFactory(sim.getReplication().getStream("default"));
263         for (String distraction : getProperty("distractions").split(","))
264         {
265             DefaultDistraction dist = DefaultDistraction.values()[Integer.parseInt(distraction) - 1];
266             distFactory.addDistraction(dist, getTaskSupplier(dist, sim.getReplication().getStream("default")));
267         }
268         new StochasticDistractionModel(getBooleanProperty("allowMultiTasking"), distFactory.build(), sim, this.network);
269 
270         // sampler
271         if (getBooleanProperty("output"))
272         {
273             this.sampler = new RoadSampler(sim);
274             Time start = new Time(0.05, TimeUnit.BASE_HOUR);
275             Time end = new Time(1.05, TimeUnit.BASE_HOUR);
276             for (Lane lane : allLanes)
277             {
278                 KpiLaneDirection kpiLane = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS);
279                 SpaceTimeRegion region = new SpaceTimeRegion(kpiLane, Length.ZERO, lane.getLength(), start, end);
280                 this.regions.add(region);
281                 this.sampler.registerSpaceTimeRegion(region);
282             }
283             this.sampler.registerExtendedDataType(this.ttc);
284         }
285 
286         // return network
287         return this.network;
288     }
289 
290     /** {@inheritDoc} */
291     @Override
292     protected void addTabs(final OTSSimulatorInterface sim, final OTSSimulationApplication<?> animation)
293     {
294         if (!getBooleanProperty("output") || !getBooleanProperty("plots"))
295         {
296             return;
297         }
298         try
299         {
300             TablePanel charts = new TablePanel(2, 2);
301             GraphPath<KpiLaneDirection> path1 = GraphLaneUtil.createPath("Left road, left lane",
302                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("AD")).getCrossSectionElement("Lane 1"),
303                             GTUDirectionality.DIR_PLUS));
304             GraphPath<KpiLaneDirection> path2 = GraphLaneUtil.createPath("Left road, right lane",
305                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("AD")).getCrossSectionElement("Lane 2"),
306                             GTUDirectionality.DIR_PLUS));
307             GraphPath<KpiLaneDirection> path3 = GraphLaneUtil.createPath("Right road, left lane",
308                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("BC")).getCrossSectionElement("Lane 1"),
309                             GTUDirectionality.DIR_PLUS));
310             GraphPath<KpiLaneDirection> path4 = GraphLaneUtil.createPath("Right road, right lane",
311                     new LaneDirection((Lane) ((CrossSectionLink) this.network.getLink("BC")).getCrossSectionElement("Lane 2"),
312                             GTUDirectionality.DIR_PLUS));
313             charts.setCell(new ContourPlotSpeed("Left road, left lane", sim, new ContourDataSource<>(this.sampler, path1))
314                     .getContentPane(), 0, 0);
315             charts.setCell(new ContourPlotSpeed("Left road, right lane", sim, new ContourDataSource<>(this.sampler, path2))
316                     .getContentPane(), 1, 0);
317             charts.setCell(new ContourPlotSpeed("Right road, left lane", sim, new ContourDataSource<>(this.sampler, path3))
318                     .getContentPane(), 0, 1);
319             charts.setCell(new ContourPlotSpeed("Right road, right lane", sim, new ContourDataSource<>(this.sampler, path4))
320                     .getContentPane(), 1, 1);
321             animation.getAnimationPanel().getTabbedPane().addTab(animation.getAnimationPanel().getTabbedPane().getTabCount(),
322                     "statistics ", charts);
323         }
324         catch (NetworkException exception)
325         {
326             throw new RuntimeException(exception);
327         }
328     }
329 
330     /**
331      * Creates a frequency vector.
332      * @param array double[]; array in veh/h
333      * @return FrequencyVector; frequency vector
334      * @throws ValueException on problem
335      */
336     private FrequencyVector freq(final double[] array) throws ValueException
337     {
338         return new FrequencyVector(array, FrequencyUnit.PER_HOUR, StorageType.DENSE);
339     }
340 
341     /**
342      * Returns a task supplier for a distraction. These are specific to the SDM simulations.
343      * @param distraction DefaultDistraction; distraction
344      * @param stream StreamInterface; random number stream for randomized aspects of the distraction
345      * @return TaskSupplier; task supplier
346      */
347     private TaskSupplier getTaskSupplier(final DefaultDistraction distraction, final StreamInterface stream)
348     {
349         switch (distraction)
350         {
351             case TALKING_CELL_PHONE:
352             {
353                 return new TaskSupplier()
354                 {
355                     /** {@inheritDoc} */
356                     @Override
357                     public Task getTask(final LaneBasedGTU gtu)
358                     {
359                         return new ExponentialTask(distraction.getId(), getDoubleProperty("phoneInit"),
360                                 getDoubleProperty("phoneFinal"), getDoubleProperty("phoneTau"), gtu.getSimulator());
361                     }
362                 };
363             }
364             case CONVERSING:
365             {
366                 return new TaskSupplier.Constant(distraction.getId(), getDoubleProperty("conversing"));
367             }
368             case MANIPULATING_AUDIO_CONTROLS:
369             {
370                 return new TaskSupplier.Constant(distraction.getId(), getDoubleProperty("audio"));
371             }
372             case EXTERNAL_DISTRACTION:
373             {
374                 return new TaskSupplier.Constant(distraction.getId(),
375                         getDoubleProperty("externalBase") + getDoubleProperty("externalVar") * stream.nextDouble());
376             }
377             default:
378                 throw new IllegalArgumentException("Distraction " + distraction + " is not recognized.");
379         }
380     }
381 
382     /** {@inheritDoc} */
383     @Override
384     protected void onSimulationEnd()
385     {
386         if (getBooleanProperty("output"))
387         {
388             Length preDetectorPosition = Length.createSI(400.0); // on link DE, upstream of lane drop
389             Length postDetectorPosition = Length.createSI(100.0); // on link EF, downstream of lane drop
390             double tts = 0.0;
391             List<Float> ttcList = new ArrayList<>();
392             List<Float> decList = new ArrayList<>();
393             int[] counts = new int[60];
394             int[] speedCounts = new int[60];
395             double[] speedSum = new double[60];
396             for (SpaceTimeRegion region : this.regions)
397             {
398                 TrajectoryGroup<?> trajectoryGroup =
399                         this.sampler.getTrajectoryGroup(region.getLaneDirection()).getTrajectoryGroup(region.getStartPosition(),
400                                 region.getEndPosition(), region.getStartTime(), region.getEndTime());
401                 for (Trajectory<?> trajectory : trajectoryGroup)
402                 {
403                     try
404                     {
405                         tts += trajectory.getTotalDuration().si;
406                         for (FloatDuration ttcVal : trajectory.getExtendedData(this.ttc))
407                         {
408                             if (!Float.isNaN(ttcVal.si) && ttcVal.si < 20)
409                             {
410                                 ttcList.add(ttcVal.si);
411                             }
412                         }
413                         for (float decVal : trajectory.getA())
414                         {
415                             if (decVal < -2.0)
416                             {
417                                 decList.add(decVal);
418                             }
419                         }
420                         if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("DE") && trajectory.size() > 1
421                                 && trajectory.getX(0) < preDetectorPosition.si
422                                 && trajectory.getX(trajectory.size() - 1) > preDetectorPosition.si)
423                         {
424                             double t = trajectory.getTimeAtPosition(postDetectorPosition).si - region.getStartTime().si;
425                             double v = trajectory.getSpeedAtPosition(postDetectorPosition).si;
426                             speedCounts[(int) (t / 60.0)]++;
427                             speedSum[(int) (t / 60.0)] += v;
428                         }
429                         if (region.getLaneDirection().getLaneData().getLinkData().getId().equals("EF") && trajectory.size() > 1
430                                 && trajectory.getX(0) < postDetectorPosition.si
431                                 && trajectory.getX(trajectory.size() - 1) > postDetectorPosition.si)
432                         {
433                             double t = trajectory.getTimeAtPosition(postDetectorPosition).si - region.getStartTime().si;
434                             counts[(int) (t / 60.0)]++;
435                         }
436                     }
437                     catch (SamplingException exception)
438                     {
439                         throw new RuntimeException(
440                                 "Unexpected exception: TimeToCollission not available or index out of bounds.", exception);
441                     }
442                 }
443             }
444             double qMax = 0;
445             for (int i = 0; i < counts.length - 4; i++)
446             {
447                 int q = 0;
448                 for (int j = i; j < i + 5; j++)
449                 {
450                     q += counts[j];
451                 }
452                 qMax = qMax > q ? qMax : q;
453             }
454             qMax *= 12; // twelve periods of 5min in an hour
455             int n = 0;
456             int countSum = 0;
457             for (int i = 0; i < counts.length; i++)
458             {
459                 double v = speedSum[i] / speedCounts[i];
460                 if (v < 80 / 3.6)
461                 {
462                     countSum += counts[i];
463                     n++;
464                 }
465             }
466             double qSat = n == 0 ? Double.NaN : 60.0 * countSum / n; // per min -> per hour
467             BufferedWriter bw;
468             try
469             {
470                 bw = new BufferedWriter(
471                         new OutputStreamWriter(Writer.createOutputStream(getProperty("outputFile"), CompressionType.ZIP)));
472                 bw.write(String.format("total time spent [s]: %.0f", tts));
473                 bw.newLine();
474                 bw.write(String.format("maximum flow [veh/h]: %.3f", qMax));
475                 bw.newLine();
476                 bw.write(String.format("saturation flow [veh/h]: %.3f", qSat));
477                 bw.newLine();
478                 bw.write(String.format("time to collision [s]: %s", ttcList));
479                 bw.newLine();
480                 bw.write(String.format("strong decelerations [m/s^2]: %s", decList));
481                 bw.close();
482             }
483             catch (IOException exception)
484             {
485                 throw new RuntimeException(exception);
486             }
487         }
488     }
489 
490 }