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