1use thiserror::Error;
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub enum LosGrade {
65 A,
67 B,
69 C,
71 D,
73 E,
75 F,
77}
78
79impl std::fmt::Display for LosGrade {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 Self::A => write!(f, "A"),
83 Self::B => write!(f, "B"),
84 Self::C => write!(f, "C"),
85 Self::D => write!(f, "D"),
86 Self::E => write!(f, "E"),
87 Self::F => write!(f, "F"),
88 }
89 }
90}
91
92pub trait LosCriteria {
102 fn classify(&self, value: f64) -> LosGrade;
104
105 fn name(&self) -> &'static str;
107
108 fn unit(&self) -> &'static str;
110}
111
112#[derive(Debug, Clone, Copy)]
127pub struct PedestrianWalkway;
128
129impl LosCriteria for PedestrianWalkway {
130 fn classify(&self, density: f64) -> LosGrade {
131 if density <= 0.31 {
132 LosGrade::A
133 } else if density <= 0.43 {
134 LosGrade::B
135 } else if density <= 0.72 {
136 LosGrade::C
137 } else if density <= 1.08 {
138 LosGrade::D
139 } else if density <= 2.17 {
140 LosGrade::E
141 } else {
142 LosGrade::F
143 }
144 }
145 fn name(&self) -> &'static str {
146 "Pedestrian Walkway"
147 }
148 fn unit(&self) -> &'static str {
149 "pax/m2"
150 }
151}
152
153#[derive(Debug, Clone, Copy)]
171pub struct PedestrianStairway;
172
173impl LosCriteria for PedestrianStairway {
174 fn classify(&self, density: f64) -> LosGrade {
175 if density <= 0.54 {
176 LosGrade::A
177 } else if density <= 0.72 {
178 LosGrade::B
179 } else if density <= 1.08 {
180 LosGrade::C
181 } else if density <= 1.54 {
182 LosGrade::D
183 } else if density <= 2.70 {
184 LosGrade::E
185 } else {
186 LosGrade::F
187 }
188 }
189 fn name(&self) -> &'static str {
190 "Pedestrian Stairway"
191 }
192 fn unit(&self) -> &'static str {
193 "pax/m2"
194 }
195}
196
197#[derive(Debug, Clone, Copy)]
214pub struct PedestrianQueuing;
215
216impl LosCriteria for PedestrianQueuing {
217 fn classify(&self, density: f64) -> LosGrade {
218 if density <= 0.83 {
219 LosGrade::A
220 } else if density <= 1.11 {
221 LosGrade::B
222 } else if density <= 1.43 {
223 LosGrade::C
224 } else if density <= 3.33 {
225 LosGrade::D
226 } else if density <= 5.00 {
227 LosGrade::E
228 } else {
229 LosGrade::F
230 }
231 }
232 fn name(&self) -> &'static str {
233 "Pedestrian Queuing"
234 }
235 fn unit(&self) -> &'static str {
236 "pax/m2"
237 }
238}
239
240#[derive(Debug, Clone, Copy)]
257pub struct VehicularFreeway;
258
259impl LosCriteria for VehicularFreeway {
260 fn classify(&self, density_pc_km_ln: f64) -> LosGrade {
261 if density_pc_km_ln <= 7.0 {
262 LosGrade::A
263 } else if density_pc_km_ln <= 11.0 {
264 LosGrade::B
265 } else if density_pc_km_ln <= 16.0 {
266 LosGrade::C
267 } else if density_pc_km_ln <= 22.0 {
268 LosGrade::D
269 } else if density_pc_km_ln <= 28.0 {
270 LosGrade::E
271 } else {
272 LosGrade::F
273 }
274 }
275 fn name(&self) -> &'static str {
276 "Vehicular Freeway"
277 }
278 fn unit(&self) -> &'static str {
279 "pc/km/ln"
280 }
281}
282
283#[derive(Debug, Clone, Copy)]
301pub struct VehicularUrbanStreet;
302
303impl LosCriteria for VehicularUrbanStreet {
304 fn classify(&self, delay_s: f64) -> LosGrade {
305 if delay_s <= 10.0 {
306 LosGrade::A
307 } else if delay_s <= 20.0 {
308 LosGrade::B
309 } else if delay_s <= 35.0 {
310 LosGrade::C
311 } else if delay_s <= 55.0 {
312 LosGrade::D
313 } else if delay_s <= 80.0 {
314 LosGrade::E
315 } else {
316 LosGrade::F
317 }
318 }
319 fn name(&self) -> &'static str {
320 "Vehicular Urban Street"
321 }
322 fn unit(&self) -> &'static str {
323 "s/veh"
324 }
325}
326
327#[derive(Debug, Clone, Copy)]
345pub struct VehicularUnsignalized;
346
347impl LosCriteria for VehicularUnsignalized {
348 fn classify(&self, delay_s: f64) -> LosGrade {
349 if delay_s <= 10.0 {
350 LosGrade::A
351 } else if delay_s <= 15.0 {
352 LosGrade::B
353 } else if delay_s <= 25.0 {
354 LosGrade::C
355 } else if delay_s <= 35.0 {
356 LosGrade::D
357 } else if delay_s <= 50.0 {
358 LosGrade::E
359 } else {
360 LosGrade::F
361 }
362 }
363 fn name(&self) -> &'static str {
364 "Vehicular Unsignalized Intersection"
365 }
366 fn unit(&self) -> &'static str {
367 "s/veh"
368 }
369}
370
371#[derive(Debug, Clone, Copy)]
390pub struct BicycleFacility;
391
392impl LosCriteria for BicycleFacility {
393 fn classify(&self, events_per_min: f64) -> LosGrade {
394 if events_per_min <= 10.0 {
395 LosGrade::A
396 } else if events_per_min <= 20.0 {
397 LosGrade::B
398 } else if events_per_min <= 30.0 {
399 LosGrade::C
400 } else if events_per_min <= 40.0 {
401 LosGrade::D
402 } else if events_per_min <= 60.0 {
403 LosGrade::E
404 } else {
405 LosGrade::F
406 }
407 }
408 fn name(&self) -> &'static str {
409 "Bicycle Facility"
410 }
411 fn unit(&self) -> &'static str {
412 "events/min"
413 }
414}
415
416#[derive(Debug, Clone, Copy)]
434pub struct TransitCapacity;
435
436impl LosCriteria for TransitCapacity {
437 fn classify(&self, load_factor: f64) -> LosGrade {
438 if load_factor <= 0.50 {
439 LosGrade::A
440 } else if load_factor <= 0.75 {
441 LosGrade::B
442 } else if load_factor <= 1.00 {
443 LosGrade::C
444 } else if load_factor <= 1.25 {
445 LosGrade::D
446 } else if load_factor <= 1.50 {
447 LosGrade::E
448 } else {
449 LosGrade::F
450 }
451 }
452 fn name(&self) -> &'static str {
453 "Transit Capacity"
454 }
455 fn unit(&self) -> &'static str {
456 "load factor"
457 }
458}
459
460#[derive(Debug, Clone, Copy, PartialEq, Error)]
466pub enum LosCriteriaConfigError {
467 #[error("thresholds must be in strictly ascending order")]
468 NonAscendingThresholds,
469}
470
471#[derive(Debug, Clone)]
473pub struct CustomLosCriteria {
474 name: &'static str,
475 unit: &'static str,
476 thresholds: [f64; 5],
477}
478
479impl CustomLosCriteria {
480 pub fn new(
482 name: &'static str,
483 unit: &'static str,
484 thresholds: [f64; 5],
485 ) -> Result<Self, LosCriteriaConfigError> {
486 for i in 1..5 {
487 if thresholds[i] <= thresholds[i - 1] {
488 return Err(LosCriteriaConfigError::NonAscendingThresholds);
489 }
490 }
491 Ok(Self {
492 name,
493 unit,
494 thresholds,
495 })
496 }
497}
498
499impl LosCriteria for CustomLosCriteria {
500 fn classify(&self, value: f64) -> LosGrade {
501 if value <= self.thresholds[0] {
502 LosGrade::A
503 } else if value <= self.thresholds[1] {
504 LosGrade::B
505 } else if value <= self.thresholds[2] {
506 LosGrade::C
507 } else if value <= self.thresholds[3] {
508 LosGrade::D
509 } else if value <= self.thresholds[4] {
510 LosGrade::E
511 } else {
512 LosGrade::F
513 }
514 }
515 fn name(&self) -> &'static str {
516 self.name
517 }
518 fn unit(&self) -> &'static str {
519 self.unit
520 }
521}
522
523#[derive(Debug, Clone)]
529pub struct DensityStatistics {
530 pub max_density: f64,
532 pub mean_density: f64,
534 pub occupied_cells: usize,
536 pub total_agents: usize,
538 pub worst_los: LosGrade,
540 pub los_distribution: [usize; 6],
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Error)]
550pub enum DensityGridError {
551 #[error("extent must be positive")]
552 InvalidExtent,
553 #[error("cell_size must be positive")]
554 InvalidCellSize,
555}
556
557#[derive(Debug, Clone)]
559pub struct DensityGrid {
560 extent_x: f64,
561 extent_y: f64,
562 cell_size: f64,
563 cell_area: f64,
564 grid_w: usize,
565 grid_h: usize,
566 counts: Vec<u32>,
567 total_agents: usize,
568}
569
570impl DensityGrid {
571 pub fn new(extent_x: f64, extent_y: f64, cell_size: f64) -> Result<Self, DensityGridError> {
573 if extent_x <= 0.0 || extent_y <= 0.0 {
574 return Err(DensityGridError::InvalidExtent);
575 }
576 if cell_size <= 0.0 {
577 return Err(DensityGridError::InvalidCellSize);
578 }
579 let grid_w = (extent_x / cell_size).ceil() as usize;
580 let grid_h = (extent_y / cell_size).ceil() as usize;
581 Ok(Self {
582 extent_x,
583 extent_y,
584 cell_size,
585 cell_area: cell_size * cell_size,
586 grid_w,
587 grid_h,
588 counts: vec![0; grid_w * grid_h],
589 total_agents: 0,
590 })
591 }
592
593 pub fn grid_dimensions(&self) -> (usize, usize) {
595 (self.grid_w, self.grid_h)
596 }
597
598 pub fn cell_size(&self) -> f64 {
600 self.cell_size
601 }
602
603 pub fn extent(&self) -> (f64, f64) {
605 (self.extent_x, self.extent_y)
606 }
607
608 pub fn add_position(&mut self, x: f64, y: f64) {
612 let cx = ((x / self.cell_size).floor() as usize).min(self.grid_w.saturating_sub(1));
613 let cy = ((y / self.cell_size).floor() as usize).min(self.grid_h.saturating_sub(1));
614 let idx = cy * self.grid_w + cx;
615 self.counts[idx] += 1;
616 self.total_agents += 1;
617 }
618
619 pub fn add_positions(&mut self, positions: impl IntoIterator<Item = (f64, f64)>) {
621 for (x, y) in positions {
622 self.add_position(x, y);
623 }
624 }
625
626 pub fn count_at(&self, x: f64, y: f64) -> u32 {
628 let cx = ((x / self.cell_size).floor() as usize).min(self.grid_w.saturating_sub(1));
629 let cy = ((y / self.cell_size).floor() as usize).min(self.grid_h.saturating_sub(1));
630 self.counts[cy * self.grid_w + cx]
631 }
632
633 pub fn density_at(&self, x: f64, y: f64) -> f64 {
635 self.count_at(x, y) as f64 / self.cell_area
636 }
637
638 pub fn los_at(&self, x: f64, y: f64, criteria: &dyn LosCriteria) -> LosGrade {
640 criteria.classify(self.density_at(x, y))
641 }
642
643 pub fn density_at_cell(&self, cx: usize, cy: usize) -> f64 {
645 if cx >= self.grid_w || cy >= self.grid_h {
646 return 0.0;
647 }
648 self.counts[cy * self.grid_w + cx] as f64 / self.cell_area
649 }
650
651 pub fn count_at_cell(&self, cx: usize, cy: usize) -> u32 {
653 if cx >= self.grid_w || cy >= self.grid_h {
654 return 0;
655 }
656 self.counts[cy * self.grid_w + cx]
657 }
658
659 pub fn max_density(&self) -> f64 {
661 self.counts.iter().copied().max().unwrap_or(0) as f64 / self.cell_area
662 }
663
664 pub fn mean_density_occupied(&self) -> f64 {
668 let occupied: Vec<u32> = self.counts.iter().copied().filter(|&c| c > 0).collect();
669 if occupied.is_empty() {
670 return 0.0;
671 }
672 let sum: u32 = occupied.iter().sum();
673 (sum as f64 / occupied.len() as f64) / self.cell_area
674 }
675
676 pub fn mean_density_all(&self) -> f64 {
678 let total_area = self.grid_w as f64 * self.grid_h as f64 * self.cell_area;
679 if total_area == 0.0 {
680 return 0.0;
681 }
682 self.total_agents as f64 / total_area
683 }
684
685 pub fn statistics(&self, criteria: &dyn LosCriteria) -> DensityStatistics {
687 let mut max_count: u32 = 0;
688 let mut occupied_cells = 0usize;
689 let mut los_distribution = [0usize; 6];
690
691 for &count in &self.counts {
692 if count > 0 {
693 occupied_cells += 1;
694 if count > max_count {
695 max_count = count;
696 }
697 let density = count as f64 / self.cell_area;
698 let los = criteria.classify(density);
699 let idx = match los {
700 LosGrade::A => 0,
701 LosGrade::B => 1,
702 LosGrade::C => 2,
703 LosGrade::D => 3,
704 LosGrade::E => 4,
705 LosGrade::F => 5,
706 };
707 los_distribution[idx] += 1;
708 }
709 }
710
711 let max_density = max_count as f64 / self.cell_area;
712 let worst_los = criteria.classify(max_density);
713
714 let mean_density = if occupied_cells > 0 {
715 (self.total_agents as f64 / occupied_cells as f64) / self.cell_area
716 } else {
717 0.0
718 };
719
720 DensityStatistics {
721 max_density,
722 mean_density,
723 occupied_cells,
724 total_agents: self.total_agents,
725 worst_los,
726 los_distribution,
727 }
728 }
729
730 pub fn counts(&self) -> &[u32] {
732 &self.counts
733 }
734
735 pub fn total_agents(&self) -> usize {
737 self.total_agents
738 }
739
740 pub fn clear(&mut self) {
742 self.counts.fill(0);
743 self.total_agents = 0;
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750
751 #[test]
752 fn pedestrian_walkway_thresholds() {
753 let c = PedestrianWalkway;
754 assert_eq!(c.classify(0.0), LosGrade::A);
755 assert_eq!(c.classify(0.31), LosGrade::A);
756 assert_eq!(c.classify(0.32), LosGrade::B);
757 assert_eq!(c.classify(0.43), LosGrade::B);
758 assert_eq!(c.classify(0.44), LosGrade::C);
759 assert_eq!(c.classify(0.72), LosGrade::C);
760 assert_eq!(c.classify(0.73), LosGrade::D);
761 assert_eq!(c.classify(1.08), LosGrade::D);
762 assert_eq!(c.classify(1.09), LosGrade::E);
763 assert_eq!(c.classify(2.17), LosGrade::E);
764 assert_eq!(c.classify(2.18), LosGrade::F);
765 assert_eq!(c.classify(10.0), LosGrade::F);
766 }
767
768 #[test]
769 fn pedestrian_stairway_thresholds() {
770 let c = PedestrianStairway;
771 assert_eq!(c.classify(0.5), LosGrade::A);
772 assert_eq!(c.classify(0.55), LosGrade::B);
773 assert_eq!(c.classify(0.73), LosGrade::C);
774 assert_eq!(c.classify(1.1), LosGrade::D);
775 assert_eq!(c.classify(1.55), LosGrade::E);
776 assert_eq!(c.classify(3.0), LosGrade::F);
777 }
778
779 #[test]
780 fn pedestrian_queuing_thresholds() {
781 let c = PedestrianQueuing;
782 assert_eq!(c.classify(0.5), LosGrade::A);
783 assert_eq!(c.classify(0.9), LosGrade::B);
784 assert_eq!(c.classify(1.2), LosGrade::C);
785 assert_eq!(c.classify(2.0), LosGrade::D);
786 assert_eq!(c.classify(4.0), LosGrade::E);
787 assert_eq!(c.classify(6.0), LosGrade::F);
788 }
789
790 #[test]
791 fn vehicular_freeway_thresholds() {
792 let c = VehicularFreeway;
793 assert_eq!(c.classify(5.0), LosGrade::A);
794 assert_eq!(c.classify(9.0), LosGrade::B);
795 assert_eq!(c.classify(14.0), LosGrade::C);
796 assert_eq!(c.classify(20.0), LosGrade::D);
797 assert_eq!(c.classify(26.0), LosGrade::E);
798 assert_eq!(c.classify(35.0), LosGrade::F);
799 }
800
801 #[test]
802 fn vehicular_urban_street_thresholds() {
803 let c = VehicularUrbanStreet;
804 assert_eq!(c.classify(5.0), LosGrade::A);
805 assert_eq!(c.classify(15.0), LosGrade::B);
806 assert_eq!(c.classify(30.0), LosGrade::C);
807 assert_eq!(c.classify(45.0), LosGrade::D);
808 assert_eq!(c.classify(70.0), LosGrade::E);
809 assert_eq!(c.classify(100.0), LosGrade::F);
810 }
811
812 #[test]
813 fn vehicular_unsignalized_thresholds() {
814 let c = VehicularUnsignalized;
815 assert_eq!(c.classify(5.0), LosGrade::A);
816 assert_eq!(c.classify(12.0), LosGrade::B);
817 assert_eq!(c.classify(20.0), LosGrade::C);
818 assert_eq!(c.classify(30.0), LosGrade::D);
819 assert_eq!(c.classify(45.0), LosGrade::E);
820 assert_eq!(c.classify(60.0), LosGrade::F);
821 }
822
823 #[test]
824 fn bicycle_facility_thresholds() {
825 let c = BicycleFacility;
826 assert_eq!(c.classify(5.0), LosGrade::A);
827 assert_eq!(c.classify(15.0), LosGrade::B);
828 assert_eq!(c.classify(25.0), LosGrade::C);
829 assert_eq!(c.classify(35.0), LosGrade::D);
830 assert_eq!(c.classify(55.0), LosGrade::E);
831 assert_eq!(c.classify(70.0), LosGrade::F);
832 }
833
834 #[test]
835 fn transit_capacity_thresholds() {
836 let c = TransitCapacity;
837 assert_eq!(c.classify(0.3), LosGrade::A);
838 assert_eq!(c.classify(0.6), LosGrade::B);
839 assert_eq!(c.classify(0.9), LosGrade::C);
840 assert_eq!(c.classify(1.1), LosGrade::D);
841 assert_eq!(c.classify(1.4), LosGrade::E);
842 assert_eq!(c.classify(2.0), LosGrade::F);
843 }
844
845 #[test]
846 fn custom_criteria() {
847 let c = CustomLosCriteria::new("Test", "units", [1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
848 assert_eq!(c.classify(0.5), LosGrade::A);
849 assert_eq!(c.classify(1.5), LosGrade::B);
850 assert_eq!(c.classify(2.5), LosGrade::C);
851 assert_eq!(c.classify(3.5), LosGrade::D);
852 assert_eq!(c.classify(4.5), LosGrade::E);
853 assert_eq!(c.classify(5.5), LosGrade::F);
854 assert_eq!(c.name(), "Test");
855 assert_eq!(c.unit(), "units");
856 }
857
858 #[test]
859 fn custom_criteria_bad_order() {
860 let err = CustomLosCriteria::new("Bad", "x", [1.0, 3.0, 2.0, 4.0, 5.0]).unwrap_err();
861 assert_eq!(err, LosCriteriaConfigError::NonAscendingThresholds);
862 }
863
864 #[test]
865 fn basic_density() {
866 let mut grid = DensityGrid::new(10.0, 10.0, 1.0).unwrap();
867 grid.add_position(0.5, 0.5);
868 grid.add_position(0.1, 0.9);
869 grid.add_position(0.3, 0.3);
870 grid.add_position(0.9, 0.1);
871
872 assert_eq!(grid.count_at(0.5, 0.5), 4);
873 assert!((grid.density_at(0.5, 0.5) - 4.0).abs() < 1e-9);
874 assert_eq!(grid.los_at(0.5, 0.5, &PedestrianWalkway), LosGrade::F);
875 }
876
877 #[test]
878 fn empty_cell_density() {
879 let grid = DensityGrid::new(10.0, 10.0, 1.0).unwrap();
880 assert_eq!(grid.count_at(5.0, 5.0), 0);
881 assert!((grid.density_at(5.0, 5.0)).abs() < 1e-9);
882 assert_eq!(grid.los_at(5.0, 5.0, &PedestrianWalkway), LosGrade::A);
883 }
884
885 #[test]
886 fn statistics_with_walkway_criteria() {
887 let mut grid = DensityGrid::new(10.0, 10.0, 2.0).unwrap();
888 grid.add_position(1.0, 1.0);
890 grid.add_position(1.5, 1.5);
891 grid.add_position(5.0, 5.0);
893
894 let stats = grid.statistics(&PedestrianWalkway);
895 assert_eq!(stats.total_agents, 3);
896 assert_eq!(stats.occupied_cells, 2);
897 assert!((stats.max_density - 0.5).abs() < 1e-9);
898 assert_eq!(stats.worst_los, LosGrade::C);
899 assert_eq!(stats.los_distribution[0], 1); assert_eq!(stats.los_distribution[2], 1); }
902
903 #[test]
904 fn statistics_with_queuing_criteria() {
905 let mut grid = DensityGrid::new(10.0, 10.0, 2.0).unwrap();
906 grid.add_position(1.0, 1.0);
909 grid.add_position(1.5, 1.5);
910 grid.add_position(5.0, 5.0);
911
912 let stats = grid.statistics(&PedestrianQueuing);
913 assert_eq!(stats.worst_los, LosGrade::A); assert_eq!(stats.los_distribution[0], 2); }
916
917 #[test]
918 fn different_criteria_same_density() {
919 assert_eq!(PedestrianWalkway.classify(0.6), LosGrade::C);
924 assert_eq!(PedestrianStairway.classify(0.6), LosGrade::B);
925 assert_eq!(PedestrianQueuing.classify(0.6), LosGrade::A);
926 }
927
928 #[test]
929 fn clear_resets() {
930 let mut grid = DensityGrid::new(10.0, 10.0, 1.0).unwrap();
931 grid.add_position(0.5, 0.5);
932 assert_eq!(grid.total_agents(), 1);
933 grid.clear();
934 assert_eq!(grid.total_agents(), 0);
935 assert_eq!(grid.count_at(0.5, 0.5), 0);
936 }
937
938 #[test]
939 fn add_positions_batch() {
940 let mut grid = DensityGrid::new(10.0, 10.0, 1.0).unwrap();
941 let positions = vec![(0.5, 0.5), (0.1, 0.1), (5.0, 5.0)];
942 grid.add_positions(positions);
943 assert_eq!(grid.total_agents(), 3);
944 assert_eq!(grid.count_at(0.5, 0.5), 2);
945 assert_eq!(grid.count_at(5.0, 5.0), 1);
946 }
947
948 #[test]
949 fn clamping_out_of_bounds() {
950 let mut grid = DensityGrid::new(10.0, 10.0, 1.0).unwrap();
951 grid.add_position(-5.0, -5.0);
952 assert_eq!(grid.count_at(0.0, 0.0), 1);
953 grid.add_position(100.0, 100.0);
954 assert_eq!(grid.count_at(9.9, 9.9), 1);
955 }
956
957 #[test]
958 fn los_grade_ordering() {
959 assert!(LosGrade::A < LosGrade::B);
960 assert!(LosGrade::B < LosGrade::C);
961 assert!(LosGrade::E < LosGrade::F);
962 }
963
964 #[test]
965 fn los_grade_display() {
966 assert_eq!(format!("{}", LosGrade::A), "A");
967 assert_eq!(format!("{}", LosGrade::F), "F");
968 }
969
970 #[test]
971 fn criteria_metadata() {
972 assert_eq!(PedestrianWalkway.name(), "Pedestrian Walkway");
973 assert_eq!(PedestrianWalkway.unit(), "pax/m2");
974 assert_eq!(VehicularUrbanStreet.name(), "Vehicular Urban Street");
975 assert_eq!(VehicularUrbanStreet.unit(), "s/veh");
976 assert_eq!(TransitCapacity.name(), "Transit Capacity");
977 assert_eq!(TransitCapacity.unit(), "load factor");
978 }
979}