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.Duration;
13 import org.djunits.value.vdouble.scalar.Frequency;
14 import org.djunits.value.vdouble.vector.DurationVector;
15 import org.djunits.value.vdouble.vector.FrequencyVector;
16 import org.djutils.exceptions.Throw;
17 import org.djutils.exceptions.Try;
18 import org.djutils.math.means.ArithmeticMean;
19 import org.opentrafficsim.base.geometry.OtsGeometryException;
20 import org.opentrafficsim.base.logger.Logger;
21 import org.opentrafficsim.base.parameters.ParameterException;
22 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
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<Duration, Duration> periods = new TreeMap<>();
69
70
71 private Duration startTime;
72
73
74 private Duration 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 @Override
122 protected void placeGtu(final PlatoonGtu<Category> platoonGtu) throws SimRuntimeException, NamingException,
123 GtuException, NetworkException, OtsGeometryException, ParameterException
124 {
125 getGenerator().queueGtu(this.characteristicsOD.draw(platoonGtu.origin(), platoonGtu.destination(),
126 platoonGtu.category(), this.strm), getPosition());
127 start();
128 }
129 };
130 }
131
132
133
134
135
136
137
138
139
140 @SuppressWarnings("synthetic-access")
141 public static Platoons<GtuType> ofGtuType(final LaneBasedGtuCharacteristicsGenerator characteristics,
142 final OtsSimulatorInterface simulator, final StreamInterface stream, final Lane position)
143 {
144 return new Platoons<GtuType>(simulator, position)
145 {
146
147 private final LaneBasedGtuCharacteristicsGenerator chrctrstcs = characteristics;
148
149 @Override
150 protected void placeGtu(final PlatoonGtu<GtuType> platoonGtu) throws SimRuntimeException, NamingException,
151 GtuException, NetworkException, OtsGeometryException, ParameterException
152 {
153
154 getGenerator().queueGtu(this.chrctrstcs.draw(), getPosition());
155 start();
156 }
157 };
158 }
159
160
161
162
163
164
165
166
167
168 public Platoons<T> addPlatoon(final Duration start, final Duration end) throws SimRuntimeException
169 {
170 Throw.when(this.started, IllegalStateException.class, "Cannot add a platoon after the Platoons was started.");
171 Throw.whenNull(start, "Start may not be null.");
172 Throw.whenNull(end, "End may not be null.");
173 this.startTime = start;
174 this.endTime = end;
175 this.periods.put(start, end);
176 return this;
177 }
178
179
180
181
182
183
184
185
186 public Platoons<T> fixInfo(final Node origin, final Node destination, final T category)
187 {
188 this.fixedOrigin = origin;
189 this.fixedDestination = destination;
190 this.fixedCategory = category;
191 return this;
192 }
193
194
195
196
197
198
199
200
201 public Platoons<T> addGtu(final Duration time)
202 {
203 Throw.when(this.fixedOrigin == null || this.fixedDestination == null || this.fixedCategory == null,
204 IllegalStateException.class, "When using addGtu(Time), used fixInfo(...) before to set other info.");
205 return addGtu(time, this.fixedOrigin, this.fixedDestination, this.fixedCategory);
206 }
207
208
209
210
211
212
213
214
215
216
217 public Platoons<T> addGtu(final Duration time, final Node origin, final Node destination, final T category)
218 {
219 Throw.when(this.started, IllegalStateException.class, "Cannot add a GTU after the Platoons was started.");
220 Throw.when(this.startTime == null || this.endTime == null, IllegalStateException.class,
221 "First call addPlatoon() before calling addGtu()");
222 Throw.when(time.gt(this.endTime) || time.lt(this.startTime), IllegalArgumentException.class,
223 "Time %s is not between %s and %s", time, this.startTime, this.endTime);
224 this.queue.add(new PlatoonGtu<>(time, origin, destination, category));
225 this.numberOfGtus.put(category, this.numberOfGtus.getOrDefault(category, 0) + 1);
226 return this;
227 }
228
229
230
231
232
233
234 public void start(final LaneBasedGtuGenerator generator) throws SimRuntimeException
235 {
236 Throw.when(this.started, IllegalStateException.class, "Cannot start the Platoons, it was already started.");
237 this.gen = generator;
238
239 Duration prevEnd = null;
240 for (Map.Entry<Duration, Duration> entry : this.periods.entrySet())
241 {
242 Duration start = entry.getKey();
243 Throw.when(prevEnd != null && start.le(prevEnd), IllegalStateException.class, "Platoons are overlapping.");
244 prevEnd = entry.getValue();
245 this.gen.disable(start, prevEnd, this.position);
246 }
247 this.started = true;
248 start();
249 }
250
251
252
253
254
255 protected LaneBasedGtuGenerator getGenerator()
256 {
257 return this.gen;
258 }
259
260
261
262
263
264 protected Lane getPosition()
265 {
266 return this.position;
267 }
268
269
270
271
272
273 protected void start() throws SimRuntimeException
274 {
275 if (!this.queue.isEmpty())
276 {
277 this.simulator.scheduleEventAbs(this.queue.peek().time(),
278 () -> Try.execute(() -> placeGtu(this.queue.poll()), "Exception while placing platoon GTU."));
279 }
280 }
281
282
283
284
285
286
287
288
289
290
291 public FrequencyVector compensate(final T category, final FrequencyVector demand, final DurationVector time,
292 final Interpolation interpolation)
293 {
294 Throw.whenNull(category, "Category may not be null.");
295 Throw.whenNull(demand, "Demand may not be null.");
296 Throw.whenNull(time, "Time may not be null.");
297 Throw.whenNull(interpolation, "Interpolation may not be null.");
298 Throw.when(demand.size() != time.size(), IllegalArgumentException.class, "Demand and time have unequal length.");
299 ArithmeticMean<Double, Double> weightedSumLost = new ArithmeticMean<>();
300 for (Map.Entry<Duration, Duration> entry : this.periods.entrySet())
301 {
302 Duration start = entry.getKey();
303 Duration end = entry.getValue();
304 for (int i = 0; i < demand.size() - 1; i++)
305 {
306 Duration s = Duration.max(start, time.get(i));
307 Duration e = Duration.min(end, time.get(i + 1));
308 if (s.lt(e))
309 {
310 Frequency fStart = interpolation.interpolateVector(s, demand, time, true);
311
312 Frequency fEnd = interpolation.interpolateVector(e, demand, time, false);
313 weightedSumLost.add((fStart.si + fEnd.si) / 2, e.si - s.si);
314 }
315 }
316 }
317 ArithmeticMean<Double, Double> weightedSumTotal = new ArithmeticMean<>();
318 for (int i = 0; i < demand.size() - 1; i++)
319 {
320 Frequency fStart = interpolation.interpolateVector(time.get(i), demand, time, true);
321 Frequency fEnd = interpolation.interpolateVector(time.get(i + 1), demand, time, false);
322 weightedSumTotal.add((fStart.si + fEnd.si) / 2, time.getSI(i + 1) - time.getSI(i));
323
324 }
325
326 double lost = weightedSumLost.getSum();
327 double total = weightedSumTotal.getSum();
328 int platooning = this.numberOfGtus.getOrDefault(category, 0);
329 double factor = (total - platooning) / (total - lost);
330 if (factor < 0.0)
331 {
332 Logger.ots().warn("Reducing demand of {} by {}, demand is set to 0.", total, total - factor * total);
333 factor = 0.0;
334 }
335
336 double[] array = new double[demand.size()];
337 for (int i = 0; i < array.length - 1; i++)
338 {
339 array[i] = demand.getInUnit(i) * factor;
340 }
341 return new FrequencyVector(array, demand.getDisplayUnit());
342 }
343
344
345
346
347
348
349
350
351
352
353
354 protected abstract void placeGtu(PlatoonGtu<T> platoonGtu) throws SimRuntimeException, NamingException, GtuException,
355 NetworkException, OtsGeometryException, ParameterException;
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 private record PlatoonGtu<K>(Duration time, Node origin, Node destination, K category) implements Comparable<PlatoonGtu<K>>
374 {
375 @Override
376 public int compareTo(final PlatoonGtu<K> o)
377 {
378 if (o == null)
379 {
380 return 1;
381 }
382 return this.time.compareTo(o.time);
383 }
384 }
385 }