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