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