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