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