1 package org.opentrafficsim.road.gtu.strategical.od;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.LinkedHashMap;
8 import java.util.LinkedHashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Set;
12 import java.util.TreeMap;
13
14 import org.djunits.unit.FrequencyUnit;
15 import org.djunits.unit.TimeUnit;
16 import org.djunits.value.ValueRuntimeException;
17 import org.djunits.value.storage.StorageType;
18 import org.djunits.value.vdouble.scalar.Frequency;
19 import org.djunits.value.vdouble.scalar.Time;
20 import org.djunits.value.vdouble.vector.FrequencyVector;
21 import org.djunits.value.vdouble.vector.TimeVector;
22 import org.djunits.value.vdouble.vector.base.DoubleVector;
23 import org.djutils.exceptions.Throw;
24 import org.opentrafficsim.base.Identifiable;
25 import org.opentrafficsim.core.network.NetworkException;
26 import org.opentrafficsim.core.network.Node;
27 import org.opentrafficsim.core.network.route.Route;
28 import org.opentrafficsim.road.gtu.generator.headway.DemandPattern;
29
30 /**
31 * The minimal OD matrix has 1 origin, 1 destination and 1 time period. More of each can be used. Further categorization of data
32 * is possible, i.e. for origin O to destination D, <i>for lane L, for route R and for vehicle class C</i>, the demand at time T
33 * is D. The further categorization is defined by an array of {@code Class}'s that define the categorization.
34 * <p>
35 * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
36 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
37 * <p>
38 * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 15, 2016 <br>
39 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
40 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
41 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
42 */
43 public class ODMatrix implements Serializable, Identifiable
44 {
45
46 /** */
47 private static final long serialVersionUID = 20160921L;
48
49 /** Id. */
50 private final String id;
51
52 /** Origin nodes. */
53 private final List<Node> origins;
54
55 /** Destination nodes. */
56 private final List<Node> destinations;
57
58 /** Categorization of demand data. */
59 private final Categorization categorization;
60
61 /** Global time vector. */
62 private final TimeVector globalTimeVector;
63
64 /** Global interpolation of the data. */
65 private final Interpolation globalInterpolation;
66
67 /** Demand data per origin and destination, and possibly further categorization. */
68 private final Map<Node, Map<Node, Map<Category, DemandPattern>>> demandData = new LinkedHashMap<>();
69
70 /** Node comparator. */
71 private static final Comparator<Node> COMPARATOR = new Comparator<Node>()
72 {
73 /** {@inheritDoc} */
74 @Override
75 public int compare(final Node o1, final Node o2)
76 {
77 return o1.getId().compareTo(o2.getId());
78 }
79 };
80
81 /**
82 * Constructs an OD matrix.
83 * @param id String; id
84 * @param origins List<? extends Node>; origin nodes
85 * @param destinations List<? extends Node>; destination nodes
86 * @param categorization Categorization; categorization of data
87 * @param globalTimeVector TimeVector; default time
88 * @param globalInterpolation Interpolation; interpolation of demand data
89 * @throws NullPointerException if any input is null
90 */
91 public ODMatrix(final String id, final List<? extends Node> origins, final List<? extends Node> destinations,
92 final Categorization categorization, final TimeVector globalTimeVector, final Interpolation globalInterpolation)
93 {
94 Throw.whenNull(id, "Id may not be null.");
95 Throw.whenNull(origins, "Origins may not be null.");
96 Throw.when(origins.contains(null), NullPointerException.class, "Origin may not contain null.");
97 Throw.whenNull(destinations, "Destination may not be null.");
98 Throw.when(destinations.contains(null), NullPointerException.class, "Destination may not contain null.");
99 Throw.whenNull(categorization, "Categorization may not be null.");
100 // Throw.whenNull(globalTimeVector, "Global time vector may not be null.");
101 // Throw.whenNull(globalInterpolation, "Global interpolation may not be null.");
102 this.id = id;
103 this.origins = new ArrayList<>(origins);
104 this.destinations = new ArrayList<>(destinations);
105 Collections.sort(this.origins, COMPARATOR);
106 Collections.sort(this.destinations, COMPARATOR);
107 this.categorization = categorization;
108 this.globalTimeVector = globalTimeVector;
109 this.globalInterpolation = globalInterpolation;
110 // build empty OD
111 for (Node origin : origins)
112 {
113 Map<Node, Map<Category, DemandPattern>> map = new LinkedHashMap<>();
114 for (Node destination : destinations)
115 {
116 map.put(destination, new TreeMap<>(new Comparator<Category>()
117 {
118 /** {@inheritDoc} */
119 @Override
120 public int compare(final Categoryad/gtu/strategical/od/Category.html#Category">Category o1, final Category o2)
121 {
122 for (int i = 0; i < o1.getCategorization().size(); i++)
123 {
124 int order = Integer.compare(o1.get(i).hashCode(), o2.get(i).hashCode());
125 if (order != 0)
126 {
127 return order;
128 }
129 }
130 return 0;
131 }
132 }));
133 }
134 this.demandData.put(origin, map);
135 }
136 }
137
138 /**
139 * @return id.
140 */
141 @Override
142 public final String getId()
143 {
144 return this.id;
145 }
146
147 /**
148 * @return origins.
149 */
150 public final List<Node> getOrigins()
151 {
152 return new ArrayList<>(this.origins);
153 }
154
155 /**
156 * @return destinations.
157 */
158 public final List<Node> getDestinations()
159 {
160 return new ArrayList<>(this.destinations);
161 }
162
163 /**
164 * @return categorization.
165 */
166 public final Categorization getCategorization()
167 {
168 return this.categorization;
169 }
170
171 /**
172 * @return globalTimeVector.
173 */
174 public final TimeVector getGlobalTimeVector()
175 {
176 return this.globalTimeVector;
177 }
178
179 /**
180 * @return globalInterpolation.
181 */
182 public final Interpolation getGlobalInterpolation()
183 {
184 return this.globalInterpolation;
185 }
186
187 /**
188 * Add a demand vector to OD.
189 * @param origin Node; origin
190 * @param destination Node; destination
191 * @param category Category; category
192 * @param demand FrequencyVector; demand data, length has to be equal to the global time vector
193 * @param fraction double; fraction of demand for this category
194 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
195 * @throws IllegalArgumentException if the category does not belong to the categorization
196 * @throws IllegalArgumentException if the demand data has a different length than time data, or is less than 2
197 * @throws IllegalArgumentException if demand is negative or time not strictly increasing
198 * @throws IllegalArgumentException if the route (if in the category) is not from the origin to the destination
199 * @throws NullPointerException if an input is null
200 */
201 public final void putDemandVector(final Node origin, final Node destination, final Category category,
202 final FrequencyVector demand, final double fraction)
203 {
204 putDemandVector(origin, destination, category, demand, this.globalTimeVector, this.globalInterpolation, fraction);
205 }
206
207 /**
208 * Add a demand vector to OD.
209 * @param origin Node; origin
210 * @param destination Node; destination
211 * @param category Category; category
212 * @param demand FrequencyVector; demand data, length has to be equal to the global time vector
213 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
214 * @throws IllegalArgumentException if the category does not belong to the categorization
215 * @throws IllegalArgumentException if the demand data has a different length than time data, or is less than 2
216 * @throws IllegalArgumentException if demand is negative or time not strictly increasing
217 * @throws IllegalArgumentException if the route (if in the category) is not from the origin to the destination
218 * @throws NullPointerException if an input is null
219 */
220 public final void putDemandVector(final Node origin, final Node destination, final Category category,
221 final FrequencyVector demand)
222 {
223 putDemandVector(origin, destination, category, demand, this.globalTimeVector, this.globalInterpolation);
224 }
225
226 /**
227 * Add a demand vector to OD. In this method, which all other methods that add or put demand indirectly refer to, many
228 * consistency and validity checks are performed. These do not include checks on network connectivity, since the network may
229 * be subject to change during simulation.
230 * @param origin Node; origin
231 * @param destination Node; destination
232 * @param category Category; category
233 * @param demand FrequencyVector; demand data, length has to be equal to the time vector
234 * @param timeVector TimeVector; time vector
235 * @param interpolation Interpolation; interpolation
236 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
237 * @throws IllegalArgumentException if the category does not belong to the categorization
238 * @throws IllegalArgumentException if the demand data has a different length than time data, or is less than 2
239 * @throws IllegalArgumentException if demand is negative or time not strictly increasing
240 * @throws IllegalArgumentException if the route (if in the category) is not from the origin to the destination
241 * @throws NullPointerException if an input is null
242 */
243 public final void putDemandVector(final Node origin, final Node destination, final Category category,
244 final FrequencyVector demand, final TimeVector timeVector, final Interpolation interpolation)
245 {
246 Throw.whenNull(origin, "Origin may not be null.");
247 Throw.whenNull(destination, "Destination may not be null.");
248 Throw.whenNull(category, "Category may not be null.");
249 Throw.whenNull(demand, "Demand data may not be null.");
250 Throw.whenNull(timeVector, "Time vector may not be null.");
251 Throw.whenNull(interpolation, "Interpolation may not be null.");
252 Throw.when(!this.origins.contains(origin), IllegalArgumentException.class, "Origin '%s' is not part of the OD matrix.",
253 origin);
254 Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
255 "Destination '%s' is not part of the OD matrix.", destination);
256 Throw.when(!this.categorization.equals(category.getCategorization()), IllegalArgumentException.class,
257 "Provided category %s does not belong to the categorization %s.", category, this.categorization);
258 Throw.when(demand.size() != timeVector.size() || demand.size() < 2, IllegalArgumentException.class,
259 "Demand data has different length than time vector, or has less than 2 values.");
260 for (Frequency q : demand)
261 {
262 Throw.when(q.lt0(), IllegalArgumentException.class, "Demand contains negative value(s).");
263 }
264 Time prevTime;
265 try
266 {
267 prevTime = timeVector.get(0).eq0() ? Time.instantiateSI(-1.0) : Time.ZERO;
268 }
269 catch (ValueRuntimeException exception)
270 {
271 // verified to be > 1, so no empty vector
272 throw new RuntimeException("Unexpected exception while checking time vector.", exception);
273 }
274 for (Time time : timeVector)
275 {
276 Throw.when(prevTime.ge(time), IllegalArgumentException.class,
277 "Time vector is not strictly increasing, or contains negative time.");
278 prevTime = time;
279 }
280 if (this.categorization.entails(Route.class))
281 {
282 Route route = category.get(Route.class);
283 try
284 {
285 Throw.when(!route.originNode().equals(origin) || !route.destinationNode().equals(destination),
286 IllegalArgumentException.class,
287 "Route from %s to %s does not comply with origin %s and destination %s.", route.originNode(),
288 route.destinationNode(), origin, destination);
289 }
290 catch (NetworkException exception)
291 {
292 throw new IllegalArgumentException("Route in OD has no nodes.", exception);
293 }
294 }
295 DemandPatternr/headway/DemandPattern.html#DemandPattern">DemandPattern demandPattern = new DemandPattern(demand, timeVector, interpolation);
296 this.demandData.get(origin).get(destination).put(category, demandPattern);
297 }
298
299 /**
300 * Add a demand vector to OD, by a fraction of total demand.
301 * @param origin Node; origin
302 * @param destination Node; destination
303 * @param category Category; category
304 * @param demand FrequencyVector; demand data, length has to be equal to the time vector
305 * @param timeVector TimeVector; time vector
306 * @param interpolation Interpolation; interpolation
307 * @param fraction double; fraction of demand for this category
308 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
309 * @throws IllegalArgumentException if the category does not belong to the categorization
310 * @throws IllegalArgumentException if the demand data has a different length than time data, or is less than 2
311 * @throws IllegalArgumentException if demand is negative or time not strictly increasing
312 * @throws IllegalArgumentException if the route (if in the category) is not from the origin to the destination
313 * @throws NullPointerException if an input is null
314 */
315 public final void putDemandVector(final Node origin, final Node destination, final Category category,
316 final FrequencyVector demand, final TimeVector timeVector, final Interpolation interpolation, final double fraction)
317 {
318 Throw.whenNull(demand, "Demand data may not be null.");
319 FrequencyVector demandScaled;
320 if (fraction == 1.0)
321 {
322 demandScaled = demand;
323 }
324 else
325 {
326 double[] in = demand.getValuesInUnit();
327 double[] scaled = new double[in.length];
328 for (int i = 0; i < in.length; i++)
329 {
330 scaled[i] = in[i] * fraction;
331 }
332 try
333 {
334 demandScaled = DoubleVector.instantiate(scaled, demand.getDisplayUnit(), demand.getStorageType());
335 }
336 catch (ValueRuntimeException exception)
337 {
338 // cannot happen, we use an existing vector
339 throw new RuntimeException("An object was null.", exception);
340 }
341 }
342 putDemandVector(origin, destination, category, demandScaled, timeVector, interpolation);
343 }
344
345 /**
346 * Add a demand vector to OD, by a fraction per time period of total demand.
347 * @param origin Node; origin
348 * @param destination Node; destination
349 * @param category Category; category
350 * @param demand FrequencyVector; demand data, length has to be equal to the time vector
351 * @param timeVector TimeVector; time vector
352 * @param interpolation Interpolation; interpolation
353 * @param fraction double[]; fraction of demand for this category
354 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
355 * @throws IllegalArgumentException if the category does not belong to the categorization
356 * @throws IllegalArgumentException if the demand data has a different length than time data, or is less than 2
357 * @throws IllegalArgumentException if demand is negative or time not strictly increasing
358 * @throws IllegalArgumentException if the route (if in the category) is not from the origin to the destination
359 * @throws NullPointerException if an input is null
360 */
361 public final void putDemandVector(final Node origin, final Node destination, final Category category,
362 final FrequencyVector demand, final TimeVector timeVector, final Interpolation interpolation,
363 final double[] fraction)
364 {
365 Throw.whenNull(demand, "Demand data may not be null.");
366 Throw.whenNull(fraction, "Fraction data may not be null.");
367 Throw.whenNull(timeVector, "Time vector may not be null.");
368 Throw.when(demand.size() != timeVector.size() || timeVector.size() != fraction.length, IllegalArgumentException.class,
369 "Arrays are of unequal length: demand=%d, timeVector=%d, fraction=%d", demand.size(), timeVector.size(),
370 fraction.length);
371 double[] in = demand.getValuesInUnit();
372 double[] scaled = new double[in.length];
373 for (int i = 0; i < in.length; i++)
374 {
375 scaled[i] = in[i] * fraction[i];
376 }
377 FrequencyVector demandScaled;
378 try
379 {
380 demandScaled = DoubleVector.instantiate(scaled, demand.getDisplayUnit(), demand.getStorageType());
381 }
382 catch (ValueRuntimeException exception)
383 {
384 // cannot happen, we use an existing vector
385 throw new RuntimeException("An object was null.", exception);
386 }
387 putDemandVector(origin, destination, category, demandScaled, timeVector, interpolation);
388 }
389
390 /**
391 * @param origin Node; origin
392 * @param destination Node; destination
393 * @param category Category; category
394 * @return demand data for given origin, destination and categorization, {@code null} if no data is given
395 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
396 * @throws IllegalArgumentException if the category does not belong to the categorization
397 * @throws NullPointerException if an input is null
398 */
399 public final FrequencyVector getDemandVector(final Node origin, final Node destination, final Category category)
400 {
401 DemandPattern demandPattern = getDemandPattern(origin, destination, category);
402 if (demandPattern == null)
403 {
404 return null;
405 }
406 return demandPattern.getDemandVector();
407 }
408
409 /**
410 * @param origin Node; origin
411 * @param destination Node; destination
412 * @param category Category; category
413 * @return interpolation for given origin, destination and categorization, {@code null} if no data is given
414 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
415 * @throws IllegalArgumentException if the category does not belong to the categorization
416 * @throws NullPointerException if an input is null
417 */
418 public final TimeVector getTimeVector(final Node origin, final Node destination, final Category category)
419 {
420 DemandPattern demandPattern = getDemandPattern(origin, destination, category);
421 if (demandPattern == null)
422 {
423 return null;
424 }
425 return demandPattern.getTimeVector();
426 }
427
428 /**
429 * @param origin Node; origin
430 * @param destination Node; destination
431 * @param category Category; category
432 * @return interpolation for given origin, destination and categorization, {@code null} if no data is given
433 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
434 * @throws IllegalArgumentException if the category does not belong to the categorization
435 * @throws NullPointerException if an input is null
436 */
437 public final Interpolation getInterpolation(final Node origin, final Node destination, final Category category)
438 {
439 DemandPattern demandPattern = getDemandPattern(origin, destination, category);
440 if (demandPattern == null)
441 {
442 return null;
443 }
444 return demandPattern.getInterpolation();
445 }
446
447 /**
448 * Returns the demand at given time. If given time is before the first time slice or after the last time slice, 0 demand is
449 * returned.
450 * @param origin Node; origin
451 * @param destination Node; destination
452 * @param category Category; category
453 * @param time Time; time
454 * @param sliceStart boolean; whether the time is at the start of an arbitrary time slice
455 * @return demand for given origin, destination and categorization, at given time
456 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
457 * @throws IllegalArgumentException if the category does not belong to the categorization
458 * @throws NullPointerException if an input is null
459 */
460 public final Frequency getDemand(final Node origin, final Node destination, final Category category, final Time time,
461 final boolean sliceStart)
462 {
463 Throw.whenNull(time, "Time may not be null.");
464 DemandPattern demandPattern = getDemandPattern(origin, destination, category);
465 if (demandPattern == null)
466 {
467 return new Frequency(0.0, FrequencyUnit.PER_HOUR); // Frequency.ZERO gives "Hz" which is not nice for flow
468 }
469 return demandPattern.getFrequency(time, sliceStart);
470 }
471
472 /**
473 * @param origin Node; origin
474 * @param destination Node; destination
475 * @param category Category; category
476 * @return OD entry for given origin, destination and categorization.
477 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
478 * @throws IllegalArgumentException if the category does not belong to the categorization
479 * @throws NullPointerException if an input is null
480 */
481 public DemandPattern getDemandPattern(final Node origin, final Node destination, final Category category)
482 {
483 Throw.whenNull(origin, "Origin may not be null.");
484 Throw.whenNull(destination, "Destination may not be null.");
485 Throw.whenNull(category, "Category may not be null.");
486 Throw.when(!this.origins.contains(origin), IllegalArgumentException.class, "Origin '%s' is not part of the OD matrix",
487 origin);
488 Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
489 "Destination '%s' is not part of the OD matrix.", destination);
490 Throw.when(!this.categorization.equals(category.getCategorization()), IllegalArgumentException.class,
491 "Provided category %s does not belong to the categorization %s.", category, this.categorization);
492 return this.demandData.get(origin).get(destination).get(category);
493 }
494
495 /**
496 * @param origin Node; origin
497 * @param destination Node; destination
498 * @param category Category; category
499 * @return whether there is data for the specified origin, destination and category
500 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
501 * @throws IllegalArgumentException if the category does not belong to the categorization
502 * @throws NullPointerException if an input is null
503 */
504 public final boolean contains(final Node origin, final Node destination, final Category category)
505 {
506 return getDemandPattern(origin, destination, category) != null;
507 }
508
509 /**
510 * Returns the categories specified for given origin-destination combination.
511 * @param origin Node; origin
512 * @param destination Node; destination
513 * @return categories specified for given origin-destination combination
514 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
515 * @throws NullPointerException if an input is null
516 */
517 public final Set<Category> getCategories(final Node origin, final Node destination)
518 {
519 Throw.whenNull(origin, "Origin may not be null.");
520 Throw.whenNull(destination, "Destination may not be null.");
521 Throw.when(!this.origins.contains(origin), IllegalArgumentException.class, "Origin '%s' is not part of the OD matrix",
522 origin);
523 Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
524 "Destination '%s' is not part of the OD matrix.", destination);
525 return new LinkedHashSet<>(this.demandData.get(origin).get(destination).keySet());
526 }
527
528 /******************************************************************************************************/
529 /****************************************** TRIP METHODS **********************************************/
530 /******************************************************************************************************/
531
532 /**
533 * @param origin Node; origin
534 * @param destination Node; destination
535 * @param category Category; category
536 * @param trips int[]; trip data, length has to be equal to the global time vector - 1, each value is the number of trips
537 * during a period
538 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
539 * @throws IllegalArgumentException if the category does not belong to the categorization
540 * @throws IllegalArgumentException if the demand data has a different length than time data, or is less than 2
541 * @throws IllegalArgumentException if demand is negative or time not strictly increasing
542 * @throws IllegalArgumentException if the route (if in the category) is not from the origin to the destination
543 * @throws NullPointerException if an input is null
544 */
545 public final void putTripsVector(final Node origin, final Node destination, final Category category, final int[] trips)
546 {
547 putTripsVector(origin, destination, category, trips, getGlobalTimeVector());
548 }
549
550 /**
551 * Sets demand data by number of trips. Interpolation over time is stepwise.
552 * @param origin Node; origin
553 * @param destination Node; destination
554 * @param category Category; category
555 * @param trips int[]; trip data, length has to be equal to the time vector - 1, each value is the number of trips during a
556 * period
557 * @param timeVector TimeVector; time vector
558 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
559 * @throws IllegalArgumentException if the category does not belong to the categorization
560 * @throws IllegalArgumentException if the demand data has a different length than time data, or is less than 2
561 * @throws IllegalArgumentException if demand is negative or time not strictly increasing
562 * @throws IllegalArgumentException if the route (if in the category) is not from the origin to the destination
563 * @throws NullPointerException if an input is null
564 */
565 public final void putTripsVector(final Node origin, final Node destination, final Category category, final int[] trips,
566 final TimeVector timeVector)
567 {
568 // this is what we need here, other checks in putDemandVector
569 Throw.whenNull(trips, "Demand data may not be null.");
570 Throw.whenNull(timeVector, "Time vector may not be null.");
571 Throw.when(trips.length != timeVector.size() - 1, IllegalArgumentException.class,
572 "Trip data and time data have wrong lengths. Trip data should be 1 shorter than time data.");
573 // convert to flow
574 double[] flow = new double[timeVector.size()];
575 try
576 {
577 for (int i = 0; i < trips.length; i++)
578 {
579 flow[i] = trips[i] / (timeVector.get(i + 1).getInUnit(TimeUnit.BASE_HOUR)
580 - timeVector.get(i).getInUnit(TimeUnit.BASE_HOUR));
581 }
582 // last value can remain zero as initialized
583 putDemandVector(origin, destination, category, DoubleVector.instantiate(flow, FrequencyUnit.PER_HOUR, StorageType.DENSE),
584 timeVector, Interpolation.STEPWISE);
585 }
586 catch (ValueRuntimeException exception)
587 {
588 // should not happen as we check and then loop over the array length
589 throw new RuntimeException("Could not translate trip vector into demand vector.", exception);
590 }
591 }
592
593 /**
594 * @param origin Node; origin
595 * @param destination Node; destination
596 * @param category Category; category
597 * @return trip data for given origin, destination and categorization, {@code null} if no data is given
598 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
599 * @throws IllegalArgumentException if the category does not belong to the categorization
600 * @throws NullPointerException if an input is null
601 */
602 public final int[] getTripsVector(final Node origin, final Node destination, final Category category)
603 {
604 FrequencyVector demand = getDemandVector(origin, destination, category);
605 if (demand == null)
606 {
607 return null;
608 }
609 int[] trips = new int[demand.size() - 1];
610 TimeVector time = getTimeVector(origin, destination, category);
611 Interpolation interpolation = getInterpolation(origin, destination, category);
612 for (int i = 0; i < trips.length; i++)
613 {
614 try
615 {
616 trips[i] = interpolation.integrate(demand.get(i), time.get(i), demand.get(i + 1), time.get(i + 1));
617 }
618 catch (ValueRuntimeException exception)
619 {
620 // should not happen as we loop over the array length
621 throw new RuntimeException("Could not translate demand vector into trip vector.", exception);
622 }
623 }
624 return trips;
625 }
626
627 /**
628 * Returns the number of trips in the given time period.
629 * @param origin Node; origin
630 * @param destination Node; destination
631 * @param category Category; category
632 * @param periodIndex int; index of time period
633 * @return demand for given origin, destination and categorization, at given time
634 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
635 * @throws IllegalArgumentException if the category does not belong to the categorization
636 * @throws IndexOutOfBoundsException if the period is outside of the specified range
637 * @throws NullPointerException if an input is null
638 */
639 public final int getTrips(final Node origin, final Node destination, final Category category, final int periodIndex)
640 {
641 TimeVector time = getTimeVector(origin, destination, category);
642 if (time == null)
643 {
644 return 0;
645 }
646 Throw.when(periodIndex < 0 || periodIndex >= time.size() - 1, IndexOutOfBoundsException.class,
647 "Period index out of range.");
648 FrequencyVector demand = getDemandVector(origin, destination, category);
649 Interpolation interpolation = getInterpolation(origin, destination, category);
650 try
651 {
652 return interpolation.integrate(demand.get(periodIndex), time.get(periodIndex), demand.get(periodIndex + 1),
653 time.get(periodIndex + 1));
654 }
655 catch (ValueRuntimeException exception)
656 {
657 // should not happen as the index was checked
658 throw new RuntimeException("Could not get number of trips.", exception);
659 }
660 }
661
662 /**
663 * Adds a number of trips to given origin-destination combination, category and time period. This can only be done for data
664 * with stepwise interpolation.
665 * @param origin Node; origin
666 * @param destination Node; destination
667 * @param category Category; category
668 * @param periodIndex int; index of time period
669 * @param trips int; trips to add (may be negative)
670 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
671 * @throws IllegalArgumentException if the category does not belong to the categorization
672 * @throws IndexOutOfBoundsException if the period is outside of the specified range
673 * @throws UnsupportedOperationException if the interpolation of the data is not stepwise, or demand becomes negtive
674 * @throws NullPointerException if an input is null
675 */
676 public final void increaseTrips(final Node origin, final Node destination, final Category category, final int periodIndex,
677 final int trips)
678 {
679 Interpolation interpolation = getInterpolation(origin, destination, category);
680 Throw.when(!interpolation.equals(Interpolation.STEPWISE), UnsupportedOperationException.class,
681 "Can only increase the number of trips for data with stepwise interpolation.");
682 TimeVector time = getTimeVector(origin, destination, category);
683 Throw.when(periodIndex < 0 || periodIndex >= time.size() - 1, IndexOutOfBoundsException.class,
684 "Period index out of range.");
685 FrequencyVector demand = getDemandVector(origin, destination, category);
686 try
687 {
688 double additionalDemand = trips / (time.get(periodIndex + 1).getInUnit(TimeUnit.BASE_HOUR)
689 - time.get(periodIndex).getInUnit(TimeUnit.BASE_HOUR));
690 double[] dem = demand.getValuesInUnit(FrequencyUnit.PER_HOUR);
691 Throw.when(dem[periodIndex] < -additionalDemand, UnsupportedOperationException.class,
692 "Demand may not become negative.");
693 dem[periodIndex] += additionalDemand;
694 putDemandVector(origin, destination, category, DoubleVector.instantiate(dem, FrequencyUnit.PER_HOUR, StorageType.DENSE),
695 time, Interpolation.STEPWISE);
696 }
697 catch (ValueRuntimeException exception)
698 {
699 // should not happen as the index was checked
700 throw new RuntimeException("Unexpected exception while getting number of trips.", exception);
701 }
702 }
703
704 /**
705 * Calculates total number of trips over time for given origin.
706 * @param origin Node; origin
707 * @return total number of trips over time for given origin
708 * @throws IllegalArgumentException if origin is not part of the OD matrix
709 * @throws NullPointerException if origin is null
710 */
711 public final int originTotal(final Node origin)
712 {
713 int sum = 0;
714 for (Node destination : getDestinations())
715 {
716 sum += originDestinationTotal(origin, destination);
717 }
718 return sum;
719 }
720
721 /**
722 * Calculates total number of trips over time for given destination.
723 * @param destination Node; destination
724 * @return total number of trips over time for given destination
725 * @throws IllegalArgumentException if destination is not part of the OD matrix
726 * @throws NullPointerException if destination is null
727 */
728 public final int destinationTotal(final Node destination)
729 {
730 int sum = 0;
731 for (Node origin : getOrigins())
732 {
733 sum += originDestinationTotal(origin, destination);
734 }
735 return sum;
736 }
737
738 /**
739 * Calculates total number of trips over time for the complete matrix.
740 * @return total number of trips over time for the complete matrix
741 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
742 * @throws NullPointerException if an input is null
743 */
744 public final int matrixTotal()
745 {
746 int sum = 0;
747 for (Node origin : getOrigins())
748 {
749 for (Node destination : getDestinations())
750 {
751 sum += originDestinationTotal(origin, destination);
752 }
753 }
754 return sum;
755 }
756
757 /**
758 * Calculates total number of trips over time for given origin-destination combination.
759 * @param origin Node; origin
760 * @param destination Node; destination
761 * @return total number of trips over time for given origin-destination combination
762 * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
763 * @throws NullPointerException if an input is null
764 */
765 public final int originDestinationTotal(final Node origin, final Node destination)
766 {
767 int sum = 0;
768 for (Category category : getCategories(origin, destination))
769 {
770 TimeVector time = getTimeVector(origin, destination, category);
771 FrequencyVector demand = getDemandVector(origin, destination, category);
772 Interpolation interpolation = getInterpolation(origin, destination, category);
773 for (int i = 0; i < time.size() - 1; i++)
774 {
775 try
776 {
777 sum += interpolation.integrate(demand.get(i), time.get(i), demand.get(i + 1), time.get(i + 1));
778 }
779 catch (ValueRuntimeException exception)
780 {
781 // should not happen as we loop over the array length
782 throw new RuntimeException("Unexcepted exception while determining total trips over time.", exception);
783 }
784 }
785 }
786 return sum;
787 }
788
789 /******************************************************************************************************/
790 /****************************************** OTHER METHODS *********************************************/
791 /******************************************************************************************************/
792
793 /** {@inheritDoc} */
794 @Override
795 @SuppressWarnings("checkstyle:designforextension")
796 public String toString()
797 {
798 return "ODMatrix [" + this.id + ", " + this.origins.size() + " origins, " + this.destinations.size() + " destinations, "
799 + this.categorization + " ]";
800 }
801
802 /**
803 * Prints the complete OD matrix with each demand data on a single line.
804 */
805 public final void print()
806 {
807 int originLength = 0;
808 for (Node origin : this.origins)
809 {
810 originLength = originLength >= origin.getId().length() ? originLength : origin.getId().length();
811 }
812 int destinLength = 0;
813 for (Node destination : this.destinations)
814 {
815 destinLength = destinLength >= destination.getId().length() ? destinLength : destination.getId().length();
816 }
817 String format = "%-" + Math.max(originLength, 1) + "s -> %-" + Math.max(destinLength, 1) + "s | ";
818 for (Node origin : this.origins)
819 {
820 Map<Node, Map<Category, DemandPattern>> destinationMap = this.demandData.get(origin);
821 for (Node destination : this.destinations)
822 {
823 Map<Category, DemandPattern> categoryMap = destinationMap.get(destination);
824 if (categoryMap.isEmpty())
825 {
826 System.out.println(String.format(format, origin.getId(), destination.getId()) + "-no data-");
827 }
828 else
829 {
830 for (Category category : categoryMap.keySet())
831 {
832 StringBuilder catStr = new StringBuilder("[");
833 String sep = "";
834 for (int i = 0; i < category.getCategorization().size(); i++)
835 {
836 catStr.append(sep);
837 Object obj = category.get(i);
838 if (obj instanceof Route)
839 {
840 catStr.append("Route: " + ((Route) obj).getId());
841 }
842 else
843 {
844 catStr.append(obj);
845 }
846 sep = ", ";
847 }
848 catStr.append("]");
849 // System.out.println("DEBUG format is \"" + format + "\"");
850 System.out.println(String.format(format, origin.getId(), destination.getId()) + catStr + " | "
851 + categoryMap.get(category).getDemandVector());
852 }
853 }
854 }
855 }
856 }
857
858 /** {@inheritDoc} */
859 @Override
860 public final int hashCode()
861 {
862 final int prime = 31;
863 int result = 1;
864 result = prime * result + ((this.categorization == null) ? 0 : this.categorization.hashCode());
865 result = prime * result + ((this.demandData == null) ? 0 : this.demandData.hashCode());
866 result = prime * result + ((this.destinations == null) ? 0 : this.destinations.hashCode());
867 result = prime * result + ((this.globalInterpolation == null) ? 0 : this.globalInterpolation.hashCode());
868 result = prime * result + ((this.globalTimeVector == null) ? 0 : this.globalTimeVector.hashCode());
869 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
870 result = prime * result + ((this.origins == null) ? 0 : this.origins.hashCode());
871 return result;
872 }
873
874 /** {@inheritDoc} */
875 @Override
876 public final boolean equals(final Object obj)
877 {
878 if (this == obj)
879 {
880 return true;
881 }
882 if (obj == null)
883 {
884 return false;
885 }
886 if (getClass() != obj.getClass())
887 {
888 return false;
889 }
890 ODMatrix./../../../../../org/opentrafficsim/road/gtu/strategical/od/ODMatrix.html#ODMatrix">ODMatrix other = (ODMatrix) obj;
891 if (this.categorization == null)
892 {
893 if (other.categorization != null)
894 {
895 return false;
896 }
897 }
898 else if (!this.categorization.equals(other.categorization))
899 {
900 return false;
901 }
902 if (this.demandData == null)
903 {
904 if (other.demandData != null)
905 {
906 return false;
907 }
908 }
909 else if (!this.demandData.equals(other.demandData))
910 {
911 return false;
912 }
913 if (this.destinations == null)
914 {
915 if (other.destinations != null)
916 {
917 return false;
918 }
919 }
920 else if (!this.destinations.equals(other.destinations))
921 {
922 return false;
923 }
924 if (this.globalInterpolation != other.globalInterpolation)
925 {
926 return false;
927 }
928 if (this.globalTimeVector == null)
929 {
930 if (other.globalTimeVector != null)
931 {
932 return false;
933 }
934 }
935 else if (!this.globalTimeVector.equals(other.globalTimeVector))
936 {
937 return false;
938 }
939 if (this.id == null)
940 {
941 if (other.id != null)
942 {
943 return false;
944 }
945 }
946 else if (!this.id.equals(other.id))
947 {
948 return false;
949 }
950 if (this.origins == null)
951 {
952 if (other.origins != null)
953 {
954 return false;
955 }
956 }
957 else if (!this.origins.equals(other.origins))
958 {
959 return false;
960 }
961 return true;
962 }
963
964 }