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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 public abstract class Platoons<T>
51 {
52
53
54 private LaneBasedGtuGenerator gen;
55
56
57 private final OtsSimulatorInterface simulator;
58
59
60 private final Lane position;
61
62
63 private final Queue<PlatoonGtu<T>> queue = new PriorityQueue<>();
64
65
66 private final SortedMap<Time, Time> periods = new TreeMap<>();
67
68
69 private Time startTime;
70
71
72 private Time endTime;
73
74
75 private Node fixedOrigin;
76
77
78 private Node fixedDestination;
79
80
81 private T fixedCategory;
82
83
84 private Map<T, Integer> numberOfGtus = new LinkedHashMap<>();
85
86
87 private boolean started = false;
88
89
90
91
92
93
94 private Platoons(final OtsSimulatorInterface simulator, final Lane position)
95 {
96 this.simulator = simulator;
97 this.position = position;
98 }
99
100
101
102
103
104
105
106
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
114 private final LaneBasedGtuCharacteristicsGeneratorOd characteristicsOD = characteristics;
115
116
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
132
133
134
135
136
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
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
152 getGenerator().queueGtu(this.chrctrstcs.draw(), getPosition());
153 start();
154 }
155 };
156 }
157
158
159
160
161
162
163
164
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
179
180
181
182
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
194
195
196
197
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
208
209
210
211
212
213
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
229
230
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
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
251
252
253 protected LaneBasedGtuGenerator getGenerator()
254 {
255 return this.gen;
256 }
257
258
259
260
261
262 protected Lane getPosition()
263 {
264 return this.position;
265 }
266
267
268
269
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
282
283
284
285
286
287
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
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
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
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
345
346
347
348
349
350
351
352
353 protected abstract void placeGtu(PlatoonGtu<T> platoonGtu) throws SimRuntimeException, NamingException, GtuException,
354 NetworkException, OtsGeometryException, ParameterException;
355
356
357
358
359
360
361
362
363
364
365
366
367
368 private static class PlatoonGtu<K> implements Comparable<PlatoonGtu<K>>
369 {
370
371
372 private final Time time;
373
374
375 private final Node origin;
376
377
378 private final Node destination;
379
380
381 private final K category;
382
383
384
385
386
387
388
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
410
411 protected Time getTime()
412 {
413 return this.time;
414 }
415
416
417
418
419 protected Node getOrigin()
420 {
421 return this.origin;
422 }
423
424
425
426
427 protected Node getDestination()
428 {
429 return this.destination;
430 }
431
432
433
434
435 protected K getCategory()
436 {
437 return this.category;
438 }
439
440 }
441 }