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