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