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.HashMap;
6   import java.util.HashSet;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.Set;
10  
11  import org.djunits.unit.FrequencyUnit;
12  import org.djunits.unit.TimeUnit;
13  import org.djunits.value.StorageType;
14  import org.djunits.value.ValueException;
15  import org.djunits.value.vdouble.scalar.Duration;
16  import org.djunits.value.vdouble.scalar.Frequency;
17  import org.djunits.value.vdouble.vector.DurationVector;
18  import org.djunits.value.vdouble.vector.FrequencyVector;
19  import org.opentrafficsim.core.geometry.OTSPoint3D;
20  import org.opentrafficsim.core.network.NetworkException;
21  import org.opentrafficsim.core.network.Node;
22  import org.opentrafficsim.core.network.OTSNetwork;
23  import org.opentrafficsim.core.network.OTSNode;
24  import org.opentrafficsim.core.network.route.Route;
25  
26  import nl.tudelft.simulation.language.Throw;
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-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
34   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
35   * <p>
36   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 15, 2016 <br>
37   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
38   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
39   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
40   */
41  public class ODMatrix implements Serializable
42  {
43  
44      /** */
45      private static final long serialVersionUID = 20160921L;
46  
47      /** Id. */
48      private final String id;
49  
50      /** Origin nodes. */
51      private final List<Node> origins;
52  
53      /** Destination nodes. */
54      private final List<Node> destinations;
55  
56      /** Categorization of demand data. */
57      private final Categorization categorization;
58  
59      /** Global time vector. */
60      private final DurationVector globalTimeVector;
61  
62      /** Global interpolation of the data. */
63      private final Interpolation globalInterpolation;
64  
65      /** Demand data per origin and destination, and possibly further categorization. */
66      private final Map<Node, Map<Node, Map<Category, ODEntry>>> demandData = new HashMap<>();
67  
68      /**
69       * Constructs an OD matrix.
70       * @param id id
71       * @param origins origin nodes
72       * @param destinations destination nodes
73       * @param categorization categorization of data
74       * @param globalTimeVector default time
75       * @param globalInterpolation interpolation of demand data
76       * @throws NullPointerException if any input is null
77       */
78      public ODMatrix(final String id, final List<Node> origins, final List<Node> destinations,
79          final Categorization categorization, final DurationVector globalTimeVector, final Interpolation globalInterpolation)
80      {
81          Throw.whenNull(id, "Id may not be null.");
82          Throw.when(origins == null || origins.contains(null), NullPointerException.class,
83              "Origin may not be or contain null.");
84          Throw.when(destinations == null || destinations.contains(null), NullPointerException.class,
85              "Destination may not be or contain null.");
86          Throw.whenNull(categorization, "Categorization may not be null.");
87          Throw.whenNull(globalTimeVector, "Global time vector may not be null.");
88          Throw.whenNull(globalInterpolation, "Global interpolation may not be null.");
89          this.id = id;
90          this.origins = new ArrayList<>(origins);
91          this.destinations = new ArrayList<>(destinations);
92          this.categorization = categorization;
93          this.globalTimeVector = globalTimeVector;
94          this.globalInterpolation = globalInterpolation;
95          // build empty OD
96          for (Node origin : origins)
97          {
98              Map<Node, Map<Category, ODEntry>> map = new HashMap<>();
99              for (Node destination : destinations)
100             {
101                 map.put(destination, new HashMap<>());
102             }
103             this.demandData.put(origin, map);
104         }
105     }
106 
107     /**
108      * @return id.
109      */
110     public final String getId()
111     {
112         return this.id;
113     }
114 
115     /**
116      * @return origins.
117      */
118     public final List<Node> getOrigins()
119     {
120         return new ArrayList<>(this.origins);
121     }
122 
123     /**
124      * @return destinations.
125      */
126     public final List<Node> getDestinations()
127     {
128         return new ArrayList<>(this.destinations);
129     }
130 
131     /**
132      * @return categorization.
133      */
134     public final Categorization getCategorization()
135     {
136         return this.categorization;
137     }
138 
139     /**
140      * @return globalTimeVector.
141      */
142     public final DurationVector getGlobalTimeVector()
143     {
144         return this.globalTimeVector;
145     }
146 
147     /**
148      * @return globalInterpolation.
149      */
150     public final Interpolation getGlobalInterpolation()
151     {
152         return this.globalInterpolation;
153     }
154 
155     /**
156      * @param origin origin
157      * @param destination destination
158      * @param category category
159      * @param demand demand data, length has to be equal to the global time vector
160      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
161      * @throws IllegalArgumentException if the category does not belong to the categorization
162      * @throws NullPointerException if an input is null
163      */
164     public final void putDemandVector(final Node origin, final Node destination, final Category category,
165         final FrequencyVector demand)
166     {
167         putDemandVector(origin, destination, category, demand, this.globalTimeVector, this.globalInterpolation);
168     }
169 
170     /**
171      * @param origin origin
172      * @param destination destination
173      * @param category category
174      * @param demand demand data, length has to be equal to the time vector
175      * @param timeVector time vector
176      * @param interpolation interpolation
177      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
178      * @throws IllegalArgumentException if the category does not belong to the categorization
179      * @throws NullPointerException if an input is null
180      */
181     public final void putDemandVector(final Node origin, final Node destination, final Category category,
182         final FrequencyVector demand, final DurationVector timeVector, final Interpolation interpolation)
183     {
184         Throw.whenNull(origin, "Origin may not be null.");
185         Throw.whenNull(destination, "Destination may not be null.");
186         Throw.whenNull(category, "Category may not be null.");
187         Throw.whenNull(demand, "Demand data may not be null.");
188         Throw.whenNull(timeVector, "Time vector may not be null.");
189         Throw.whenNull(interpolation, "Interpolation may not be null.");
190         Throw.when(!this.origins.contains(origin), IllegalArgumentException.class,
191             "Origin '%s' is not part of the OD matrix.", origin);
192         Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
193             "Destination '%s' is not part of the OD matrix.", destination);
194         Throw.when(!this.categorization.equals(category.getCategorization()), IllegalArgumentException.class,
195             "Provided category %s does not belong to the categorization %s.", category, this.categorization);
196         ODEntry odEntry = new ODEntry(demand, timeVector, interpolation); // performs checks on vector length
197         this.demandData.get(origin).get(destination).put(category, odEntry);
198     }
199 
200     /**
201      * @param origin origin
202      * @param destination destination
203      * @param category category
204      * @return demand data for given origin, destination and categorization, {@code null} if no data is given
205      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
206      * @throws IllegalArgumentException if the category does not belong to the categorization
207      * @throws NullPointerException if an input is null
208      */
209     public final FrequencyVector getDemandVector(final Node origin, final Node destination, final Category category)
210     {
211         ODEntry odEntry = getODEntry(origin, destination, category);
212         if (odEntry == null)
213         {
214             return null;
215         }
216         return odEntry.getDemandVector();
217     }
218 
219     /**
220      * @param origin origin
221      * @param destination destination
222      * @param category category
223      * @return interpolation for given origin, destination and categorization, {@code null} if no data is given
224      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
225      * @throws IllegalArgumentException if the category does not belong to the categorization
226      * @throws NullPointerException if an input is null
227      */
228     public final DurationVector getTimeVector(final Node origin, final Node destination, final Category category)
229     {
230         ODEntry odEntry = getODEntry(origin, destination, category);
231         if (odEntry == null)
232         {
233             return null;
234         }
235         return odEntry.getTimeVector();
236     }
237 
238     /**
239      * @param origin origin
240      * @param destination destination
241      * @param category category
242      * @return interpolation for given origin, destination and categorization, {@code null} if no data is given
243      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
244      * @throws IllegalArgumentException if the category does not belong to the categorization
245      * @throws NullPointerException if an input is null
246      */
247     public final Interpolation getInterpolation(final Node origin, final Node destination, final Category category)
248     {
249         ODEntry odEntry = getODEntry(origin, destination, category);
250         if (odEntry == null)
251         {
252             return null;
253         }
254         return odEntry.getInterpolation();
255     }
256 
257     /**
258      * Returns the demand at given time. If given time is before the first time slice or after the last time slice, 0 demand is
259      * returned.
260      * @param origin origin
261      * @param destination destination
262      * @param category category
263      * @param time time
264      * @return demand for given origin, destination and categorization, at given time
265      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
266      * @throws IllegalArgumentException if the category does not belong to the categorization
267      * @throws NullPointerException if an input is null
268      */
269     public final Frequency
270         getDemand(final Node origin, final Node destination, final Category category, final Duration time)
271     {
272         Throw.whenNull(time, "Time may not be null.");
273         ODEntry odEntry = getODEntry(origin, destination, category);
274         if (odEntry == null)
275         {
276             return new Frequency(0.0, FrequencyUnit.PER_HOUR); // Frequency.ZERO give "Hz" which is not nice for flow
277         }
278         return odEntry.getDemand(time);
279     }
280 
281     /**
282      * @param origin origin
283      * @param destination destination
284      * @param category category
285      * @return OD entry for given origin, destination and categorization.
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 NullPointerException if an input is null
289      */
290     private ODEntry getODEntry(final Node origin, final Node destination, final Category category)
291     {
292         Throw.whenNull(origin, "Origin may not be null.");
293         Throw.whenNull(destination, "Destination may not be null.");
294         Throw.whenNull(category, "Category may not be null.");
295         Throw.when(!this.origins.contains(origin), IllegalArgumentException.class,
296             "Origin '%s' is not part of the OD matrix", origin);
297         Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
298             "Destination '%s' is not part of the OD matrix.", destination);
299         Throw.when(!this.categorization.equals(category.getCategorization()), IllegalArgumentException.class,
300             "Provided category %s does not belong to the categorization %s.", category, this.categorization);
301         return this.demandData.get(origin).get(destination).get(category);
302     }
303     
304     /**
305      * @param origin origin
306      * @param destination destination
307      * @param category category
308      * @return whether there is data for the specified origin, destination and category
309      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
310      * @throws IllegalArgumentException if the category does not belong to the categorization
311      * @throws NullPointerException if an input is null
312      */
313     public final boolean contains(final Node origin, final Node destination, final Category category)
314     {
315         return getODEntry(origin, destination, category) != null;
316     }
317     
318     /**
319      * Returns the categories specified for given origin-destination combination.
320      * @param origin origin
321      * @param destination destination
322      * @return categories specified for given origin-destination combination
323      * @throws IllegalArgumentException if origin or destination is not part of the OD matrix
324      * @throws NullPointerException if an input is null
325      */
326     public final Set<Category> getCategories(final Node origin, final Node destination)
327     {
328         Throw.whenNull(origin, "Origin may not be null.");
329         Throw.whenNull(destination, "Destination may not be null.");
330         Throw.when(!this.origins.contains(origin), IllegalArgumentException.class,
331             "Origin '%s' is not part of the OD matrix", origin);
332         Throw.when(!this.destinations.contains(destination), IllegalArgumentException.class,
333             "Destination '%s' is not part of the OD matrix.", destination);
334         return new HashSet<>(this.demandData.get(origin).get(destination).keySet());
335     }
336 
337     /** {@inheritDoc} */
338     @SuppressWarnings("checkstyle:designforextension")
339     public String toString()
340     {
341         return "ODMatrix [" + this.origins.size() + " origins, " + this.destinations.size() + " destinations, "
342             + this.categorization + " ]";
343     }
344 
345     /**
346      * Prints the complete OD matrix with each demand data on a single line.
347      */
348     public final void print()
349     {
350         int originLength = 0;
351         for (Node origin : this.origins)
352         {
353             originLength = originLength >= origin.getId().length() ? originLength : origin.getId().length();
354         }
355         int destinLength = 0;
356         for (Node destination : this.destinations)
357         {
358             destinLength = destinLength >= destination.getId().length() ? destinLength : destination.getId().length();
359         }
360         String format = "%-" + originLength + "s -> %-" + destinLength + "s | ";
361         for (Node origin : this.origins)
362         {
363             Map<Node, Map<Category, ODEntry>> destinationMap = this.demandData.get(origin);
364             for (Node destination : this.destinations)
365             {
366                 Map<Category, ODEntry> categoryMap = destinationMap.get(destination);
367                 if (categoryMap.isEmpty())
368                 {
369                     System.out.println(String.format(format, origin.getId(), destination.getId()) + "-no data-");
370                 }
371                 else
372                 {
373                     for (Category category : categoryMap.keySet())
374                     {
375                         System.out.println(String.format(format, origin.getId(), destination.getId()) + category + " | "
376                             + categoryMap.get(category).getDemandVector());
377                     }
378                 }
379             }
380         }
381     }
382 
383     /** {@inheritDoc} */
384     @Override
385     public final int hashCode()
386     {
387         final int prime = 31;
388         int result = 1;
389         result = prime * result + ((this.categorization == null) ? 0 : this.categorization.hashCode());
390         result = prime * result + ((this.demandData == null) ? 0 : this.demandData.hashCode());
391         result = prime * result + ((this.destinations == null) ? 0 : this.destinations.hashCode());
392         result = prime * result + ((this.globalInterpolation == null) ? 0 : this.globalInterpolation.hashCode());
393         result = prime * result + ((this.globalTimeVector == null) ? 0 : this.globalTimeVector.hashCode());
394         result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
395         result = prime * result + ((this.origins == null) ? 0 : this.origins.hashCode());
396         return result;
397     }
398 
399     /** {@inheritDoc} */
400     @Override
401     public final boolean equals(final Object obj)
402     {
403         if (this == obj)
404         {
405             return true;
406         }
407         if (obj == null)
408         {
409             return false;
410         }
411         if (getClass() != obj.getClass())
412         {
413             return false;
414         }
415         ODMatrix other = (ODMatrix) obj;
416         if (this.categorization == null)
417         {
418             if (other.categorization != null)
419             {
420                 return false;
421             }
422         }
423         else if (!this.categorization.equals(other.categorization))
424         {
425             return false;
426         }
427         if (this.demandData == null)
428         {
429             if (other.demandData != null)
430             {
431                 return false;
432             }
433         }
434         else if (!this.demandData.equals(other.demandData))
435         {
436             return false;
437         }
438         if (this.destinations == null)
439         {
440             if (other.destinations != null)
441             {
442                 return false;
443             }
444         }
445         else if (!this.destinations.equals(other.destinations))
446         {
447             return false;
448         }
449         if (this.globalInterpolation != other.globalInterpolation)
450         {
451             return false;
452         }
453         if (this.globalTimeVector == null)
454         {
455             if (other.globalTimeVector != null)
456             {
457                 return false;
458             }
459         }
460         else if (!this.globalTimeVector.equals(other.globalTimeVector))
461         {
462             return false;
463         }
464         if (this.id == null)
465         {
466             if (other.id != null)
467             {
468                 return false;
469             }
470         }
471         else if (!this.id.equals(other.id))
472         {
473             return false;
474         }
475         if (this.origins == null)
476         {
477             if (other.origins != null)
478             {
479                 return false;
480             }
481         }
482         else if (!this.origins.equals(other.origins))
483         {
484             return false;
485         }
486         return true;
487     }
488 
489     // TODO remove this method as soon as there is a JUNIT test
490     public static void main(final String[] args) throws ValueException, NetworkException
491     {
492 
493         int aa = 10;
494         System.out.println(aa);
495         aa = aa + (aa >> 1);
496         System.out.println(aa);
497         aa = aa + (aa >> 1);
498         System.out.println(aa);
499         aa = aa + (aa >> 1);
500         System.out.println(aa);
501         aa = aa + (aa >> 1);
502         System.out.println(aa);
503 
504         OTSNetwork net = new OTSNetwork("test");
505         OTSPoint3D point = new OTSPoint3D(0, 0, 0);
506         Node a = new OTSNode(net, "A", point);
507         Node b = new OTSNode(net, "Barendrecht", point);
508         Node c = new OTSNode(net, "C", point);
509         Node d = new OTSNode(net, "Delft", point);
510         Node e = new OTSNode(net, "E", point);
511         List<Node> origins = new ArrayList<>();
512         origins.add(a);
513         origins.add(b);
514         origins.add(c);
515         List<Node> destinations = new ArrayList<>();
516         destinations.add(a);
517         destinations.add(c);
518         destinations.add(d);
519         Categorization categorization = new Categorization("test", Route.class, String.class);
520         Route ac1 = new Route("AC1");
521         Route ac2 = new Route("AC2");
522         Route ad1 = new Route("AD1");
523         Route bc1 = new Route("BC1");
524         Route bc2 = new Route("BC2");
525         Route bd1 = new Route("BD1");
526 
527         DurationVector timeVector = new DurationVector(new double[] {0, 1200, 3600}, TimeUnit.SECOND, StorageType.DENSE);
528         ODMatrix odMatrix = new ODMatrix("TestOD", origins, destinations, categorization, timeVector, Interpolation.LINEAR);
529 
530         Category category = new Category(categorization, ac1, "car");
531         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
532             StorageType.DENSE));
533         category = new Category(categorization, ac2, "car");
534         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
535             StorageType.DENSE));
536         category = new Category(categorization, ad1, "car");
537         odMatrix.putDemandVector(a, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
538             StorageType.DENSE));
539         category = new Category(categorization, ac1, "car");
540         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
541             StorageType.DENSE));
542         category = new Category(categorization, ac2, "truck");
543         odMatrix.putDemandVector(a, c, category, new FrequencyVector(new double[] {100, 200, 500}, FrequencyUnit.PER_HOUR,
544             StorageType.DENSE));
545         category = new Category(categorization, ad1, "truck");
546         odMatrix.putDemandVector(a, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
547             StorageType.DENSE));
548         category = new Category(categorization, bc1, "truck");
549         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
550             StorageType.DENSE));
551         category = new Category(categorization, bc2, "truck");
552         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
553             StorageType.DENSE));
554         category = new Category(categorization, bd1, "car");
555         odMatrix.putDemandVector(b, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
556             StorageType.DENSE));
557         category = new Category(categorization, bc1, "car");
558         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
559             StorageType.DENSE));
560         category = new Category(categorization, bc2, "car");
561         odMatrix.putDemandVector(b, c, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
562             StorageType.DENSE));
563         category = new Category(categorization, bd1, "truck");
564         odMatrix.putDemandVector(b, d, category, new FrequencyVector(new double[] {100, 200, 300}, FrequencyUnit.PER_HOUR,
565             StorageType.DENSE));
566 
567         odMatrix.print();
568         System.out.println(odMatrix);
569 
570         category = new Category(categorization, ac2, "truck");
571         for (double t = -100; t <= 3700; t += 100)
572         {
573             Duration time = new Duration(t, TimeUnit.SECOND);
574             System.out.println("@ t = " + time + ", q = " + odMatrix.getDemand(a, c, category, time));
575         }
576 
577         System.out.println("For OD       that does not exist; q = " + odMatrix.getDemand(c, a, category, Duration.ZERO));
578         category = new Category(categorization, ac2, "does not exist");
579         System.out.println("For category that does not exist; q = " + odMatrix.getDemand(a, c, category, Duration.ZERO));
580 
581     }
582 
583     /**
584      * An ODEntry contains a demand vector, and optionally a time vector and interpolation method that may differ from the
585      * global time vector or interpolation method.
586      * <p>
587      * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
588      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
589      * <p>
590      * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 16, 2016 <br>
591      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
592      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
593      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
594      */
595     private class ODEntry
596     {
597 
598         /** Demand vector. */
599         private final FrequencyVector demandVector;
600 
601         /** Time vector, may be null. */
602         private final DurationVector timeVector;
603 
604         /** Interpolation, may be null. */
605         private final Interpolation interpolation;
606 
607         /**
608          * @param demandVector demand vector
609          * @param timeVector time vector
610          * @param interpolation interpolation
611          * @throws IllegalArgumentException if the demand data has a different length than time data
612          */
613         ODEntry(final FrequencyVector demandVector, final DurationVector timeVector, final Interpolation interpolation)
614         {
615             Throw.when(demandVector.size() != timeVector.size(), IllegalArgumentException.class,
616                 "Demand data has different length than time vector.");
617             this.demandVector = demandVector;
618             this.timeVector = timeVector;
619             this.interpolation = interpolation;
620         }
621 
622         /**
623          * Returns the demand at given time. If given time is before the first time slice or after the last time slice, 0 demand
624          * is returned.
625          * @param time time of demand requested
626          * @return demand at given time
627          */
628         public final Frequency getDemand(final Duration time)
629         {
630             try
631             {
632                 // empty data or before start or after end, return 0
633                 if (this.timeVector.size() == 0 || time.lt(this.timeVector.get(0))
634                     || time.ge(this.timeVector.get(this.timeVector.size() - 1)))
635                 {
636                     return new Frequency(0.0, FrequencyUnit.PER_HOUR); // Frequency.ZERO give "Hz" which is not nice for flow
637                 }
638                 // interpolate
639                 for (int i = 0; i < this.timeVector.size() - 1; i++)
640                 {
641                     if (this.timeVector.get(i + 1).ge(time))
642                     {
643                         return this.interpolation.interpolate(this.demandVector.get(i), this.timeVector.get(i),
644                             this.demandVector.get(i + 1), this.timeVector.get(i + 1), time);
645                     }
646                 }
647             }
648             catch (ValueException ve)
649             {
650                 // should not happen, vector lengths are checked when given is input
651                 throw new RuntimeException("Index out of bounds.", ve);
652             }
653             // should not happen
654             throw new RuntimeException("Demand interpolation failed.");
655         }
656 
657         /**
658          * @return demandVector
659          */
660         final FrequencyVector getDemandVector()
661         {
662             return this.demandVector;
663         }
664 
665         /**
666          * @return timeVector
667          */
668         final DurationVector getTimeVector()
669         {
670             return this.timeVector;
671         }
672 
673         /**
674          * @return interpolation
675          */
676         final Interpolation getInterpolation()
677         {
678             return this.interpolation;
679         }
680 
681         /** {@inheritDoc} */
682         @Override
683         public final int hashCode()
684         {
685             final int prime = 31;
686             int result = 1;
687             result = prime * result + getOuterType().hashCode();
688             result = prime * result + ((this.demandVector == null) ? 0 : this.demandVector.hashCode());
689             result = prime * result + ((this.interpolation == null) ? 0 : this.interpolation.hashCode());
690             result = prime * result + ((this.timeVector == null) ? 0 : this.timeVector.hashCode());
691             return result;
692         }
693 
694         /** {@inheritDoc} */
695         @Override
696         public final boolean equals(final Object obj)
697         {
698             if (this == obj)
699             {
700                 return true;
701             }
702             if (obj == null)
703             {
704                 return false;
705             }
706             if (getClass() != obj.getClass())
707             {
708                 return false;
709             }
710             ODEntry other = (ODEntry) obj;
711             if (!getOuterType().equals(other.getOuterType()))
712             {
713                 return false;
714             }
715             if (this.demandVector == null)
716             {
717                 if (other.demandVector != null)
718                 {
719                     return false;
720                 }
721             }
722             else if (!this.demandVector.equals(other.demandVector))
723             {
724                 return false;
725             }
726             if (this.interpolation != other.interpolation)
727             {
728                 return false;
729             }
730             if (this.timeVector == null)
731             {
732                 if (other.timeVector != null)
733                 {
734                     return false;
735                 }
736             }
737             else if (!this.timeVector.equals(other.timeVector))
738             {
739                 return false;
740             }
741             return true;
742         }
743 
744         /**
745          * Accessor for hashcode and equals.
746          * @return encompassing OD matrix
747          */
748         private ODMatrix getOuterType()
749         {
750             return ODMatrix.this;
751         }
752 
753         /** {@inheritDoc} */
754         @Override
755         public String toString()
756         {
757             return "ODEntry [demandVector=" + this.demandVector + ", timeVector=" + this.timeVector + ", interpolation="
758                     + this.interpolation + "]";
759         }
760 
761     }
762 
763 }