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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 public abstract class Platoons<T>
53 {
54
55
56 private LaneBasedGtuGenerator gen;
57
58
59 private final OtsSimulatorInterface simulator;
60
61
62 private final Lane position;
63
64
65 private final Queue<PlatoonGtu<T>> queue = new PriorityQueue<>();
66
67
68 private final SortedMap<Time, Time> periods = new TreeMap<>();
69
70
71 private Time startTime;
72
73
74 private Time endTime;
75
76
77 private Node fixedOrigin;
78
79
80 private Node fixedDestination;
81
82
83 private T fixedCategory;
84
85
86 private Map<T, Integer> numberOfGtus = new LinkedHashMap<>();
87
88
89 private boolean started = false;
90
91
92
93
94
95
96 private Platoons(final OtsSimulatorInterface simulator, final Lane position)
97 {
98 this.simulator = simulator;
99 this.position = position;
100 }
101
102
103
104
105
106
107
108
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
116 private final LaneBasedGtuCharacteristicsGeneratorOd characteristicsOD = characteristics;
117
118
119 private final StreamInterface strm = stream;
120
121
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
135
136
137
138
139
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
148 private final LaneBasedGtuCharacteristicsGenerator chrctrstcs = characteristics;
149
150
151 @Override
152 protected void placeGtu(final PlatoonGtu<GtuType> platoonGtu) throws SimRuntimeException, NamingException,
153 GtuException, NetworkException, OtsGeometryException, ProbabilityException, ParameterException
154 {
155
156 getGenerator().queueGtu(this.chrctrstcs.draw(), getPosition());
157 start();
158 }
159 };
160 }
161
162
163
164
165
166
167
168
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
183
184
185
186
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
198
199
200
201
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
212
213
214
215
216
217
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
233
234
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
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
255
256
257 protected LaneBasedGtuGenerator getGenerator()
258 {
259 return this.gen;
260 }
261
262
263
264
265
266 protected Lane getPosition()
267 {
268 return this.position;
269 }
270
271
272
273
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
286
287
288
289
290
291
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
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
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
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
349
350
351
352
353
354
355
356
357
358 protected abstract void placeGtu(PlatoonGtu<T> platoonGtu) throws SimRuntimeException, NamingException, GtuException,
359 NetworkException, OtsGeometryException, ProbabilityException, ParameterException;
360
361
362
363
364
365
366
367
368
369
370
371
372
373 private static class PlatoonGtu<K> implements Comparable<PlatoonGtu<K>>
374 {
375
376
377 private final Time time;
378
379
380 private final Node origin;
381
382
383 private final Node destination;
384
385
386 private final K category;
387
388
389
390
391
392
393
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
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
416
417 protected Time getTime()
418 {
419 return this.time;
420 }
421
422
423
424
425 protected Node getOrigin()
426 {
427 return this.origin;
428 }
429
430
431
432
433 protected Node getDestination()
434 {
435 return this.destination;
436 }
437
438
439
440
441 protected K getCategory()
442 {
443 return this.category;
444 }
445
446 }
447 }