1use rand::{
2 Rng
3};
4
5const EMPTY_STATE: usize = std::usize::MAX;
6
7pub struct AsWaksmanTopology {
9 pub topology: Vec<Vec<(usize, usize)>>,
10 pub size: usize
11 }
13
14impl AsWaksmanTopology {
15 pub fn new(size: usize) -> Self {
16 assert!(size > 1, "don't make strange moves");
17
18 let num_colunms = Self::num_colunms(size);
19
20 let mut topology = vec![vec![(EMPTY_STATE, EMPTY_STATE); size]; num_colunms];
22
23 let destinations: Vec<usize> = (0..size).collect();
24
25 Self::construct_inner(0, num_colunms-1, 0, size-1, &destinations, &mut topology);
27
28 Self {
29 topology,
30 size
31 }
32 }
33
34 fn calculate_top_height(size: usize) -> usize {
35 size / 2
36 }
37
38 fn construct_inner(left: usize, right: usize,
39 low: usize, high: usize,
40 destinations: &[usize],
41 topology: &mut [Vec<(usize, usize)>])
42 {
43 if left > right {
44 return;
45 }
46
47 let rows_to_generate = high - low + 1;
49 assert_eq!(destinations.len(), rows_to_generate);
50
51 let columns_to_generate = Self::num_colunms(rows_to_generate);
52 let num_columns = right - left + 1;
53 assert!(num_columns >= columns_to_generate);
54
55 if num_columns > columns_to_generate
56 {
57 for idx in low..=high {
62 topology[left][idx].0 = idx;
63 topology[left][idx].1 = idx;
64
65 topology[right][idx].0 = destinations[idx - low];
66 topology[right][idx].1 = destinations[idx - low];
67 }
68 let mut subdestinations = vec![EMPTY_STATE; rows_to_generate];
69
70 for idx in low..=high {
71 subdestinations[idx-low] = idx;
72 }
73
74 Self::construct_inner(left+1, right-1, low, high, &subdestinations, topology);
75 } else if rows_to_generate == 2 {
76 topology[left][low].0 = destinations[0];
77 topology[left][high].1 = destinations[0];
78
79 topology[left][low].1 = destinations[1];
80 topology[left][high].0 = destinations[1];
81 } else {
82 let mut subdestinations = vec![EMPTY_STATE; rows_to_generate];
85 let limit = if rows_to_generate % 2 == 1 {
86 high
87 } else {
88 high + 1
89 };
90
91 for idx in (low..limit).step_by(2) {
92 let top_idx = Self::calculate_in_out_index(rows_to_generate, low, idx, true);
93 topology[left][idx].0 = top_idx;
94 topology[left][idx+1].1 = top_idx;
95
96 let bottom_idx = Self::calculate_in_out_index(rows_to_generate, low, idx, false);
97 topology[left][idx].1 = bottom_idx;
98 topology[left][idx+1].0 = bottom_idx;
99
100 subdestinations[(top_idx as usize) - low] = idx;
101 subdestinations[(bottom_idx as usize) - low] = idx + 1;
102
103 topology[right][idx].0 = destinations[idx - low];
104 topology[right][idx+1].1 = destinations[idx - low];
105
106 topology[right][idx].1 = destinations[idx + 1 - low];
107 topology[right][idx+1].0 = destinations[idx + 1 - low];
108 }
109
110 if rows_to_generate % 2 == 1
111 {
112 topology[left][high].0 = high;
113 topology[left][high].1 = high;
114
115 topology[right][high].0 = destinations[high - low];
116 topology[right][high].1 = destinations[high - low];
117
118 subdestinations[high - low] = high;
119 }
120 else
121 {
122 topology[left][high-1].1 = topology[left][high-1].0;
123 topology[left][high].1 = topology[left][high].0;
124 }
125
126 let d = AsWaksmanTopology::calculate_top_height(rows_to_generate);
127 let top_subnet_destinations = &subdestinations[..d];
128 let bottom_subnet_destinations = &subdestinations[d..];
129
130 Self::construct_inner(left+1, right-1, low, low + d - 1, top_subnet_destinations, topology);
131 Self::construct_inner(left+1, right-1, low + d, high, bottom_subnet_destinations, topology);
132 }
133 }
134
135 pub(crate) fn num_colunms(size: usize) -> usize {
136 if size <= 1 {
137 return 0;
138 }
139
140 let as_float = f64::from(size as u32);
141
142 let ceil = as_float.log(2.0).ceil();
143 let num_colunms = ceil as usize;
144
145 2*num_colunms - 1
146 }
147
148 fn calculate_in_out_index(
149 size: usize,
150 offset: usize,
151 index: usize,
152 is_top: bool
153 ) -> usize {
154 let mut relative_position = index - offset;
155 assert!(relative_position % 2 == 0 && relative_position + 1 < size);
156 relative_position >>= 1;
157 let mut suboffset = 0;
158 if !is_top {
159 suboffset = AsWaksmanTopology::calculate_top_height(size);
160 }
161
162 offset + relative_position + suboffset
163 }
164}
165
166#[derive(Debug)]
170pub struct IntegerPermutation {
171 pub elements: Vec<usize>,
172 pub min: usize,
173 pub max: usize
174}
175
176impl IntegerPermutation {
177 pub fn new(size: usize) -> Self {
178 Self::new_for_max_and_min(0, size - 1)
179 }
180
181 pub fn new_from_permutation(permutation: Vec<u64>) -> Self {
182 unimplemented!();
183 }
184
185 pub(crate) fn new_for_max_and_min(min: usize, max: usize) -> Self {
186 let elements: Vec<usize> = (min..=max).collect();
187
188 Self {
189 elements,
190 min: min,
191 max: max
192 }
193 }
194
195 pub fn size(&self) -> usize {
196 self.max - self.min + 1
197 }
199
200 pub fn make_permutation<R: Rng>(&mut self, rng: &mut R) {
201 let mut copy = self.elements.clone();
202 rng.shuffle(&mut copy);
203 self.elements = copy;
204 }
205
206 pub fn get(&self, index: usize) -> usize {
207 assert!(index >= self.min && index <= self.max);
208 self.elements[index - self.min]
209 }
210
211 pub fn set(&mut self, index: usize, value: usize) {
212 assert!(index >= self.min && index <= self.max);
213 self.elements[index - self.min] = value;
214 }
215
216 pub fn slice(&self, min: usize, max: usize) -> Self {
217 assert!(self.min <= min && min <= max && max <= self.max);
218
219 let result = Self {
220 elements: self.elements[(min - self.min)..(max - self.min + 1)].to_vec(),
221 min: min,
222 max: max
223 };
224
225 assert!(result.size() == result.elements.len());
226
227 result
228 }
229
230 pub fn inverse(&self) -> Self {
231 let mut new = Self::new_for_max_and_min(self.min, self.max);
232 for idx in self.min..=self.max {
233 new.elements[self.elements[idx - self.min] - self.min] = idx;
234 }
235
236 new
237 }
238
239 pub fn is_valid(&self) -> bool {
240 if self.elements.len() == 0 {
241 return true;
242 }
243
244 let mut set: std::collections::HashSet<usize, > = std::collections::HashSet::with_capacity(self.elements.len());
245 for element in self.elements.iter() {
246 if *element < self.min || *element > self.max {
247 return false;
248 }
249 if set.contains(element) {
250 return false;
251 }
252 set.insert(*element);
253 }
254
255 true
256 }
257}
258
259pub struct AsWaksmanRoute {
261 pub switches: Vec<std::collections::HashMap<usize, bool>>,
262 pub size: usize
263}
264
265impl AsWaksmanRoute {
266 pub fn new(permutation: &IntegerPermutation) -> Self {
267 let size = permutation.size();
268 let num_columns = AsWaksmanTopology::num_colunms(size);
269 let empty_assignment: std::collections::HashMap<usize, bool> = std::collections::HashMap::new();
270 let mut assignments = vec![empty_assignment; num_columns];
271
272 let inversed_permutation = permutation.inverse();
273 assert!(inversed_permutation.inverse().elements == permutation.elements);
274 Self::construct_inner(0, num_columns-1, 0, size-1, permutation, &inversed_permutation, &mut assignments);
275
276 Self {
277 switches: assignments,
278 size
279 }
280 }
281
282 fn get_canonical_row_index(offset: usize, row_index: usize) -> usize {
283 let suboffset = row_index - offset;
284
285 let mask = std::usize::MAX - 1;
286
287 (suboffset & mask) + offset
288 }
289
290 fn get_switch_setting_from_top_bottom_decision(offset: usize, packet_index: usize, is_top: bool) -> bool {
291 let row_index = Self::get_canonical_row_index(offset, packet_index);
292
293 (packet_index == row_index) ^ is_top
294 }
295
296 fn get_top_bottom_decision_from_switch_setting(offset: usize, packet_index: usize, is_on: bool) -> bool {
297 let row_index = Self::get_canonical_row_index(offset, packet_index);
298
299 (row_index == packet_index) ^ is_on
300 }
301
302 fn calculate_other_position(offset: usize, packet_index: usize) -> usize {
303 let row_index = Self::get_canonical_row_index(offset, packet_index);
304 (1 - (packet_index - row_index)) + row_index
305 }
306
307 fn construct_inner(
308 left: usize, right: usize,
309 low: usize, high: usize,
310 permutation: &IntegerPermutation,
311 permutation_inversed: &IntegerPermutation,
312 switches: &mut [std::collections::HashMap<usize, bool>]
313 ) {
314 if left > right
315 {
316 return;
317 }
318
319 let rows_to_generate = high - low + 1;
320 let columns_to_generate = AsWaksmanTopology::num_colunms(rows_to_generate);
321 let num_columns = right - left + 1;
322 assert!(num_columns >= columns_to_generate);
323
324 assert!(permutation.min == low);
325 assert!(permutation.max == high);
326 assert!(permutation.size() == rows_to_generate);
327 assert!(permutation.is_valid());
328 assert!(permutation.inverse().elements == permutation_inversed.elements);
329 assert!(permutation_inversed.inverse().elements == permutation.elements);
330
331 if num_columns > columns_to_generate
332 {
333 Self::construct_inner(left+1, right - 1, low, high, permutation, permutation_inversed, switches);
334 }
335 else if rows_to_generate == 2
336 {
337 assert!(permutation.get(low) == low || permutation.get(low) == low+1);
338 assert!(permutation.get(low+1) == low || permutation.get(low+1) == low+1);
339 assert!(permutation.get(low) != permutation.get(low+1));
340
341 switches[left].insert(low, permutation.get(low) != low);
342 }
343 else
344 {
345 let mut new_permutation = IntegerPermutation::new_for_max_and_min(low, high);
353 let mut new_permutation_inversed = IntegerPermutation::new_for_max_and_min(low, high);
354 let mut lhs_is_routed = vec![false; rows_to_generate];
355
356 let mut to_route;
357 let mut max_unrouted;
358
359 let mut should_route_left;
360
361 if rows_to_generate % 2 == 1
362 {
363 if permutation.get(high) == high
369 {
370 new_permutation.set(high, high);
375 new_permutation_inversed.set(high, high);
376 to_route = high - 1;
377 should_route_left = true;
378 }
379 else
380 {
381 let rhs_switch = Self::get_canonical_row_index(low, permutation.get(high));
387 let rhs_switch_setting = Self::get_switch_setting_from_top_bottom_decision(low, permutation.get(high), false);
388 switches[right].insert(rhs_switch, rhs_switch_setting);
389
390 let tprime = AsWaksmanTopology::calculate_in_out_index(rows_to_generate, low, rhs_switch, false);
391 new_permutation.set(high, tprime);
392 new_permutation_inversed.set(tprime, high);
393
394 to_route = Self::calculate_other_position(low, permutation.get(high));
395
396 should_route_left = false;
397 }
398
399 lhs_is_routed[high - low] = true;
400 max_unrouted = high - 1;
401 }
402 else
403 {
404 switches[left].insert(high - 1, false);
409 to_route = high;
410 should_route_left = true;
411 max_unrouted = high;
412 }
413
414 loop
415 {
416 if should_route_left
421 {
422 let lhs_switch = Self::get_canonical_row_index(low, to_route);
424 if switches[left].get(&lhs_switch).is_none()
425 {
426 switches[left].insert(lhs_switch, false);
427 }
428 let lhs_switch_setting = *switches[left].get(&lhs_switch).unwrap();
429 let should_use_top = Self::get_top_bottom_decision_from_switch_setting(low, to_route, lhs_switch_setting);
430 let t = AsWaksmanTopology::calculate_in_out_index(rows_to_generate, low, lhs_switch, should_use_top);
431 if permutation.get(to_route) == high
432 {
433 new_permutation.set(t, high);
438 new_permutation_inversed.set(high, t);
439 lhs_is_routed[to_route - low] = true;
440 to_route = max_unrouted;
441 should_route_left = true;
442 }
443 else
444 {
445 let rhs_switch = Self::get_canonical_row_index(low, permutation.get(to_route));
446 assert!(switches[right].get(&rhs_switch).is_none());
451
452 let switch_setting = Self::get_switch_setting_from_top_bottom_decision(low, permutation.get(to_route), should_use_top);
453 switches[right].insert(rhs_switch, switch_setting);
454 let tprime = AsWaksmanTopology::calculate_in_out_index(rows_to_generate, low, rhs_switch, should_use_top);
455 new_permutation.set(t, tprime);
456 new_permutation_inversed.set(tprime, t);
457
458 lhs_is_routed[to_route - low] = true;
459 to_route = Self::calculate_other_position(low, permutation.get(to_route));
460 should_route_left = false;
461 }
462 }
463 else
464 {
465 let rhs_switch = Self::get_canonical_row_index(low, to_route);
470 let lhs_switch = Self::get_canonical_row_index(low, permutation_inversed.get(to_route));
471 assert!(switches[right].get(&rhs_switch).is_some());
472 let rhs_switch_setting = *switches[right].get(&rhs_switch).unwrap();
473 let should_use_top = Self::get_top_bottom_decision_from_switch_setting(low, to_route, rhs_switch_setting);
474 let lhs_switch_setting = Self::get_switch_setting_from_top_bottom_decision(low, permutation_inversed.get(to_route) as usize, should_use_top);
475
476 if let Some(value) = switches[left].get(&lhs_switch) {
478 assert!(*value == lhs_switch_setting);
479 }
480
481 switches[left].insert(lhs_switch, lhs_switch_setting);
482
483 let t = AsWaksmanTopology::calculate_in_out_index(rows_to_generate, low, rhs_switch, should_use_top);
484 let tprime = AsWaksmanTopology::calculate_in_out_index(rows_to_generate, low, lhs_switch, should_use_top);
485 new_permutation.set(tprime, t);
486 new_permutation_inversed.set(t, tprime);
487
488 lhs_is_routed[permutation_inversed.get(to_route) - low] = true;
489 to_route = Self::calculate_other_position(low, permutation_inversed.get(to_route));
490 should_route_left = true;
491 }
492
493 if !should_route_left || !lhs_is_routed[to_route-low]
495 {
496 continue;
497 }
498
499 while max_unrouted > low && lhs_is_routed[max_unrouted-low]
501 {
502 max_unrouted -= 1;
503 }
504
505 if max_unrouted < low || (max_unrouted == low && lhs_is_routed[0])
506 {
507 break;
509 }
510 else
511 {
512 to_route = max_unrouted;
513 should_route_left = true;
514 }
515 }
516
517 if rows_to_generate % 2 == 0
518 {
519 switches[left].remove(&(high - 1));
521 }
522
523 assert!(new_permutation.is_valid());
524 assert!(new_permutation_inversed.is_valid());
525
526 let d = AsWaksmanTopology::calculate_top_height(rows_to_generate);
527 let new_permutation_upper = new_permutation.slice(low, low + d - 1);
528 let new_permutation_lower = new_permutation.slice(low + d, high);
529
530 let new_permutation_inversed_upper = new_permutation_inversed.slice(low, low + d - 1);
531 let new_permutation_inversed_lower = new_permutation_inversed.slice(low + d, high);
532
533 Self::construct_inner(left+1, right-1, low, low + d - 1, &new_permutation_upper, &new_permutation_inversed_upper, switches);
534 Self::construct_inner(left+1, right-1, low + d, high, &new_permutation_lower, &new_permutation_inversed_lower, switches);
535 }
536 }
537
538 fn validate_routing_for_permutation(permutation: &IntegerPermutation,
539 routing: &Self) -> bool
540 {
541 let size = permutation.size();
542 let num_columns = AsWaksmanTopology::num_colunms(size);
543 let topology = AsWaksmanTopology::new(size);
544
545 let mut current_perm = IntegerPermutation::new(size);
546
547 for column_idx in 0..num_columns {
548 let mut next_perm = IntegerPermutation::new(size);
549 for packet_idx in 0..size {
550 let routed_index;
551 if topology.topology[column_idx][packet_idx].0 == topology.topology[column_idx][packet_idx].1 {
552 routed_index = topology.topology[column_idx][packet_idx].0;
554 }
555 else
556 {
557 let a = routing.switches[column_idx].get(&packet_idx);
558 let b = if packet_idx > 0 {
559 routing.switches[column_idx].get(&(packet_idx - 1))
560 } else {
561 None
562 };
563 assert!(a.is_some() ^ b.is_some());
564 let switch_setting = if a.is_some() {
565 *a.unwrap()
566 } else {
567 *b.unwrap()
568 };
569
570 routed_index = if switch_setting {
571 topology.topology[column_idx][packet_idx].1
572 } else {
573 topology.topology[column_idx][packet_idx].0
574 };
575 }
576
577 let val = current_perm.get(packet_idx);
578 next_perm.set(routed_index, val);
579 }
580
581 current_perm = next_perm;
582 }
583
584 current_perm.elements == permutation.inverse().elements
585 }
586
587 fn get_number_of_gates(&self) -> usize {
588 let mut result = 0;
589 for column in self.switches.iter() {
590 result += column.len();
591 }
592
593 result
594 }
595
596 fn assign_switches(&mut self, switch_assignments: &[bool]) {
597 let required_switches = self.get_number_of_gates();
598 assert!(switch_assignments.len() == required_switches);
599 let mut i = 0;
600 for column in self.switches.iter_mut() {
601 let mut keys = column.keys().cloned().into_iter().collect::<Vec<_>>();
602 keys.sort();
603 keys.reverse();
604 for k in keys.into_iter() {
605 column.insert(k, switch_assignments[i]);
606 i += 1;
607 }
608 }
609 }
610
611 fn dump_assignments(&self) -> Vec<bool> {
612 let mut result = vec![false; self.get_number_of_gates()];
613 let mut i = 0;
614 for column in self.switches.iter() {
615 let mut keys = column.keys().cloned().into_iter().collect::<Vec<_>>();
616 keys.sort();
617 keys.reverse();
618 for k in keys.into_iter() {
619 result[i] = *column.get(&k).unwrap();
620 i += 1;
621 }
622 }
623
624 result
625 }
626
627
628 fn calculate_permutation(&self) -> IntegerPermutation
631 {
632 let num_columns = AsWaksmanTopology::num_colunms(self.size);
633 let topology = AsWaksmanTopology::new(self.size);
634
635 let mut permutation = IntegerPermutation::new(self.size);
636
637 for column_idx in 0..num_columns {
638 let mut permutation_by_this_column = IntegerPermutation::new(self.size);
639 for packet_idx in 0..self.size {
640 let routed_into;
641 if topology.topology[column_idx][packet_idx].0 == topology.topology[column_idx][packet_idx].1 {
642 routed_into = topology.topology[column_idx][packet_idx].0;
644 }
645 else
646 {
647 let a = self.switches[column_idx].get(&packet_idx);
648 let b = if packet_idx > 0 {
649 self.switches[column_idx].get(&(packet_idx - 1))
650 } else {
651 None
652 };
653 assert!(a.is_some() ^ b.is_some());
654 let switch_setting = if a.is_some() {
655 *a.unwrap()
656 } else {
657 *b.unwrap()
658 };
659
660 routed_into = if switch_setting {
661 topology.topology[column_idx][packet_idx].1
662 } else {
663 topology.topology[column_idx][packet_idx].0
664 };
665 }
666
667 let value_at_this_level = permutation.get(packet_idx);
668 permutation_by_this_column.set(routed_into, value_at_this_level);
669 }
670
671 permutation = permutation_by_this_column;
673 }
674
675 permutation.inverse()
676 }
677}
678
679#[test]
680fn test_aswaksman() {
681 use rand::{Rand, thread_rng};
682 let size = 3;
683
684 let mut permutation = IntegerPermutation::new(size);
685 let rng = &mut thread_rng();
686 permutation.make_permutation(rng);
687 let no_permutation = IntegerPermutation::new(size);
691 assert!(permutation.inverse().inverse().elements == permutation.elements);
692
693 let router = AsWaksmanRoute::new(&permutation);
694
695 let is_valid = AsWaksmanRoute::validate_routing_for_permutation(&permutation, &router);
696}
697
698#[test]
699fn test_back_and_forward_pass() {
700 use rand::{Rand, thread_rng};
701 let rng = &mut thread_rng();
702 for size in 3..4 {
703 let mut permutation = IntegerPermutation::new(size);
704 permutation.make_permutation(rng);
705
706 let router = AsWaksmanRoute::new(&permutation);
707 println!("Permutation = {:?}", permutation.elements);
708
709 let is_valid = AsWaksmanRoute::validate_routing_for_permutation(&permutation, &router);
710 assert!(is_valid);
711
712 let new_shuffle = router.calculate_permutation();
713 println!("Obtained shuffle = {:?}", new_shuffle.elements);
714 assert!(new_shuffle.elements == permutation.elements);
715 }
716}
717
718#[test]
719fn test_forward_pass() {
720 use rand::{Rand, thread_rng};
721 let rng = &mut thread_rng();
722 for size in 3..9 {
723 let mut permutation = IntegerPermutation::new(size);
724 permutation.make_permutation(rng);
725
726 let router = AsWaksmanRoute::new(&permutation);
727
728 let is_valid = AsWaksmanRoute::validate_routing_for_permutation(&permutation, &router);
729 assert!(is_valid);
730
731 let dump = router.dump_assignments();
732 let no_permutation = IntegerPermutation::new(size);
733 let mut router = AsWaksmanRoute::new(&no_permutation);
734 router.assign_switches(&dump);
735 let shuffle = router.calculate_permutation();
736
737 assert!(shuffle.elements == permutation.elements);
738 }
739}
740
741#[test]
742fn test_trivial_permutations() {
743 use rand::{Rand, thread_rng};
744 let rng = &mut thread_rng();
745 let topology = AsWaksmanTopology::new(3);
746 for _ in 0..100 {
748 for size in 2..128 {
749 let mut permutation = IntegerPermutation::new(size);
750 permutation.make_permutation(rng);
751 assert!(permutation.inverse().inverse().elements == permutation.elements);
752 }
753 }
754}
755
756#[test]
757fn test_routing_for_permutation() {
758 use rand::{Rand, thread_rng};
759 let rng = &mut thread_rng();
760 let topology = AsWaksmanTopology::new(3);
761 for size in 2..128 {
763 println!("size = {}", size);
764 for _ in 0..100 {
765 let mut permutation = IntegerPermutation::new(size);
766 permutation.make_permutation(rng);
768 assert!(permutation.inverse().inverse().elements == permutation.elements);
770
771 let router = AsWaksmanRoute::new(&permutation);
772
773 let is_valid = AsWaksmanRoute::validate_routing_for_permutation(&permutation, &router);
774 assert!(is_valid);
775 }
776 }
777}
778
779#[test]
780fn test_uniformity() {
781 use rand::{Rand, thread_rng};
782 let rng = &mut thread_rng();
783 let size = 64;
784 let mut hists: Vec<std::collections::HashMap<usize, f64>> = vec![std::collections::HashMap::new(); size];
785 let permutation = IntegerPermutation::new(size);
786 let mut router = AsWaksmanRoute::new(&permutation);
787 let num_switches = router.get_number_of_gates();
788 let num_trials = 1000000;
789 for _ in 0..num_trials {
790 let switches: Vec<bool> = (0..num_switches).map(|_| rng.gen::<bool>()).collect();
791 router.assign_switches(&switches);
792 let shuffle = router.calculate_permutation();
793 for i in 0..size {
794 let subhist = &mut hists[i];
795 let routed_into = shuffle.elements[i];
796 if subhist.get(&routed_into).is_some() {
797 let e = subhist.get_mut(&routed_into).unwrap();
798 *e += 1.0;
799 } else {
800 subhist.insert(routed_into, 1.0);
801 }
802 }
803 }
804
805 let mut min_xi_squared = 10000000000f64;
806 let mut max_xi_squared = 0.0f64;
807
808 let mut max_idx = 0;
809 let mut min_idx = 0;
810
811 for i in 0..size {
812 let subhist = &hists[i];
813 let mut xi_squared = 0.0f64;
814 let expected = (num_trials as f64) / (size as f64);
815 for (_, v) in subhist.iter() {
816 xi_squared += (v - expected)*(v - expected)/expected;
817 }
818
819 if xi_squared > max_xi_squared {
820 max_xi_squared = xi_squared;
821 max_idx = i;
822 }
823
824 if xi_squared < min_xi_squared {
825 min_xi_squared = xi_squared;
826 min_idx = i;
827 }
828 }
829
830 println!("Max XI^2 = {} for bin {}", max_xi_squared, max_idx);
831 println!("Min XI^2 = {} for bin {}", min_xi_squared, min_idx);
832
833 }