View Javadoc
1   package org.opentrafficsim.road.gtu.generator;
2   
3   import java.util.LinkedHashMap;
4   import java.util.Map;
5   import java.util.PriorityQueue;
6   import java.util.Queue;
7   import java.util.Set;
8   import java.util.SortedMap;
9   import java.util.TreeMap;
10  
11  import javax.naming.NamingException;
12  
13  import org.djunits.value.ValueRuntimeException;
14  import org.djunits.value.storage.StorageType;
15  import org.djunits.value.vdouble.scalar.Frequency;
16  import org.djunits.value.vdouble.scalar.Time;
17  import org.djunits.value.vdouble.vector.FrequencyVector;
18  import org.djunits.value.vdouble.vector.TimeVector;
19  import org.djunits.value.vdouble.vector.base.DoubleVector;
20  import org.djutils.exceptions.Throw;
21  import org.opentrafficsim.base.WeightedMeanAndSum;
22  import org.opentrafficsim.base.parameters.ParameterException;
23  import org.opentrafficsim.core.distributions.ProbabilityException;
24  import org.opentrafficsim.core.geometry.OTSGeometryException;
25  import org.opentrafficsim.core.gtu.GTUException;
26  import org.opentrafficsim.core.gtu.GTUType;
27  import org.opentrafficsim.core.network.NetworkException;
28  import org.opentrafficsim.core.network.Node;
29  import org.opentrafficsim.road.gtu.generator.characteristics.LaneBasedGTUCharacteristicsGenerator;
30  import org.opentrafficsim.road.gtu.generator.od.GTUCharacteristicsGeneratorOD;
31  import org.opentrafficsim.road.gtu.strategical.od.Category;
32  import org.opentrafficsim.road.gtu.strategical.od.Interpolation;
33  import org.opentrafficsim.road.network.lane.LaneDirection;
34  
35  import nl.tudelft.simulation.dsol.SimRuntimeException;
36  import nl.tudelft.simulation.dsol.logger.SimLogger;
37  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
38  import nl.tudelft.simulation.jstats.streams.StreamInterface;
39  
40  /**
41   * Connects with a lane-based GTU generator to disable it over some time and generate a platoon instead. Platoons are added as:
42   * {@code Platoons.ofCategory(...).addPlatoon(...).addGtu(...).addGtu(...).addPlatoon(...).addGtu(...).addGtu(...).start();}.
43   * Method {@code addGtu(...)} may only determine a generation time if other info is set by {@code fixInfo(...)}.<br>
44   * <br>
45   * This class may be used with a {@code LaneBasedGTUCharacteristicsGenerator} or {@code GTUCharacteristicsGeneratorOD}. Use
46   * {@code ofGtuType()} or {@code ofCategory()} respectively.
47   * <p>
48   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
49   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
50   * <p>
51   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 sep. 2018 <br>
52   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
53   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
54   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
55   * @param <T> type of demand category, typically a Category in an ODMatrix or a GTUType
56   */
57  public abstract class Platoons<T>
58  {
59  
60      /** GTU generator to disable. */
61      private LaneBasedGTUGenerator gen;
62  
63      /** Simulator. */
64      private final DEVSSimulatorInterface.TimeDoubleUnit simulator;
65  
66      /** Position to generate the GTU's at. */
67      private final Set<LaneDirection> position;
68  
69      /** Queue of GTU information to generate GTU's with. */
70      private final Queue<PlatoonGtu<T>> queue = new PriorityQueue<>();
71  
72      /** Map of platoon start and end times. */
73      private final SortedMap<Time, Time> periods = new TreeMap<>();
74  
75      /** Start time of current platoon. */
76      private Time startTime;
77  
78      /** End time of current platoon. */
79      private Time endTime;
80  
81      /** Origin to use on added GTU. */
82      private Node fixedOrigin;
83  
84      /** Destination to use on added GTU. */
85      private Node fixedDestination;
86  
87      /** Category to use on added GTU. */
88      private T fixedCategory;
89  
90      /** Number of GTUs that will be generated. */
91      private Map<T, Integer> numberOfGtus = new LinkedHashMap<>();
92  
93      /** Whether the Platoons was started, after which nothing may be added. */
94      private boolean started = false;
95  
96      /**
97       * Constructor.
98       * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
99       * @param position Set&lt;LaneDirection&gt;; position
100      */
101     private Platoons(final DEVSSimulatorInterface.TimeDoubleUnit simulator, final Set<LaneDirection> position)
102     {
103         this.simulator = simulator;
104         this.position = position;
105     }
106 
107     /**
108      * Creates a {@code Platoon&lt;Category&gt;} instance for platoons.
109      * @param characteristics GTUCharacteristicsGeneratorOD; characteristics generator
110      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
111      * @param stream StreamInterface; random number stream
112      * @param position Set&lt;LaneDirection&gt;; position
113      * @return Platoons&lt;Category&gt;; platoons based on OD
114      */
115     @SuppressWarnings("synthetic-access")
116     public static Platoons<Category> ofCategory(final GTUCharacteristicsGeneratorOD characteristics,
117             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final StreamInterface stream,
118             final Set<LaneDirection> position)
119     {
120         return new Platoons<Category>(simulator, position)
121         {
122             /** Characteristics generator OD based. */
123             private final GTUCharacteristicsGeneratorOD characteristicsOD = characteristics;
124 
125             /** Random number stream. */
126             private final StreamInterface strm = stream;
127 
128             /** {@inheritDoc} */
129             @Override
130             protected void placeGtu(final PlatoonGtu<Category> platoonGtu) throws SimRuntimeException, NamingException,
131                     GTUException, NetworkException, OTSGeometryException, ProbabilityException, ParameterException
132             {
133                 getGenerator().queueGtu(this.characteristicsOD.draw(platoonGtu.getOrigin(), platoonGtu.getDestination(),
134                         platoonGtu.getCategory(), this.strm), getPosition());
135                 start();
136             }
137         };
138     }
139 
140     /**
141      * Creates a {@code Platoon&lt;GTUType&gt;} instance for platoons.
142      * @param characteristics LaneBasedGTUCharacteristicsGenerator; characteristics generator
143      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
144      * @param stream StreamInterface; random number stream
145      * @param position Set&lt;LaneDirection&gt;; position
146      * @return Platoons&lt;GTUType&gt;; platoons based on OD
147      */
148     @SuppressWarnings("synthetic-access")
149     public static Platoons<GTUType> ofGtuType(final LaneBasedGTUCharacteristicsGenerator characteristics,
150             final DEVSSimulatorInterface.TimeDoubleUnit simulator, final StreamInterface stream,
151             final Set<LaneDirection> position)
152     {
153         return new Platoons<GTUType>(simulator, position)
154         {
155             /** Characteristics generator. */
156             private final LaneBasedGTUCharacteristicsGenerator chrctrstcs = characteristics;
157 
158             /** {@inheritDoc} */
159             @Override
160             protected void placeGtu(final PlatoonGtu<GTUType> platoonGtu) throws SimRuntimeException, NamingException,
161                     GTUException, NetworkException, OTSGeometryException, ProbabilityException, ParameterException
162             {
163                 // we actually do nothing with the platoonGtu here
164                 getGenerator().queueGtu(this.chrctrstcs.draw(), getPosition());
165                 start();
166             }
167         };
168     }
169 
170     /**
171      * Add a platoon. The generator is disabled during the provided time frame. Individual GTU's should be added using
172      * {@code addGtu}.
173      * @param start Time; start time
174      * @param end Time; end time
175      * @return Platoons&lt;T&gt;; for method chaining
176      * @throws SimRuntimeException on exception
177      */
178     public Platoons<T> addPlatoon(final Time start, final Time end) throws SimRuntimeException
179     {
180         Throw.when(this.started, IllegalStateException.class, "Cannot add a platoon after the Platoons was started.");
181         Throw.whenNull(start, "Start may not be null.");
182         Throw.whenNull(end, "End may not be null.");
183         this.startTime = start;
184         this.endTime = end;
185         this.periods.put(start, end);
186         return this;
187     }
188 
189     /**
190      * Fix all info except time for GTU's added hereafter.
191      * @param origin Node; origin
192      * @param destination Node; destination
193      * @param category T; category
194      * @return Platoons&lt;T&gt;; for method chaining
195      */
196     public Platoons<T> fixInfo(final Node origin, final Node destination, final T category)
197     {
198         this.fixedOrigin = origin;
199         this.fixedDestination = destination;
200         this.fixedCategory = category;
201         return this;
202     }
203 
204     /**
205      * Add GTU to the current platoon. Per platoon, GTU may be given in any order. This method uses info set with
206      * {@code fixInfo}.
207      * @param time Time; time of generation
208      * @return Platoons&lt;T&gt;; for method chaining
209      * @throws IllegalStateException if no fixed info was set using {@code fixInfo}
210      */
211     public Platoons<T> addGtu(final Time time)
212     {
213         Throw.when(this.fixedOrigin == null || this.fixedDestination == null || this.fixedCategory == null,
214                 IllegalStateException.class, "When using addGtu(Time), used fixInfo(...) before to set other info.");
215         return addGtu(time, this.fixedOrigin, this.fixedDestination, this.fixedCategory);
216     }
217 
218     /**
219      * Add GTU to the current platoon. Per platoon, GTU may be given in any order.
220      * @param time Time; time of generation
221      * @param origin Node; origin
222      * @param destination Node; destination
223      * @param category T; category
224      * @return Platoons&lt;T&gt;; for method chaining
225      * @throws IllegalStateException if no platoon was started or time is outside of the platoon time range
226      */
227     public Platoons<T> addGtu(final Time time, final Node origin, final Node destination, final T category)
228     {
229         Throw.when(this.started, IllegalStateException.class, "Cannot add a GTU after the Platoons was started.");
230         Throw.when(this.startTime == null || this.endTime == null, IllegalStateException.class,
231                 "First call addPlatoon() before calling addGtu()");
232         Throw.when(time.gt(this.endTime) || time.lt(this.startTime), IllegalArgumentException.class,
233                 "Time %s is not between %s and %s", time, this.startTime, this.endTime);
234         this.queue.add(new PlatoonGtu<>(time, origin, destination, category));
235         this.numberOfGtus.put(category, this.numberOfGtus.getOrDefault(category, 0) + 1);
236         return this;
237     }
238 
239     /**
240      * Sets the generator and starts the events.
241      * @param generator LaneBasedGTUGenerator; GTU generator
242      * @throws SimRuntimeException if start of first platoon is in the past
243      */
244     public void start(final LaneBasedGTUGenerator generator) throws SimRuntimeException
245     {
246         Throw.when(this.started, IllegalStateException.class, "Cannot start the Platoons, it was already started.");
247         this.gen = generator;
248         // check platoon overlap
249         Time prevEnd = null;
250         for (Map.Entry<Time, Time> entry : this.periods.entrySet())
251         {
252             Time start = entry.getKey();
253             Throw.when(prevEnd != null && start.le(prevEnd), IllegalStateException.class, "Platoons are overlapping.");
254             prevEnd = entry.getValue();
255             this.gen.disable(start, prevEnd, this.position);
256         }
257         this.started = true;
258         start();
259     }
260 
261     /**
262      * Returns the vehicle generator for sub classes.
263      * @return LaneBasedGTUGenerator; vehicle generator for sub classes
264      */
265     protected LaneBasedGTUGenerator getGenerator()
266     {
267         return this.gen;
268     }
269 
270     /**
271      * Returns the position for sub classes.
272      * @return Set&lt;LaneDirection&gt;; position for sub classes
273      */
274     protected Set<LaneDirection> getPosition()
275     {
276         return this.position;
277     }
278 
279     /**
280      * Starts the events.
281      * @throws SimRuntimeException if start of first platoon is in the past
282      */
283     protected void start() throws SimRuntimeException
284     {
285         if (!this.queue.isEmpty())
286         {
287             this.simulator.scheduleEventAbs(this.queue.peek().getTime(), this, this, "placeGtu",
288                     new Object[] {this.queue.poll()});
289         }
290     }
291 
292     /**
293      * Creates a demand vector in which the platoon demand has been compensated from the input demand vector. Only demand
294      * pertaining to the location where the platoons are generated should be compensated.
295      * @param category T; category
296      * @param demand FrequencyVector; demand vector
297      * @param time TimeVector; time vector
298      * @param interpolation Interpolation; interpolation
299      * @return FrequencyVector; demand vector in which the platoon demand has been compensated from the input demand vector
300      */
301     public FrequencyVector compensate(final T category, final FrequencyVector demand, final TimeVector time,
302             final Interpolation interpolation)
303     {
304         Throw.whenNull(category, "Category may not be null.");
305         Throw.whenNull(demand, "Demand may not be null.");
306         Throw.whenNull(time, "Time may not be null.");
307         Throw.whenNull(interpolation, "Interpolation may not be null.");
308         Throw.when(demand.size() != time.size(), IllegalArgumentException.class, "Demand and time have unequal length.");
309         try
310         {
311             WeightedMeanAndSum<Double, Double> weightedSumLost = new WeightedMeanAndSum<>();
312             for (Map.Entry<Time, Time> entry : this.periods.entrySet())
313             {
314                 Time start = entry.getKey();
315                 Time end = entry.getValue();
316                 for (int i = 0; i < demand.size() - 1; i++)
317                 {
318                     Time s = Time.max(start, time.get(i));
319                     Time e = Time.min(end, time.get(i + 1));
320                     if (s.lt(e))
321                     {
322                         Frequency fStart = interpolation.interpolateVector(s, demand, time, true);
323                         // TODO: end time of platoon may be in next demand period, which makes the demand non-linear
324                         Frequency fEnd = interpolation.interpolateVector(e, demand, time, false);
325                         weightedSumLost.add((fStart.si + fEnd.si) / 2, e.si - s.si);
326                     }
327                 }
328             }
329             WeightedMeanAndSum<Double, Double> weightedSumTotal = new WeightedMeanAndSum<>();
330             for (int i = 0; i < demand.size() - 1; i++)
331             {
332                 Frequency fStart = interpolation.interpolateVector(time.get(i), demand, time, true);
333                 Frequency fEnd = interpolation.interpolateVector(time.get(i + 1), demand, time, false);
334                 weightedSumTotal.add((fStart.si + fEnd.si) / 2, time.getSI(i + 1) - time.getSI(i));
335 
336             }
337             // calculate factor
338             double lost = weightedSumLost.getSum();
339             double total = weightedSumTotal.getSum();
340             int platooning = this.numberOfGtus.getOrDefault(category, 0);
341             double factor = (total - platooning) / (total - lost);
342             if (factor < 0.0)
343             {
344                 SimLogger.always().warn("Reducing demand of {} by {}, demand is set to 0.", total, total - factor * total);
345                 factor = 0.0;
346             }
347             // create and return factor copy
348             double[] array = new double[demand.size()];
349             for (int i = 0; i < array.length - 1; i++)
350             {
351                 array[i] = demand.getInUnit(i) * factor;
352             }
353             return DoubleVector.instantiate(array, demand.getDisplayUnit(), StorageType.DENSE);
354         }
355         catch (ValueRuntimeException exception)
356         {
357             throw new RuntimeException("Unexpected exception while looping vector.", exception);
358         }
359     }
360 
361     /**
362      * Places the next platoon GTU and schedules the next one.
363      * @param platoonGtu PlatoonGtu&lt;T&gt;; info of GTU to generate
364      * @throws SimRuntimeException on exception
365      * @throws NamingException on exception
366      * @throws GTUException on exception
367      * @throws NetworkException on exception
368      * @throws OTSGeometryException on exception
369      * @throws ParameterException on exception
370      * @throws ProbabilityException on exception
371      */
372     protected abstract void placeGtu(PlatoonGtu<T> platoonGtu) throws SimRuntimeException, NamingException, GTUException,
373             NetworkException, OTSGeometryException, ProbabilityException, ParameterException;
374 
375     /**
376      * Class containing info of a GTU to generate.
377      * <p>
378      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
379      * <br>
380      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
381      * <p>
382      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 sep. 2018 <br>
383      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
384      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
385      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
386      * @param <K> type of demand category, typically a Category in an ODMatrix or a GTUType
387      */
388     private static class PlatoonGtu<K> implements Comparable<PlatoonGtu<K>>
389     {
390 
391         /** Time to generate. */
392         private final Time time;
393 
394         /** Origin. */
395         private final Node origin;
396 
397         /** Destination. */
398         private final Node destination;
399 
400         /** Category. */
401         private final K category;
402 
403         /**
404          * Constructor.
405          * @param time Time; time to generate
406          * @param origin Node; origin
407          * @param destination Node; destination
408          * @param category K; category
409          */
410         PlatoonGtu(final Time time, final Node origin, final Node destination, final K category)
411         {
412             this.time = time;
413             this.origin = origin;
414             this.destination = destination;
415             this.category = category;
416         }
417 
418         /** {@inheritDoc} */
419         @Override
420         public int compareTo(final PlatoonGtu<K> o)
421         {
422             if (o == null)
423             {
424                 return 1;
425             }
426             return this.time.compareTo(o.time);
427         }
428 
429         /**
430          * @return time.
431          */
432         protected Time getTime()
433         {
434             return this.time;
435         }
436 
437         /**
438          * @return origin.
439          */
440         protected Node getOrigin()
441         {
442             return this.origin;
443         }
444 
445         /**
446          * @return destination.
447          */
448         protected Node getDestination()
449         {
450             return this.destination;
451         }
452 
453         /**
454          * @return category.
455          */
456         protected K getCategory()
457         {
458             return this.category;
459         }
460 
461     }
462 }