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