View Javadoc
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&lt;? extends Node&gt;; origin nodes
85       * @param destinations List&lt;? extends Node&gt;; 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 }