1use crate::arithmetic::{Integer, IntegerRef, Rational, RationalRef};
19use crate::cli::Parallel;
20use crate::parallelism::RangeStrategy;
21use crate::types::{Election, ElectionResult};
22use crate::vote_count::{self, VoteCount, VoteCountingThreadPool};
23use log::{debug, info, log_enabled, warn, Level};
24use std::fmt::{self, Debug, Display};
25use std::io;
26use std::marker::PhantomData;
27use std::num::NonZeroUsize;
28use std::time::Instant;
29
30#[allow(clippy::too_many_arguments)]
33pub fn stv_droop<I, R>(
34 stdout: &mut impl io::Write,
35 election: &Election,
36 package_name: &str,
37 omega_exponent: usize,
38 parallel: Parallel,
39 num_threads: Option<NonZeroUsize>,
40 disable_work_stealing: bool,
41 force_positive_surplus: bool,
42 equalize: bool,
43) -> io::Result<ElectionResult>
44where
45 I: Integer + Send + Sync,
46 for<'a> &'a I: IntegerRef<I>,
47 R: Rational<I> + Send + Sync,
48 for<'a> &'a R: RationalRef<&'a I, R>,
49{
50 info!(
51 "Parallel vote counting is {}",
52 match parallel {
53 Parallel::No => "disabled",
54 Parallel::Rayon => "enabled with rayon",
55 Parallel::Custom => "enabled using custom threads",
56 }
57 );
58 info!(
59 "Equalized vote counting is {}",
60 if equalize { "enabled" } else { "disabled" }
61 );
62
63 let pascal = if equalize {
64 Some(vote_count::pascal::<I>(election.num_candidates))
65 } else {
66 None
67 };
68 let pascal = pascal.as_deref();
69
70 match parallel {
71 Parallel::No => {
72 let state = State::<I, R>::new(
73 election,
74 omega_exponent,
75 parallel,
76 force_positive_surplus,
77 pascal,
78 None,
79 );
80 state.run(stdout, package_name, omega_exponent)
81 }
82 Parallel::Rayon => {
83 if let Some(num_threads) = num_threads {
84 info!("Spawning {num_threads} rayon threads");
85 rayon::ThreadPoolBuilder::new()
86 .num_threads(num_threads.into())
87 .build_global()
88 .unwrap();
89 }
90
91 let state = State::<I, R>::new(
92 election,
93 omega_exponent,
94 parallel,
95 force_positive_surplus,
96 pascal,
97 None,
98 );
99 state.run(stdout, package_name, omega_exponent)
100 }
101 Parallel::Custom => std::thread::scope(|thread_scope| -> io::Result<ElectionResult> {
102 let num_threads: NonZeroUsize = num_threads.unwrap_or_else(|| {
103 let count = match std::thread::available_parallelism() {
104 Ok(num_threads) => num_threads.get(),
105 Err(e) => {
106 warn!("Failed to query available parallelism: {e:?}");
107 1
108 }
109 };
110 NonZeroUsize::new(count)
111 .expect("A positive number of threads must be spawned, but the available parallelism is zero threads")
112 });
113 info!("Spawning {num_threads} threads");
114 let thread_pool = VoteCountingThreadPool::new(
115 thread_scope,
116 num_threads,
117 if disable_work_stealing {
118 RangeStrategy::Fixed
119 } else {
120 RangeStrategy::WorkStealing
121 },
122 election,
123 pascal,
124 );
125
126 let state = State::<I, R>::new(
127 election,
128 omega_exponent,
129 parallel,
130 force_positive_surplus,
131 pascal,
132 Some(thread_pool),
133 );
134 state.run(stdout, package_name, omega_exponent)
135 }),
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141enum Status {
142 Candidate,
144 Withdrawn,
146 Elected,
148 NotElected,
150}
151
152#[derive(Debug, PartialEq, Eq)]
154enum DroopIteration {
155 Elected,
157 Stable,
159 Omega,
161}
162
163impl Display for DroopIteration {
164 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165 match self {
166 DroopIteration::Elected => f.write_str("elected"),
167 DroopIteration::Stable => f.write_str("stable"),
168 DroopIteration::Omega => f.write_str("omega"),
169 }
170 }
171}
172
173pub struct State<'scope, 'e, I, R> {
175 election: &'e Election,
177 statuses: Vec<Status>,
179 elected: Vec<usize>,
181 not_elected: Vec<usize>,
183 to_elect: usize,
185 keep_factors: Vec<R>,
187 threshold: R,
189 surplus: R,
192 omega: R,
195 parallel: Parallel,
197 force_positive_surplus: bool,
200 pascal: Option<&'e [Vec<I>]>,
203 thread_pool: Option<VoteCountingThreadPool<'scope, I, R>>,
206 _phantom: PhantomData<I>,
207}
208
209#[cfg(test)]
210impl<'e, I, R> State<'_, 'e, I, R> {
211 fn builder() -> test::StateBuilder<'e, I, R> {
212 test::StateBuilder::default()
213 }
214}
215
216impl<'scope, 'e, I, R> State<'scope, 'e, I, R>
217where
218 I: Integer + Send + Sync + 'e,
219 for<'a> &'a I: IntegerRef<I>,
220 R: Rational<I> + Send + Sync + 'scope,
221 for<'a> &'a R: RationalRef<&'a I, R>,
222{
223 fn new<'a>(
224 election: &'a Election,
225 omega_exponent: usize,
226 parallel: Parallel,
227 force_positive_surplus: bool,
228 pascal: Option<&'a [Vec<I>]>,
229 thread_pool: Option<VoteCountingThreadPool<'scope, I, R>>,
230 ) -> State<'scope, 'a, I, R> {
231 State {
232 election,
233 statuses: election
234 .candidates
235 .iter()
236 .map(|c| {
237 if c.is_withdrawn {
238 Status::Withdrawn
239 } else {
240 Status::Candidate
241 }
242 })
243 .collect(),
244 elected: Vec::new(),
245 not_elected: Vec::new(),
246 to_elect: election.num_seats,
247 keep_factors: vec![R::one(); election.candidates.len()],
248 threshold: VoteCount::<I, R>::threshold_exhausted(election, &R::zero()),
249 surplus: R::zero(),
250 omega: R::one() / (0..omega_exponent).map(|_| I::from_usize(10)).product(),
251 parallel,
252 force_positive_surplus,
253 pascal,
254 thread_pool,
255 _phantom: PhantomData,
256 }
257 }
258
259 fn run(
261 mut self,
262 stdout: &mut impl io::Write,
263 package_name: &str,
264 omega_exponent: usize,
265 ) -> io::Result<ElectionResult> {
266 let beginning = Instant::now();
267 let mut timestamp = beginning;
268
269 let mut count = self.start_election(stdout, package_name, omega_exponent)?;
270
271 for round in 1.. {
272 if self.election_completed(stdout, round)? {
273 break;
274 }
275
276 self.election_round(stdout, &mut count)?;
277
278 if log_enabled!(Level::Debug) {
279 let now = Instant::now();
280 debug!(
281 "Elapsed time to compute this round: {:?} / total: {:?}",
282 now.duration_since(timestamp),
283 now.duration_since(beginning)
284 );
285 timestamp = now;
286 }
287 }
288
289 self.handle_remaining_candidates(stdout, &mut count)?;
290
291 self.write_action(stdout, "Count Complete", &count, true)?;
292
293 if log_enabled!(Level::Info) {
294 let now = Instant::now();
295 info!("Total elapsed time: {:?}", now.duration_since(beginning));
296 }
297
298 let result = ElectionResult {
299 elected: self.elected,
300 not_elected: self.not_elected,
301 };
302
303 writeln!(stdout)?;
304 Ok(result)
305 }
306
307 fn elect_candidate(&mut self, i: usize) {
309 info!(
310 "Electing candidate #{i} ({})",
311 self.election.candidates[i].nickname
312 );
313 assert!(self.to_elect != 0);
314 self.elected.push(i);
315 self.statuses[i] = Status::Elected;
316 self.to_elect -= 1;
317 }
318
319 fn defeat_candidate(&mut self, i: usize) {
321 info!(
322 "Defeating candidate #{i} ({})",
323 self.election.candidates[i].nickname
324 );
325 self.not_elected.push(i);
326 self.statuses[i] = Status::NotElected;
327 }
328
329 fn num_candidates(&self) -> usize {
331 self.election.num_candidates
332 }
333
334 fn count_votes(&self) -> VoteCount<I, R> {
336 VoteCount::<I, R>::count_votes(
337 self.election,
338 &self.keep_factors,
339 self.parallel,
340 self.thread_pool.as_ref(),
341 self.pascal,
342 )
343 }
344
345 fn start_election(
347 &self,
348 stdout: &mut impl io::Write,
349 package_name: &str,
350 omega_exponent: usize,
351 ) -> io::Result<VoteCount<I, R>> {
352 writeln!(
353 stdout,
354 r"
355Election: {}
356
357 {package_name}
358 Rule: Meek Parametric (omega = 1/10^{omega_exponent})
359 Arithmetic: {}
360 Seats: {}
361 Ballots: {}
362 Quota: {}
363 Omega: {}
364",
365 self.election.title,
366 R::description(),
367 self.election.num_seats,
368 self.election.num_ballots,
369 self.threshold,
370 self.omega,
371 )?;
372
373 for candidate in &self.election.candidates {
374 writeln!(
375 stdout,
376 "\tAdd {}: {}",
377 if candidate.is_withdrawn {
378 "withdrawn"
379 } else {
380 "eligible"
381 },
382 candidate.name
383 )?;
384 }
385
386 let mut count = self.count_votes();
387 count.exhausted = R::zero();
389
390 self.write_action(stdout, "Begin Count", &count, true)?;
391
392 Ok(count)
393 }
394
395 fn election_completed(&self, stdout: &mut impl io::Write, round: usize) -> io::Result<bool> {
397 let candidate_count = self
398 .statuses
399 .iter()
400 .filter(|&&status| status == Status::Candidate)
401 .count();
402 debug!(
403 "Checking if count is complete: candidates={candidate_count}, to_elect={}",
404 self.to_elect
405 );
406 assert!(candidate_count >= self.to_elect);
407 if self.to_elect == 0 || candidate_count == self.to_elect {
408 info!("Count is now complete!");
409 return Ok(true);
410 }
411 writeln!(stdout, "Round {round}:")?;
412
413 if log_enabled!(Level::Debug) {
414 debug!("Weights:");
415 for (i, candidate) in self.election.candidates.iter().enumerate() {
416 debug!(
417 " [{i}] {} ({:?}) ~ {}",
418 candidate.nickname,
419 self.statuses[i],
420 self.keep_factors[i].to_f64()
421 );
422 }
423 }
424
425 if self
426 .statuses
427 .iter()
428 .all(|&status| status != Status::Candidate)
429 {
430 debug!("Election done!");
431 return Ok(true);
432 }
433
434 Ok(false)
435 }
436
437 fn election_round(
440 &mut self,
441 stdout: &mut impl io::Write,
442 count: &mut VoteCount<I, R>,
443 ) -> io::Result<()> {
444 let iteration = self.iterate_droop(stdout, count)?;
445 self.write_action(stdout, &format!("Iterate ({iteration})"), count, false)?;
446
447 let reason = match iteration {
448 DroopIteration::Elected => {
449 debug!("Iteration returned Elected, continuing the loop");
450 None
451 }
452 DroopIteration::Stable => Some(format!("stable surplus {}", self.surplus)),
453 DroopIteration::Omega => Some(format!("surplus {} < omega", self.surplus)),
454 };
455
456 if let Some(reason) = reason {
457 let not_elected = self.next_defeated_candidate(stdout, count)?;
458
459 self.defeat_candidate(not_elected);
460 let message = format!(
461 "Defeat ({reason}): {}",
462 self.election.candidates[not_elected].name
463 );
464 self.write_action(stdout, &message, count, true)?;
465
466 self.keep_factors[not_elected] = R::zero();
467 count.sum[not_elected] = R::zero();
468
469 *count = self.count_votes();
470 }
471
472 self.debug_count(count);
473
474 Ok(())
475 }
476
477 fn iterate_droop(
480 &mut self,
481 stdout: &mut impl io::Write,
482 count: &mut VoteCount<I, R>,
483 ) -> io::Result<DroopIteration> {
484 let mut last_surplus = R::from_usize(self.election.num_ballots);
485
486 loop {
487 *count = self.count_votes();
488 self.threshold = count.threshold(self.election);
489
490 let has_elected = self.elect_candidates(stdout, count)?;
491
492 self.surplus = if self.force_positive_surplus {
493 count.surplus_positive(&self.threshold, &self.elected)
494 } else {
495 count.surplus_droop(&self.threshold, &self.elected)
496 };
497
498 if has_elected {
499 debug!("Returning from iterate_droop (Elected)");
500 return Ok(DroopIteration::Elected);
501 }
502
503 if self.surplus <= self.omega {
504 debug!("Returning from iterate_droop (Omega)");
505 return Ok(DroopIteration::Omega);
506 }
507
508 if self.surplus >= last_surplus {
509 writeln!(stdout, "\tStable state detected ({})", self.surplus)?;
510 debug!("Returning from iterate_droop (Stable)");
511 return Ok(DroopIteration::Stable);
512 }
513
514 last_surplus = self.surplus.clone();
515
516 debug!("Updating keep factors");
518 for i in self.statuses.iter().enumerate().filter_map(|(i, &status)| {
519 if status == Status::Elected {
520 Some(i)
521 } else {
522 None
523 }
524 }) {
525 let new_factor = self.keep_factors[i]
526 .mul_up(&self.threshold)
527 .div_up_as_keep_factor(&count.sum[i]);
528 debug!(
529 "\t{}: {} ~ {} -> {new_factor} ~ {}",
530 self.election.candidates[i].nickname,
531 self.keep_factors[i],
532 self.keep_factors[i].to_f64(),
533 new_factor.to_f64()
534 );
535 self.keep_factors[i] = new_factor;
536 }
537 }
538 }
539
540 fn elect_candidates(
543 &mut self,
544 stdout: &mut impl io::Write,
545 count: &VoteCount<I, R>,
546 ) -> io::Result<bool> {
547 let mut has_elected = false;
548 for (i, candidate) in self.election.candidates.iter().enumerate() {
549 match self.statuses[i] {
550 Status::Elected => {
554 if log_enabled!(Level::Warn) && count.sum[i] < self.threshold {
555 warn!(
556 "Count for elected candidate #{i} ({}) is lower than the quota: {} < {} ~ {} < {}",
557 candidate.nickname,
558 count.sum[i],
559 self.threshold,
560 count.sum[i].to_f64(),
561 self.threshold.to_f64(),
562 )
563 }
564 }
565 Status::NotElected => assert!(count.sum[i].is_zero()),
567 Status::Candidate => {
568 let exceeds_threshold = if R::is_exact() {
570 count.sum[i] > self.threshold
571 } else {
572 count.sum[i] >= self.threshold
573 };
574 if exceeds_threshold {
575 assert!(self.to_elect > 0);
577 self.elect_candidate(i);
578 self.write_action(
579 stdout,
580 &format!("Elect: {}", candidate.name),
581 count,
582 true,
583 )?;
584 has_elected = true;
585 }
586 }
587 Status::Withdrawn => (),
588 }
589 }
590 Ok(has_elected)
591 }
592
593 fn handle_remaining_candidates(
596 &mut self,
597 stdout: &mut impl io::Write,
598 count: &mut VoteCount<I, R>,
599 ) -> io::Result<()> {
600 debug!("Checking remaining candidates");
601 for (i, candidate) in self.election.candidates.iter().enumerate() {
602 if self.statuses[i] == Status::Candidate {
603 if self.elected.len() < self.election.num_seats {
604 self.elect_candidate(i);
605 self.write_action(
606 stdout,
607 &format!("Elect remaining: {}", candidate.name),
608 count,
609 true,
610 )?;
611 } else {
612 self.defeat_candidate(i);
613 self.write_action(
614 stdout,
615 &format!("Defeat remaining: {}", candidate.name),
616 count,
617 true,
618 )?;
619 self.keep_factors[i] = R::zero();
620 count.sum[i] = R::zero();
621 }
622 *count = self.count_votes();
623 }
624 }
625 Ok(())
626 }
627
628 fn next_defeated_candidate(
630 &self,
631 stdout: &mut impl io::Write,
632 count: &VoteCount<I, R>,
633 ) -> io::Result<usize> {
634 let min_sum = count
635 .sum
636 .iter()
637 .enumerate()
638 .filter_map(|(i, x)| {
639 if self.statuses[i] == Status::Candidate {
640 Some(x)
641 } else {
642 None
643 }
644 })
645 .min_by(|x, y| x.partial_cmp(y).unwrap())
646 .unwrap();
647 debug!("Lowest vote: {min_sum} ~ {}", min_sum.to_f64());
648
649 let low_threshold = min_sum + &self.surplus;
650 debug!(
651 "Low threshold: {low_threshold} ~ {}",
652 low_threshold.to_f64()
653 );
654
655 let low_candidates = (0..self.num_candidates())
656 .filter(|&i| self.statuses[i] == Status::Candidate && count.sum[i] <= low_threshold)
657 .collect::<Vec<_>>();
658 debug!("Low candidates: {low_candidates:?}");
659
660 if low_candidates.len() == 1 {
661 return Ok(low_candidates[0]);
662 }
663
664 let defeated = *low_candidates
665 .iter()
666 .min_by_key(|c| self.election.tie_order.get(c).unwrap())
667 .unwrap();
668
669 let low_candidates_list: String = low_candidates
670 .into_iter()
671 .map(|c| &self.election.candidates[c].name)
672 .fold(String::new(), |mut s, c| {
673 if !s.is_empty() {
674 s.push_str(", ");
675 }
676 s.push_str(c);
677 s
678 });
679 self.write_action(
680 stdout,
681 &format!(
682 "Break tie (defeat): [{}] -> {}",
683 low_candidates_list, self.election.candidates[defeated].name
684 ),
685 count,
686 false,
687 )?;
688
689 Ok(defeated)
690 }
691
692 fn write_action(
694 &self,
695 stdout: &mut impl io::Write,
696 action: &str,
697 count: &VoteCount<I, R>,
698 print_full_count: bool,
699 ) -> io::Result<()> {
700 writeln!(stdout, "Action: {action}")?;
701 if print_full_count {
702 self.write_candidate_counts(stdout, count)?;
703 }
704 count.write_stats(stdout, &self.threshold, &self.surplus)?;
705 Ok(())
706 }
707
708 fn write_candidate_counts(
710 &self,
711 stdout: &mut impl io::Write,
712 count: &VoteCount<I, R>,
713 ) -> io::Result<()> {
714 let mut droop_sorted_candidates: Vec<usize> = (0..self.num_candidates()).collect();
716 droop_sorted_candidates.sort_by_key(|&i| match self.statuses[i] {
717 Status::Withdrawn | Status::Elected => 0,
718 Status::Candidate => 1,
719 Status::NotElected => {
720 if !count.sum[i].is_zero() {
721 2
722 } else {
723 3
724 }
725 }
726 });
727
728 let split = droop_sorted_candidates
730 .iter()
731 .position(|&i| self.statuses[i] == Status::NotElected && count.sum[i] == R::zero());
732
733 for &i in droop_sorted_candidates[..split.unwrap_or(droop_sorted_candidates.len())].iter() {
735 let status = match self.statuses[i] {
736 Status::Elected => "Elected: ",
737 Status::Candidate => "Hopeful: ",
738 Status::NotElected => "Defeated:",
739 Status::Withdrawn => continue,
740 };
741 writeln!(
742 stdout,
743 "\t{status} {} ({})",
744 self.election.candidates[i].name, count.sum[i]
745 )?;
746 }
747
748 if let Some(split) = split {
751 write!(stdout, "\tDefeated: ")?;
752 for (rank, &i) in droop_sorted_candidates[split..].iter().enumerate() {
753 assert_eq!(self.statuses[i], Status::NotElected);
754 assert_eq!(count.sum[i], R::zero());
755 if rank != 0 {
756 write!(stdout, ", ")?;
757 }
758 write!(stdout, "{}", self.election.candidates[i].name)?;
759 }
760 writeln!(stdout, " ({})", R::zero())?;
761 }
762
763 Ok(())
764 }
765
766 fn debug_count(&self, count: &VoteCount<I, R>) {
768 if !log_enabled!(Level::Debug) {
769 return;
770 }
771
772 let mut sorted_candidates: Vec<usize> = (0..self.num_candidates()).collect();
773 sorted_candidates.sort_by(|&i, &j| count.sum[i].partial_cmp(&count.sum[j]).unwrap());
774
775 debug!("Sums:");
776 for (rank, &i) in sorted_candidates.iter().rev().enumerate() {
777 debug!(
778 " [{rank}] {} ({:?}) ~ {}",
779 self.election.candidates[i].nickname,
780 self.statuses[i],
781 count.sum[i].to_f64()
782 );
783 }
784 debug!(" @exhausted ~ {}", count.exhausted.to_f64());
785
786 let sum = count.sum.iter().sum::<R>();
787 debug!("Sum = {sum}");
788 let total = sum + &count.exhausted;
789 debug!("Total = {total}");
790 R::assert_eq(
791 total,
792 R::from_usize(self.election.num_ballots),
793 "Total count must be equal to the number of ballots",
794 );
795
796 debug!("Elected:");
797 for (i, &id) in self.elected.iter().enumerate() {
798 debug!(" [{i}] {}", self.election.candidates[id].nickname);
799 }
800 debug!("Not elected:");
801 for (i, &id) in self.not_elected.iter().enumerate() {
802 debug!(
803 " [{}] {}",
804 self.num_candidates() - i - 1,
805 self.election.candidates[id].nickname
806 );
807 }
808 }
809}
810
811#[cfg(test)]
812mod test {
813 use super::*;
814 use crate::arithmetic::{FixedDecimal9, Integer64};
815 use crate::types::{Ballot, Candidate};
816 use crate::util::log_tester::ThreadLocalLogger;
817 use log::Level::{Debug, Info, Warn};
818 use num::traits::{One, Zero};
819 use num::BigRational;
820
821 pub struct StateBuilder<'e, I, R> {
822 election: Option<&'e Election>,
823 statuses: Option<Vec<Status>>,
824 keep_factors: Option<Vec<R>>,
825 threshold: Option<R>,
826 surplus: Option<R>,
827 omega: Option<R>,
828 parallel: Option<Parallel>,
829 force_positive_surplus: Option<bool>,
830 pascal: Option<Option<&'e [Vec<I>]>>,
831 _phantom: PhantomData<I>,
832 }
833
834 impl<I, R> Default for StateBuilder<'_, I, R> {
835 fn default() -> Self {
836 StateBuilder {
837 election: None,
838 statuses: None,
839 keep_factors: None,
840 threshold: None,
841 surplus: None,
842 omega: None,
843 parallel: None,
844 force_positive_surplus: None,
845 pascal: None,
846 _phantom: PhantomData,
847 }
848 }
849 }
850
851 impl<'e, I, R> StateBuilder<'e, I, R>
852 where
853 R: Clone,
854 {
855 fn election(mut self, election: &'e Election) -> Self {
856 self.election = Some(election);
857 self
858 }
859
860 fn statuses(mut self, statuses: impl Into<Vec<Status>>) -> Self {
861 self.statuses = Some(statuses.into());
862 self
863 }
864
865 fn keep_factors(mut self, keep_factors: impl Into<Vec<R>>) -> Self {
866 self.keep_factors = Some(keep_factors.into());
867 self
868 }
869
870 fn threshold(mut self, threshold: R) -> Self {
871 self.threshold = Some(threshold);
872 self
873 }
874
875 fn surplus(mut self, surplus: R) -> Self {
876 self.surplus = Some(surplus);
877 self
878 }
879
880 fn omega(mut self, omega: R) -> Self {
881 self.omega = Some(omega);
882 self
883 }
884
885 fn parallel(mut self, parallel: Parallel) -> Self {
886 self.parallel = Some(parallel);
887 self
888 }
889
890 fn force_positive_surplus(mut self, force_positive_surplus: bool) -> Self {
891 self.force_positive_surplus = Some(force_positive_surplus);
892 self
893 }
894
895 fn pascal(mut self, pascal: Option<&'e [Vec<I>]>) -> Self {
896 self.pascal = Some(pascal);
897 self
898 }
899
900 #[track_caller]
901 fn build(self) -> State<'static, 'e, I, R> {
902 let election = self.election.unwrap();
903 let statuses = self.statuses.unwrap();
904 let elected = statuses
905 .iter()
906 .enumerate()
907 .filter_map(|(i, status)| {
908 if let Status::Elected = status {
909 Some(i)
910 } else {
911 None
912 }
913 })
914 .collect::<Vec<_>>();
915 let not_elected = statuses
916 .iter()
917 .enumerate()
918 .filter_map(|(i, status)| {
919 if let Status::NotElected = status {
920 Some(i)
921 } else {
922 None
923 }
924 })
925 .collect::<Vec<_>>();
926 let to_elect = election.num_seats - elected.len();
927 State {
928 election,
929 statuses,
930 elected,
931 not_elected,
932 to_elect,
933 keep_factors: self.keep_factors.unwrap(),
934 threshold: self.threshold.unwrap(),
935 surplus: self.surplus.unwrap(),
936 omega: self.omega.unwrap(),
937 parallel: self.parallel.unwrap(),
938 force_positive_surplus: self.force_positive_surplus.unwrap(),
939 pascal: self.pascal.unwrap(),
940 thread_pool: None,
941 _phantom: PhantomData,
942 }
943 }
944 }
945
946 fn make_fake_election() -> Election {
947 Election::builder()
948 .title("Vegetable contest")
949 .num_seats(3)
950 .candidates([
951 Candidate::new("apple", false),
952 Candidate::new("banana", true),
953 Candidate::new("cherry", false),
954 Candidate::new("date", false),
955 Candidate::new("eggplant", false),
956 Candidate::new("fig", true),
957 Candidate::new("grape", false),
958 Candidate::new("hazelnut", false),
959 Candidate::new("jalapeno", false),
960 ])
961 .num_ballots(6)
962 .build()
963 }
964
965 fn make_fake_state(election: &Election) -> State<Integer64, FixedDecimal9> {
966 State::builder()
967 .election(election)
968 .statuses([
969 Status::Candidate,
970 Status::Withdrawn,
971 Status::Elected,
972 Status::NotElected,
973 Status::Candidate,
974 Status::NotElected,
975 Status::Elected,
976 Status::NotElected,
977 Status::NotElected,
978 ])
979 .keep_factors([
980 FixedDecimal9::one(),
981 FixedDecimal9::zero(),
982 FixedDecimal9::ratio(1, 2),
983 FixedDecimal9::zero(),
984 FixedDecimal9::one(),
985 FixedDecimal9::zero(),
986 FixedDecimal9::ratio(2, 3),
987 FixedDecimal9::zero(),
988 FixedDecimal9::zero(),
989 ])
990 .threshold(FixedDecimal9::ratio(3, 2))
991 .surplus(FixedDecimal9::ratio(1, 10))
992 .omega(FixedDecimal9::ratio(1, 1_000_000))
993 .parallel(Parallel::No)
994 .force_positive_surplus(false)
995 .pascal(None)
996 .build()
997 }
998
999 fn make_fake_count() -> VoteCount<Integer64, FixedDecimal9> {
1000 VoteCount::new(
1001 [
1002 FixedDecimal9::ratio(7, 9),
1003 FixedDecimal9::zero(),
1004 FixedDecimal9::ratio(5, 3),
1005 FixedDecimal9::ratio(1, 10),
1006 FixedDecimal9::ratio(7, 8),
1007 FixedDecimal9::zero(),
1008 FixedDecimal9::ratio(11, 6),
1009 FixedDecimal9::ratio(2, 10),
1010 FixedDecimal9::zero(),
1011 ],
1012 FixedDecimal9::new(547222224),
1013 )
1014 }
1015
1016 #[test]
1017 fn test_stv_droop_parallel_no() {
1018 test_stv_droop(Parallel::No);
1019 }
1020
1021 #[test]
1022 fn test_stv_droop_parallel_rayon() {
1023 test_stv_droop(Parallel::Rayon);
1024 }
1025
1026 #[test]
1027 fn test_stv_droop_parallel_custom() {
1028 test_stv_droop(Parallel::Custom);
1029 }
1030
1031 fn test_stv_droop(parallel: Parallel) {
1032 let election = Election::builder()
1033 .title("Vegetable contest")
1034 .num_seats(5)
1035 .candidates([
1036 Candidate::new("apple", false),
1037 Candidate::new("banana", true),
1038 Candidate::new("cherry", false),
1039 Candidate::new("date", false),
1040 Candidate::new("eggplant", false),
1041 Candidate::new("fig", true),
1042 Candidate::new("grape", false),
1043 Candidate::new("hazelnut", false),
1044 Candidate::new("jalapeno", false),
1045 ])
1046 .ballots([
1047 Ballot::new(1, [vec![0]]),
1048 Ballot::new(2, [vec![2]]),
1049 Ballot::new(3, [vec![3]]),
1050 Ballot::new(4, [vec![4]]),
1051 Ballot::new(5, [vec![6]]),
1052 Ballot::new(6, [vec![7]]),
1053 Ballot::new(7, [vec![8]]),
1054 ])
1055 .build();
1056
1057 let mut buf = Vec::new();
1058 let result = stv_droop::<Integer64, FixedDecimal9>(
1059 &mut buf,
1060 &election,
1061 "package name",
1062 6,
1063 parallel,
1064 None,
1065 false,
1066 false,
1067 false,
1068 )
1069 .unwrap();
1070
1071 assert_eq!(
1072 result,
1073 ElectionResult {
1074 elected: vec![6, 7, 8, 4, 3],
1075 not_elected: vec![0, 2]
1076 }
1077 );
1078 assert_eq!(
1079 std::str::from_utf8(&buf).unwrap(),
1080 r"
1081Election: Vegetable contest
1082
1083 package name
1084 Rule: Meek Parametric (omega = 1/10^6)
1085 Arithmetic: fixed-point decimal arithmetic (9 places)
1086 Seats: 5
1087 Ballots: 28
1088 Quota: 4.666666667
1089 Omega: 0.000001000
1090
1091 Add eligible: Apple
1092 Add withdrawn: Banana
1093 Add eligible: Cherry
1094 Add eligible: Date
1095 Add eligible: Eggplant
1096 Add withdrawn: Fig
1097 Add eligible: Grape
1098 Add eligible: Hazelnut
1099 Add eligible: Jalapeno
1100Action: Begin Count
1101 Hopeful: Apple (1.000000000)
1102 Hopeful: Cherry (2.000000000)
1103 Hopeful: Date (3.000000000)
1104 Hopeful: Eggplant (4.000000000)
1105 Hopeful: Grape (5.000000000)
1106 Hopeful: Hazelnut (6.000000000)
1107 Hopeful: Jalapeno (7.000000000)
1108 Quota: 4.666666667
1109 Votes: 28.000000000
1110 Residual: 0.000000000
1111 Total: 28.000000000
1112 Surplus: 0.000000000
1113Round 1:
1114Action: Elect: Grape
1115 Elected: Grape (5.000000000)
1116 Hopeful: Apple (1.000000000)
1117 Hopeful: Cherry (2.000000000)
1118 Hopeful: Date (3.000000000)
1119 Hopeful: Eggplant (4.000000000)
1120 Hopeful: Hazelnut (6.000000000)
1121 Hopeful: Jalapeno (7.000000000)
1122 Quota: 4.666666667
1123 Votes: 28.000000000
1124 Residual: 0.000000000
1125 Total: 28.000000000
1126 Surplus: 0.000000000
1127Action: Elect: Hazelnut
1128 Elected: Grape (5.000000000)
1129 Elected: Hazelnut (6.000000000)
1130 Hopeful: Apple (1.000000000)
1131 Hopeful: Cherry (2.000000000)
1132 Hopeful: Date (3.000000000)
1133 Hopeful: Eggplant (4.000000000)
1134 Hopeful: Jalapeno (7.000000000)
1135 Quota: 4.666666667
1136 Votes: 28.000000000
1137 Residual: 0.000000000
1138 Total: 28.000000000
1139 Surplus: 0.000000000
1140Action: Elect: Jalapeno
1141 Elected: Grape (5.000000000)
1142 Elected: Hazelnut (6.000000000)
1143 Elected: Jalapeno (7.000000000)
1144 Hopeful: Apple (1.000000000)
1145 Hopeful: Cherry (2.000000000)
1146 Hopeful: Date (3.000000000)
1147 Hopeful: Eggplant (4.000000000)
1148 Quota: 4.666666667
1149 Votes: 28.000000000
1150 Residual: 0.000000000
1151 Total: 28.000000000
1152 Surplus: 0.000000000
1153Action: Iterate (elected)
1154 Quota: 4.666666667
1155 Votes: 28.000000000
1156 Residual: 0.000000000
1157 Total: 28.000000000
1158 Surplus: 3.999999999
1159Round 2:
1160Action: Elect: Eggplant
1161 Elected: Eggplant (4.000000000)
1162 Elected: Grape (4.000000005)
1163 Elected: Hazelnut (4.000000008)
1164 Elected: Jalapeno (4.000000004)
1165 Hopeful: Apple (1.000000000)
1166 Hopeful: Cherry (2.000000000)
1167 Hopeful: Date (3.000000000)
1168 Quota: 3.666666670
1169 Votes: 22.000000017
1170 Residual: 5.999999983
1171 Total: 28.000000000
1172 Surplus: 2.000000001
1173Action: Iterate (elected)
1174 Quota: 3.666666670
1175 Votes: 22.000000017
1176 Residual: 5.999999983
1177 Total: 28.000000000
1178 Surplus: 1.333333337
1179Round 3:
1180Action: Iterate (omega)
1181 Quota: 3.000000464
1182 Votes: 18.000002783
1183 Residual: 9.999997217
1184 Total: 28.000000000
1185 Surplus: 0.000000927
1186Action: Defeat (surplus 0.000000927 < omega): Apple
1187 Elected: Eggplant (3.000000696)
1188 Elected: Grape (3.000000695)
1189 Elected: Hazelnut (3.000000696)
1190 Elected: Jalapeno (3.000000696)
1191 Hopeful: Cherry (2.000000000)
1192 Hopeful: Date (3.000000000)
1193 Defeated: Apple (1.000000000)
1194 Quota: 3.000000464
1195 Votes: 18.000002783
1196 Residual: 9.999997217
1197 Total: 28.000000000
1198 Surplus: 0.000000927
1199Round 4:
1200Action: Elect: Date
1201 Elected: Date (3.000000000)
1202 Elected: Eggplant (3.000000696)
1203 Elected: Grape (3.000000695)
1204 Elected: Hazelnut (3.000000696)
1205 Elected: Jalapeno (3.000000696)
1206 Hopeful: Cherry (2.000000000)
1207 Defeated: Apple (0.000000000)
1208 Quota: 2.833333798
1209 Votes: 17.000002783
1210 Residual: 10.999997217
1211 Total: 28.000000000
1212 Surplus: 0.000000927
1213Action: Iterate (elected)
1214 Quota: 2.833333798
1215 Votes: 17.000002783
1216 Residual: 10.999997217
1217 Total: 28.000000000
1218 Surplus: 0.833333793
1219Action: Defeat remaining: Cherry
1220 Elected: Date (3.000000000)
1221 Elected: Eggplant (3.000000696)
1222 Elected: Grape (3.000000695)
1223 Elected: Hazelnut (3.000000696)
1224 Elected: Jalapeno (3.000000696)
1225 Defeated: Cherry (2.000000000)
1226 Defeated: Apple (0.000000000)
1227 Quota: 2.833333798
1228 Votes: 17.000002783
1229 Residual: 10.999997217
1230 Total: 28.000000000
1231 Surplus: 0.833333793
1232Action: Count Complete
1233 Elected: Date (3.000000000)
1234 Elected: Eggplant (3.000000696)
1235 Elected: Grape (3.000000695)
1236 Elected: Hazelnut (3.000000696)
1237 Elected: Jalapeno (3.000000696)
1238 Defeated: Apple, Cherry (0.000000000)
1239 Quota: 2.833333798
1240 Votes: 15.000002783
1241 Residual: 12.999997217
1242 Total: 28.000000000
1243 Surplus: 0.833333793
1244
1245"
1246 );
1247 }
1248
1249 #[test]
1250 fn test_stv_droop_equalize_parallel_no() {
1251 test_stv_droop_equalize(Parallel::No);
1252 }
1253
1254 #[test]
1255 fn test_stv_droop_equalize_parallel_rayon() {
1256 test_stv_droop_equalize(Parallel::Rayon);
1257 }
1258
1259 #[test]
1260 fn test_stv_droop_equalize_parallel_custom() {
1261 test_stv_droop_equalize(Parallel::Custom);
1262 }
1263
1264 fn test_stv_droop_equalize(parallel: Parallel) {
1265 let election = Election::builder()
1266 .title("Vegetable contest")
1267 .num_seats(3)
1268 .candidates([
1269 Candidate::new("apple", false),
1270 Candidate::new("banana", false),
1271 Candidate::new("cherry", false),
1272 Candidate::new("date", false),
1273 Candidate::new("eggplant", false),
1274 ])
1275 .ballots([
1276 Ballot::new(1, [vec![0, 1], vec![2, 3, 4]]),
1277 Ballot::new(2, [vec![1, 2], vec![3, 4, 0]]),
1278 Ballot::new(3, [vec![2, 3], vec![4, 0, 1]]),
1279 Ballot::new(4, [vec![3, 4], vec![0, 1, 2]]),
1280 Ballot::new(5, [vec![4, 0], vec![1, 2, 3]]),
1281 ])
1282 .build();
1283
1284 let mut buf = Vec::new();
1285 let result = stv_droop::<Integer64, FixedDecimal9>(
1286 &mut buf,
1287 &election,
1288 "package name",
1289 6,
1290 parallel,
1291 None,
1292 false,
1293 false,
1294 true,
1295 )
1296 .unwrap();
1297
1298 assert_eq!(
1299 result,
1300 ElectionResult {
1301 elected: vec![4, 3, 0],
1302 not_elected: vec![1, 2]
1303 }
1304 );
1305 assert_eq!(
1306 std::str::from_utf8(&buf).unwrap(),
1307 r"
1308Election: Vegetable contest
1309
1310 package name
1311 Rule: Meek Parametric (omega = 1/10^6)
1312 Arithmetic: fixed-point decimal arithmetic (9 places)
1313 Seats: 3
1314 Ballots: 15
1315 Quota: 3.750000001
1316 Omega: 0.000001000
1317
1318 Add eligible: Apple
1319 Add eligible: Banana
1320 Add eligible: Cherry
1321 Add eligible: Date
1322 Add eligible: Eggplant
1323Action: Begin Count
1324 Hopeful: Apple (3.000000000)
1325 Hopeful: Banana (1.500000000)
1326 Hopeful: Cherry (2.500000000)
1327 Hopeful: Date (3.500000000)
1328 Hopeful: Eggplant (4.500000000)
1329 Quota: 3.750000001
1330 Votes: 15.000000000
1331 Residual: 0.000000000
1332 Total: 15.000000000
1333 Surplus: 0.000000000
1334Round 1:
1335Action: Elect: Eggplant
1336 Elected: Eggplant (4.500000000)
1337 Hopeful: Apple (3.000000000)
1338 Hopeful: Banana (1.500000000)
1339 Hopeful: Cherry (2.500000000)
1340 Hopeful: Date (3.500000000)
1341 Quota: 3.750000001
1342 Votes: 15.000000000
1343 Residual: 0.000000000
1344 Total: 15.000000000
1345 Surplus: 0.000000000
1346Action: Iterate (elected)
1347 Quota: 3.750000001
1348 Votes: 15.000000000
1349 Residual: 0.000000000
1350 Total: 15.000000000
1351 Surplus: 0.749999999
1352Round 2:
1353Action: Elect: Date
1354 Elected: Date (3.833333332)
1355 Elected: Eggplant (3.750000003)
1356 Hopeful: Apple (3.416666665)
1357 Hopeful: Banana (1.500000000)
1358 Hopeful: Cherry (2.500000000)
1359 Quota: 3.750000001
1360 Votes: 15.000000000
1361 Residual: 0.000000000
1362 Total: 15.000000000
1363 Surplus: 0.749999999
1364Action: Iterate (elected)
1365 Quota: 3.750000001
1366 Votes: 15.000000000
1367 Residual: 0.000000000
1368 Total: 15.000000000
1369 Surplus: 0.083333333
1370Round 3:
1371Action: Iterate (omega)
1372 Quota: 3.749999996
1373 Votes: 14.999999983
1374 Residual: 0.000000017
1375 Total: 15.000000000
1376 Surplus: 0.000000588
1377Action: Defeat (surplus 0.000000588 < omega): Banana
1378 Elected: Date (3.750000581)
1379 Elected: Eggplant (3.749999999)
1380 Hopeful: Apple (3.447382656)
1381 Hopeful: Cherry (2.546334971)
1382 Defeated: Banana (1.506281776)
1383 Quota: 3.749999996
1384 Votes: 14.999999983
1385 Residual: 0.000000017
1386 Total: 15.000000000
1387 Surplus: 0.000000588
1388Round 4:
1389Action: Elect: Apple
1390 Elected: Apple (3.950523544)
1391 Elected: Date (3.750000581)
1392 Elected: Eggplant (3.749999999)
1393 Hopeful: Cherry (3.549475859)
1394 Defeated: Banana (0.000000000)
1395 Quota: 3.749999996
1396 Votes: 14.999999983
1397 Residual: 0.000000017
1398 Total: 15.000000000
1399 Surplus: 0.000000588
1400Action: Iterate (elected)
1401 Quota: 3.749999996
1402 Votes: 14.999999983
1403 Residual: 0.000000017
1404 Total: 15.000000000
1405 Surplus: 0.200524136
1406Action: Defeat remaining: Cherry
1407 Elected: Apple (3.950523544)
1408 Elected: Date (3.750000581)
1409 Elected: Eggplant (3.749999999)
1410 Defeated: Cherry (3.549475859)
1411 Defeated: Banana (0.000000000)
1412 Quota: 3.749999996
1413 Votes: 14.999999983
1414 Residual: 0.000000017
1415 Total: 15.000000000
1416 Surplus: 0.200524136
1417Action: Count Complete
1418 Elected: Apple (4.744588121)
1419 Elected: Date (5.916055638)
1420 Elected: Eggplant (4.339356223)
1421 Defeated: Banana, Cherry (0.000000000)
1422 Quota: 3.749999996
1423 Votes: 14.999999982
1424 Residual: 0.000000018
1425 Total: 15.000000000
1426 Surplus: 0.200524136
1427
1428"
1429 );
1430 }
1431
1432 #[test]
1433 fn test_stv_droop_below_quota() {
1434 let election = Election::builder()
1435 .title("Vegetable contest")
1436 .num_seats(2)
1437 .candidates([
1438 Candidate::new("apple", false),
1439 Candidate::new("banana", false),
1440 Candidate::new("cherry", false),
1441 Candidate::new("date", false),
1442 ])
1443 .ballots([
1444 Ballot::new(3, [vec![0, 1], vec![2]]),
1445 Ballot::new(2, [vec![0, 3], vec![1]]),
1446 ])
1447 .build();
1448
1449 let logger = ThreadLocalLogger::start();
1450 let mut buf = Vec::new();
1451 let result = stv_droop::<Integer64, FixedDecimal9>(
1452 &mut buf,
1453 &election,
1454 "package name",
1455 6,
1456 Parallel::No,
1457 None,
1458 false,
1459 false,
1460 false,
1461 )
1462 .unwrap();
1463
1464 assert_eq!(
1465 result,
1466 ElectionResult {
1467 elected: vec![0, 1],
1468 not_elected: vec![2, 3]
1469 }
1470 );
1471 logger.check_logs_at_target_level(
1472 "stv_rs::meek",
1473 Warn,
1474 r"Count for elected candidate #0 (apple) is lower than the quota: 1.666666665 < 1.666666666 ~ 1.666666665 < 1.666666666
1475",
1476 );
1477
1478 assert_eq!(
1479 std::str::from_utf8(&buf).unwrap(),
1480 r"
1481Election: Vegetable contest
1482
1483 package name
1484 Rule: Meek Parametric (omega = 1/10^6)
1485 Arithmetic: fixed-point decimal arithmetic (9 places)
1486 Seats: 2
1487 Ballots: 5
1488 Quota: 1.666666667
1489 Omega: 0.000001000
1490
1491 Add eligible: Apple
1492 Add eligible: Banana
1493 Add eligible: Cherry
1494 Add eligible: Date
1495Action: Begin Count
1496 Hopeful: Apple (2.500000000)
1497 Hopeful: Banana (1.500000000)
1498 Hopeful: Cherry (0.000000000)
1499 Hopeful: Date (1.000000000)
1500 Quota: 1.666666667
1501 Votes: 5.000000000
1502 Residual: 0.000000000
1503 Total: 5.000000000
1504 Surplus: 0.000000000
1505Round 1:
1506Action: Elect: Apple
1507 Elected: Apple (2.500000000)
1508 Hopeful: Banana (1.500000000)
1509 Hopeful: Cherry (0.000000000)
1510 Hopeful: Date (1.000000000)
1511 Quota: 1.666666667
1512 Votes: 5.000000000
1513 Residual: 0.000000000
1514 Total: 5.000000000
1515 Surplus: 0.000000000
1516Action: Iterate (elected)
1517 Quota: 1.666666667
1518 Votes: 5.000000000
1519 Residual: 0.000000000
1520 Total: 5.000000000
1521 Surplus: 0.833333333
1522Round 2:
1523Action: Elect: Banana
1524 Elected: Apple (1.666666665)
1525 Elected: Banana (1.833333332)
1526 Hopeful: Cherry (0.499999998)
1527 Hopeful: Date (1.000000000)
1528 Quota: 1.666666666
1529 Votes: 4.999999995
1530 Residual: 0.000000005
1531 Total: 5.000000000
1532 Surplus: 0.833333333
1533Action: Iterate (elected)
1534 Quota: 1.666666666
1535 Votes: 4.999999995
1536 Residual: 0.000000005
1537 Total: 5.000000000
1538 Surplus: 0.166666665
1539Action: Defeat remaining: Cherry
1540 Elected: Apple (1.666666665)
1541 Elected: Banana (1.833333332)
1542 Hopeful: Date (1.000000000)
1543 Defeated: Cherry (0.499999998)
1544 Quota: 1.666666666
1545 Votes: 4.999999995
1546 Residual: 0.000000005
1547 Total: 5.000000000
1548 Surplus: 0.166666665
1549Action: Defeat remaining: Date
1550 Elected: Apple (1.666666665)
1551 Elected: Banana (1.833333332)
1552 Defeated: Date (1.000000000)
1553 Defeated: Cherry (0.000000000)
1554 Quota: 1.666666666
1555 Votes: 4.499999997
1556 Residual: 0.500000003
1557 Total: 5.000000000
1558 Surplus: 0.166666665
1559Action: Count Complete
1560 Elected: Apple (2.333333333)
1561 Elected: Banana (2.166666666)
1562 Defeated: Cherry, Date (0.000000000)
1563 Quota: 1.666666666
1564 Votes: 4.499999999
1565 Residual: 0.500000001
1566 Total: 5.000000000
1567 Surplus: 0.166666665
1568
1569"
1570 );
1571 }
1572
1573 #[cfg(feature = "checked_i64")]
1574 fn make_panicking_election() -> Election {
1575 Election::builder()
1576 .title("Vegetable contest")
1577 .num_seats(1)
1578 .candidates([Candidate::new("apple", false)])
1579 .ballots([
1580 Ballot::new(9223372036854775807, [vec![0]]),
1581 Ballot::new(9223372036854775807, [vec![0]]),
1582 Ballot::new(9223372036854775807, [vec![0]]),
1583 Ballot::new(9223372036854775807, [vec![0]]),
1584 Ballot::new(9223372036854775807, [vec![0]]),
1585 ])
1586 .build()
1587 }
1588
1589 #[cfg(feature = "checked_i64")]
1590 #[test]
1591 #[cfg_attr(
1592 not(overflow_checks),
1593 should_panic(expected = "called `Option::unwrap()` on a `None` value")
1594 )]
1595 #[cfg_attr(
1596 overflow_checks,
1597 should_panic(expected = "attempt to add with overflow")
1598 )]
1599 fn test_stv_droop_panic() {
1600 let _ = stv_droop::<Integer64, FixedDecimal9>(
1601 &mut Vec::new(),
1602 &make_panicking_election(),
1603 "package name",
1604 6,
1605 Parallel::Custom,
1606 Some(NonZeroUsize::new(2).unwrap()),
1607 false,
1608 false,
1609 false,
1610 );
1611 }
1612
1613 #[cfg(feature = "checked_i64")]
1614 #[test]
1615 fn test_stv_droop_panic_logs() {
1616 let logger = ThreadLocalLogger::start();
1617 let result = std::panic::catch_unwind(|| {
1618 stv_droop::<Integer64, FixedDecimal9>(
1619 &mut Vec::new(),
1620 &make_panicking_election(),
1621 "package name",
1622 6,
1623 Parallel::Custom,
1624 Some(NonZeroUsize::new(2).unwrap()),
1625 false,
1626 false,
1627 false,
1628 )
1629 });
1630
1631 assert!(result.is_err());
1635 assert_eq!(
1636 result.as_ref().unwrap_err().type_id(),
1637 std::any::TypeId::of::<&str>()
1638 );
1639
1640 #[cfg(not(overflow_checks))]
1641 assert_eq!(
1642 *result.unwrap_err().downcast::<&str>().unwrap(),
1643 "called `Option::unwrap()` on a `None` value"
1644 );
1645 #[cfg(overflow_checks)]
1646 assert_eq!(
1647 *result.unwrap_err().downcast::<&str>().unwrap(),
1648 "attempt to add with overflow"
1649 );
1650
1651 #[cfg(not(overflow_checks))]
1652 logger.check_logs([
1653 (
1654 Info,
1655 "stv_rs::meek",
1656 "Parallel vote counting is enabled using custom threads",
1657 ),
1658 (Info, "stv_rs::meek", "Equalized vote counting is disabled"),
1659 (Info, "stv_rs::meek", "Spawning 2 threads"),
1660 (
1661 Debug,
1662 "stv_rs::parallelism::thread_pool",
1663 "[main thread] Spawned threads",
1664 ),
1665 (
1666 Debug,
1667 "stv_rs::parallelism::thread_pool",
1668 "[main thread] Notifying threads to finish...",
1669 ),
1670 (
1671 Debug,
1672 "stv_rs::parallelism::thread_pool",
1673 "[main thread] Joining threads in the pool...",
1674 ),
1675 (
1676 Debug,
1677 "stv_rs::parallelism::thread_pool",
1678 "[main thread] Thread 0 joined with result: Ok(())",
1679 ),
1680 (
1681 Debug,
1682 "stv_rs::parallelism::thread_pool",
1683 "[main thread] Thread 1 joined with result: Ok(())",
1684 ),
1685 (
1686 Debug,
1687 "stv_rs::parallelism::thread_pool",
1688 "[main thread] Joined threads.",
1689 ),
1690 #[cfg(feature = "log_parallelism")]
1691 (
1692 Info,
1693 "stv_rs::parallelism::range",
1694 "Work-stealing statistics:",
1695 ),
1696 #[cfg(feature = "log_parallelism")]
1697 (Info, "stv_rs::parallelism::range", "- increments: 0"),
1698 #[cfg(feature = "log_parallelism")]
1699 (Info, "stv_rs::parallelism::range", "- failed_increments: 0"),
1700 #[cfg(feature = "log_parallelism")]
1701 (Info, "stv_rs::parallelism::range", "- other_loads: 0"),
1702 #[cfg(feature = "log_parallelism")]
1703 (Info, "stv_rs::parallelism::range", "- thefts: 0"),
1704 #[cfg(feature = "log_parallelism")]
1705 (Info, "stv_rs::parallelism::range", "- failed_thefts: 0"),
1706 #[cfg(feature = "log_parallelism")]
1707 (
1708 Info,
1709 "stv_rs::parallelism::range",
1710 "- increments + thefts: 0",
1711 ),
1712 ]);
1713 #[cfg(overflow_checks)]
1714 logger.check_logs([]);
1715 }
1716
1717 #[test]
1718 fn test_start_election() {
1719 let election = Election::builder()
1720 .title("Vegetable contest")
1721 .num_seats(3)
1722 .candidates([
1723 Candidate::new("apple", false),
1724 Candidate::new("banana", true),
1725 Candidate::new("cherry", false),
1726 Candidate::new("date", false),
1727 Candidate::new("eggplant", false),
1728 Candidate::new("fig", true),
1729 Candidate::new("grape", false),
1730 Candidate::new("hazelnut", false),
1731 Candidate::new("jalapeno", false),
1732 ])
1733 .ballots([
1734 Ballot::new(1, [vec![0]]),
1735 Ballot::new(2, [vec![2]]),
1736 Ballot::new(3, [vec![3]]),
1737 Ballot::new(4, [vec![4]]),
1738 Ballot::new(5, [vec![6]]),
1739 Ballot::new(6, [vec![7]]),
1740 Ballot::new(7, [vec![8]]),
1741 ])
1742 .build();
1743 let omega_exponent = 6;
1744 let state = State::new(&election, omega_exponent, Parallel::No, false, None, None);
1745
1746 let mut buf = Vec::new();
1747 let count = state
1748 .start_election(&mut buf, "STV-rs", omega_exponent)
1749 .unwrap();
1750
1751 assert_eq!(
1752 count,
1753 VoteCount::new(
1754 [
1755 FixedDecimal9::from_usize(1),
1756 FixedDecimal9::zero(),
1757 FixedDecimal9::from_usize(2),
1758 FixedDecimal9::from_usize(3),
1759 FixedDecimal9::from_usize(4),
1760 FixedDecimal9::zero(),
1761 FixedDecimal9::from_usize(5),
1762 FixedDecimal9::from_usize(6),
1763 FixedDecimal9::from_usize(7),
1764 ],
1765 FixedDecimal9::zero(),
1766 )
1767 );
1768 assert_eq!(
1769 std::str::from_utf8(&buf).unwrap(),
1770 r"
1771Election: Vegetable contest
1772
1773 STV-rs
1774 Rule: Meek Parametric (omega = 1/10^6)
1775 Arithmetic: fixed-point decimal arithmetic (9 places)
1776 Seats: 3
1777 Ballots: 28
1778 Quota: 7.000000001
1779 Omega: 0.000001000
1780
1781 Add eligible: Apple
1782 Add withdrawn: Banana
1783 Add eligible: Cherry
1784 Add eligible: Date
1785 Add eligible: Eggplant
1786 Add withdrawn: Fig
1787 Add eligible: Grape
1788 Add eligible: Hazelnut
1789 Add eligible: Jalapeno
1790Action: Begin Count
1791 Hopeful: Apple (1.000000000)
1792 Hopeful: Cherry (2.000000000)
1793 Hopeful: Date (3.000000000)
1794 Hopeful: Eggplant (4.000000000)
1795 Hopeful: Grape (5.000000000)
1796 Hopeful: Hazelnut (6.000000000)
1797 Hopeful: Jalapeno (7.000000000)
1798 Quota: 7.000000001
1799 Votes: 28.000000000
1800 Residual: 0.000000000
1801 Total: 28.000000000
1802 Surplus: 0.000000000
1803"
1804 );
1805 }
1806
1807 #[test]
1808 fn test_election_completed() {
1809 fn make_election(num_seats: usize) -> Election {
1810 Election::builder()
1811 .title("Vegetable contest")
1812 .num_seats(num_seats)
1813 .candidates([
1814 Candidate::new("apple", false),
1815 Candidate::new("banana", true),
1816 Candidate::new("cherry", false),
1817 Candidate::new("date", false),
1818 Candidate::new("eggplant", false),
1819 ])
1820 .build()
1821 }
1822
1823 fn make_state(
1824 election: &Election,
1825 statuses: [Status; 5],
1826 ) -> State<'_, '_, Integer64, FixedDecimal9> {
1827 State::builder()
1828 .election(election)
1829 .statuses(statuses)
1830 .keep_factors([FixedDecimal9::zero(); 5])
1831 .threshold(FixedDecimal9::zero())
1832 .surplus(FixedDecimal9::zero())
1833 .omega(FixedDecimal9::zero())
1834 .parallel(Parallel::No)
1835 .force_positive_surplus(false)
1836 .pascal(None)
1837 .build()
1838 }
1839
1840 let logger = ThreadLocalLogger::start();
1842 let election = make_election(1);
1843 let state = make_state(
1844 &election,
1845 [
1846 Status::Candidate,
1847 Status::Withdrawn,
1848 Status::Elected,
1849 Status::NotElected,
1850 Status::Candidate,
1851 ],
1852 );
1853
1854 let mut buf = Vec::new();
1855 let completed = state.election_completed(&mut buf, 42).unwrap();
1856
1857 assert!(completed);
1858 assert!(buf.is_empty());
1859 logger.check_target_logs(
1860 "stv_rs::meek",
1861 [
1862 (
1863 Debug,
1864 "Checking if count is complete: candidates=2, to_elect=0",
1865 ),
1866 (Info, "Count is now complete!"),
1867 ],
1868 );
1869
1870 let logger = ThreadLocalLogger::start();
1872 let election = make_election(3);
1873 let state = make_state(
1874 &election,
1875 [
1876 Status::Candidate,
1877 Status::Withdrawn,
1878 Status::Elected,
1879 Status::NotElected,
1880 Status::Candidate,
1881 ],
1882 );
1883
1884 let mut buf = Vec::new();
1885 let completed = state.election_completed(&mut buf, 42).unwrap();
1886
1887 assert!(completed);
1888 assert!(buf.is_empty());
1889 logger.check_target_logs(
1890 "stv_rs::meek",
1891 [
1892 (
1893 Debug,
1894 "Checking if count is complete: candidates=2, to_elect=2",
1895 ),
1896 (Info, "Count is now complete!"),
1897 ],
1898 );
1899
1900 let logger = ThreadLocalLogger::start();
1902 let election = make_election(2);
1903 let state = make_state(
1904 &election,
1905 [
1906 Status::Candidate,
1907 Status::Withdrawn,
1908 Status::Elected,
1909 Status::NotElected,
1910 Status::Candidate,
1911 ],
1912 );
1913
1914 let mut buf = Vec::new();
1915 let completed = state.election_completed(&mut buf, 42).unwrap();
1916
1917 assert!(!completed);
1918 assert_eq!(std::str::from_utf8(&buf).unwrap(), "Round 42:\n");
1919 logger.check_target_level_logs(
1920 "stv_rs::meek",
1921 Debug,
1922 r"Checking if count is complete: candidates=2, to_elect=1
1923Weights:
1924 [0] apple (Candidate) ~ 0
1925 [1] banana (Withdrawn) ~ 0
1926 [2] cherry (Elected) ~ 0
1927 [3] date (NotElected) ~ 0
1928 [4] eggplant (Candidate) ~ 0
1929",
1930 );
1931 }
1932
1933 #[test]
1934 fn test_election_round() {
1935 fn make_election(ballots: impl Into<Vec<Ballot>>) -> Election {
1936 Election::builder()
1937 .title("Vegetable contest")
1938 .num_seats(2)
1939 .candidates([
1940 Candidate::new("apple", false),
1941 Candidate::new("banana", false),
1942 Candidate::new("cherry", false),
1943 Candidate::new("date", false),
1944 ])
1945 .ballots(ballots)
1946 .build()
1947 }
1948
1949 fn make_state(
1950 election: &Election,
1951 statuses: impl Into<Vec<Status>>,
1952 ) -> State<'_, '_, Integer64, FixedDecimal9> {
1953 State::builder()
1954 .election(election)
1955 .statuses(statuses)
1956 .keep_factors([FixedDecimal9::one(); 4])
1957 .threshold(FixedDecimal9::zero())
1958 .surplus(FixedDecimal9::zero())
1959 .omega(FixedDecimal9::ratio(1, 1_000_000))
1960 .parallel(Parallel::No)
1961 .force_positive_surplus(false)
1962 .pascal(None)
1963 .build()
1964 }
1965
1966 fn make_count() -> VoteCount<Integer64, FixedDecimal9> {
1967 VoteCount::new([FixedDecimal9::zero(); 4], FixedDecimal9::zero())
1968 }
1969
1970 let election = make_election([]);
1972 let mut state = make_state(&election, [Status::Candidate; 4]);
1973 let mut count = make_count();
1974
1975 let mut buf = Vec::new();
1976 state.election_round(&mut buf, &mut count).unwrap();
1977 assert_eq!(
1978 (
1979 state.threshold,
1980 state.surplus,
1981 state.statuses,
1982 state.keep_factors
1983 ),
1984 (
1985 FixedDecimal9::epsilon(),
1986 FixedDecimal9::zero(),
1987 vec![
1988 Status::NotElected,
1989 Status::Candidate,
1990 Status::Candidate,
1991 Status::Candidate,
1992 ],
1993 vec![
1994 FixedDecimal9::zero(),
1995 FixedDecimal9::one(),
1996 FixedDecimal9::one(),
1997 FixedDecimal9::one(),
1998 ]
1999 )
2000 );
2001 assert_eq!(
2002 count,
2003 VoteCount::new([FixedDecimal9::zero(); 4], FixedDecimal9::zero())
2004 );
2005 assert_eq!(
2006 std::str::from_utf8(&buf).unwrap(),
2007 r"Action: Iterate (omega)
2008 Quota: 0.000000001
2009 Votes: 0.000000000
2010 Residual: 0.000000000
2011 Total: 0.000000000
2012 Surplus: 0.000000000
2013Action: Break tie (defeat): [Apple, Banana, Cherry, Date] -> Apple
2014 Quota: 0.000000001
2015 Votes: 0.000000000
2016 Residual: 0.000000000
2017 Total: 0.000000000
2018 Surplus: 0.000000000
2019Action: Defeat (surplus 0.000000000 < omega): Apple
2020 Hopeful: Banana (0.000000000)
2021 Hopeful: Cherry (0.000000000)
2022 Hopeful: Date (0.000000000)
2023 Defeated: Apple (0.000000000)
2024 Quota: 0.000000001
2025 Votes: 0.000000000
2026 Residual: 0.000000000
2027 Total: 0.000000000
2028 Surplus: 0.000000000
2029"
2030 );
2031
2032 let election = make_election([Ballot::new(1, [vec![1]])]);
2034 let mut state = make_state(&election, [Status::Candidate; 4]);
2035 let mut count = make_count();
2036
2037 let mut buf = Vec::new();
2038 state.election_round(&mut buf, &mut count).unwrap();
2039 assert_eq!(
2040 (
2041 state.threshold,
2042 state.surplus,
2043 state.statuses,
2044 state.keep_factors
2045 ),
2046 (
2047 FixedDecimal9::ratio(1, 3) + FixedDecimal9::epsilon(),
2048 FixedDecimal9::ratio(2, 3),
2049 vec![
2050 Status::Candidate,
2051 Status::Elected,
2052 Status::Candidate,
2053 Status::Candidate,
2054 ],
2055 vec![FixedDecimal9::one(); 4]
2056 )
2057 );
2058 assert_eq!(
2059 count,
2060 VoteCount::new(
2061 [
2062 FixedDecimal9::zero(),
2063 FixedDecimal9::one(),
2064 FixedDecimal9::zero(),
2065 FixedDecimal9::zero(),
2066 ],
2067 FixedDecimal9::zero(),
2068 )
2069 );
2070 assert_eq!(
2071 std::str::from_utf8(&buf).unwrap(),
2072 r"Action: Elect: Banana
2073 Elected: Banana (1.000000000)
2074 Hopeful: Apple (0.000000000)
2075 Hopeful: Cherry (0.000000000)
2076 Hopeful: Date (0.000000000)
2077 Quota: 0.333333334
2078 Votes: 1.000000000
2079 Residual: 0.000000000
2080 Total: 1.000000000
2081 Surplus: 0.000000000
2082Action: Iterate (elected)
2083 Quota: 0.333333334
2084 Votes: 1.000000000
2085 Residual: 0.000000000
2086 Total: 1.000000000
2087 Surplus: 0.666666666
2088"
2089 );
2090
2091 let election = make_election([
2093 Ballot::new(100, [vec![1]]),
2094 Ballot::new(1, [vec![0]]),
2095 Ballot::new(2, [vec![2]]),
2096 Ballot::new(3, [vec![3]]),
2097 ]);
2098 let mut state = make_state(
2099 &election,
2100 [
2101 Status::Candidate,
2102 Status::Elected,
2103 Status::Candidate,
2104 Status::Candidate,
2105 ],
2106 );
2107 let mut count = make_count();
2108
2109 let mut buf = Vec::new();
2110 state.election_round(&mut buf, &mut count).unwrap();
2111 assert_eq!(
2112 (
2113 state.threshold,
2114 state.surplus,
2115 state.statuses,
2116 state.keep_factors
2117 ),
2118 (
2119 FixedDecimal9::new(3_000_000_301),
2120 FixedDecimal9::new(599),
2121 vec![
2122 Status::NotElected,
2123 Status::Elected,
2124 Status::Candidate,
2125 Status::Candidate,
2126 ],
2127 vec![
2128 FixedDecimal9::zero(),
2129 FixedDecimal9::new(30_000_009),
2130 FixedDecimal9::one(),
2131 FixedDecimal9::one(),
2132 ]
2133 )
2134 );
2135 assert_eq!(
2136 count,
2137 VoteCount::new(
2138 [
2139 FixedDecimal9::zero(),
2140 FixedDecimal9::new(3_000_000_900),
2141 FixedDecimal9::from_usize(2),
2142 FixedDecimal9::from_usize(3),
2143 ],
2144 FixedDecimal9::new(97_999_999_100),
2145 )
2146 );
2147 assert_eq!(
2148 std::str::from_utf8(&buf).unwrap(),
2149 r"Action: Iterate (omega)
2150 Quota: 3.000000301
2151 Votes: 9.000000900
2152 Residual: 96.999999100
2153 Total: 106.000000000
2154 Surplus: 0.000000599
2155Action: Defeat (surplus 0.000000599 < omega): Apple
2156 Elected: Banana (3.000000900)
2157 Hopeful: Cherry (2.000000000)
2158 Hopeful: Date (3.000000000)
2159 Defeated: Apple (1.000000000)
2160 Quota: 3.000000301
2161 Votes: 9.000000900
2162 Residual: 96.999999100
2163 Total: 106.000000000
2164 Surplus: 0.000000599
2165"
2166 );
2167
2168 let election = make_election([
2170 Ballot::new(100_000, [vec![1]]),
2171 Ballot::new(1, [vec![0]]),
2172 Ballot::new(2, [vec![2]]),
2173 Ballot::new(3, [vec![3]]),
2174 ]);
2175 let mut state = make_state(
2176 &election,
2177 [
2178 Status::Candidate,
2179 Status::Elected,
2180 Status::Candidate,
2181 Status::Candidate,
2182 ],
2183 );
2184 let mut count = make_count();
2185
2186 let mut buf = Vec::new();
2187 state.election_round(&mut buf, &mut count).unwrap();
2188 assert_eq!(
2189 (
2190 state.threshold,
2191 state.surplus,
2192 state.statuses,
2193 state.keep_factors
2194 ),
2195 (
2196 FixedDecimal9::new(3_000_033_334),
2197 FixedDecimal9::new(66_666),
2198 vec![
2199 Status::NotElected,
2200 Status::Elected,
2201 Status::Candidate,
2202 Status::Candidate,
2203 ],
2204 vec![
2205 FixedDecimal9::zero(),
2206 FixedDecimal9::new(30_001),
2207 FixedDecimal9::one(),
2208 FixedDecimal9::one(),
2209 ]
2210 )
2211 );
2212 assert_eq!(
2213 count,
2214 VoteCount::new(
2215 [
2216 FixedDecimal9::zero(),
2217 FixedDecimal9::new(3_000_100_000),
2218 FixedDecimal9::from_usize(2),
2219 FixedDecimal9::from_usize(3),
2220 ],
2221 FixedDecimal9::new(99_997_999_900_000),
2222 )
2223 );
2224 assert_eq!(
2225 std::str::from_utf8(&buf).unwrap(),
2226 r" Stable state detected (0.000066666)
2227Action: Iterate (stable)
2228 Quota: 3.000033334
2229 Votes: 9.000100000
2230 Residual: 99996.999900000
2231 Total: 100006.000000000
2232 Surplus: 0.000066666
2233Action: Defeat (stable surplus 0.000066666): Apple
2234 Elected: Banana (3.000100000)
2235 Hopeful: Cherry (2.000000000)
2236 Hopeful: Date (3.000000000)
2237 Defeated: Apple (1.000000000)
2238 Quota: 3.000033334
2239 Votes: 9.000100000
2240 Residual: 99996.999900000
2241 Total: 100006.000000000
2242 Surplus: 0.000066666
2243"
2244 );
2245 }
2246
2247 #[test]
2248 fn test_iterate_droop() {
2249 fn make_election(ballots: impl Into<Vec<Ballot>>) -> Election {
2250 Election::builder()
2251 .title("Vegetable contest")
2252 .num_seats(2)
2253 .candidates([
2254 Candidate::new("apple", false),
2255 Candidate::new("banana", false),
2256 Candidate::new("cherry", false),
2257 Candidate::new("date", false),
2258 ])
2259 .ballots(ballots)
2260 .build()
2261 }
2262
2263 fn make_state(
2264 election: &Election,
2265 statuses: impl Into<Vec<Status>>,
2266 ) -> State<'_, '_, Integer64, FixedDecimal9> {
2267 State::builder()
2268 .election(election)
2269 .statuses(statuses)
2270 .keep_factors([FixedDecimal9::one(); 4])
2271 .threshold(FixedDecimal9::zero())
2272 .surplus(FixedDecimal9::zero())
2273 .omega(FixedDecimal9::ratio(1, 1_000_000))
2274 .parallel(Parallel::No)
2275 .force_positive_surplus(false)
2276 .pascal(None)
2277 .build()
2278 }
2279
2280 fn make_count() -> VoteCount<Integer64, FixedDecimal9> {
2281 VoteCount::new([FixedDecimal9::zero(); 4], FixedDecimal9::zero())
2282 }
2283
2284 let election = make_election([]);
2286 let mut state = make_state(&election, [Status::Candidate; 4]);
2287 let mut count = make_count();
2288
2289 let mut buf = Vec::new();
2290 let iteration = state.iterate_droop(&mut buf, &mut count).unwrap();
2291 assert_eq!(iteration, DroopIteration::Omega);
2292 assert_eq!(
2294 (
2295 state.threshold,
2296 state.surplus,
2297 state.statuses,
2298 state.keep_factors
2299 ),
2300 (
2301 FixedDecimal9::epsilon(),
2302 FixedDecimal9::zero(),
2303 vec![Status::Candidate; 4],
2304 vec![FixedDecimal9::one(); 4]
2305 )
2306 );
2307 assert_eq!(
2308 count,
2309 VoteCount::new([FixedDecimal9::zero(); 4], FixedDecimal9::zero())
2310 );
2311 assert!(buf.is_empty());
2312
2313 let election = make_election([Ballot::new(1, [vec![1]])]);
2315 let mut state = make_state(&election, [Status::Candidate; 4]);
2316 let mut count = make_count();
2317
2318 let mut buf = Vec::new();
2319 let iteration = state.iterate_droop(&mut buf, &mut count).unwrap();
2320 assert_eq!(iteration, DroopIteration::Elected);
2321 assert_eq!(
2322 (
2323 state.threshold,
2324 state.surplus,
2325 state.statuses,
2326 state.keep_factors
2327 ),
2328 (
2329 FixedDecimal9::ratio(1, 3) + FixedDecimal9::epsilon(),
2330 FixedDecimal9::ratio(2, 3),
2331 vec![
2332 Status::Candidate,
2333 Status::Elected,
2334 Status::Candidate,
2335 Status::Candidate,
2336 ],
2337 vec![FixedDecimal9::one(); 4]
2338 )
2339 );
2340 assert_eq!(
2341 count,
2342 VoteCount::new(
2343 [
2344 FixedDecimal9::zero(),
2345 FixedDecimal9::one(),
2346 FixedDecimal9::zero(),
2347 FixedDecimal9::zero(),
2348 ],
2349 FixedDecimal9::zero(),
2350 )
2351 );
2352 assert_eq!(
2353 std::str::from_utf8(&buf).unwrap(),
2354 r"Action: Elect: Banana
2355 Elected: Banana (1.000000000)
2356 Hopeful: Apple (0.000000000)
2357 Hopeful: Cherry (0.000000000)
2358 Hopeful: Date (0.000000000)
2359 Quota: 0.333333334
2360 Votes: 1.000000000
2361 Residual: 0.000000000
2362 Total: 1.000000000
2363 Surplus: 0.000000000
2364"
2365 );
2366
2367 let election = make_election([Ballot::new(3, [vec![1]]), Ballot::new(1, [vec![3]])]);
2369 let mut state = make_state(
2370 &election,
2371 [
2372 Status::Candidate,
2373 Status::Elected,
2374 Status::Candidate,
2375 Status::Candidate,
2376 ],
2377 );
2378 let mut count = make_count();
2379
2380 let mut buf = Vec::new();
2381 let iteration = state.iterate_droop(&mut buf, &mut count).unwrap();
2382 assert_eq!(iteration, DroopIteration::Elected);
2383 assert_eq!(
2384 (
2385 state.threshold,
2386 state.surplus,
2387 state.statuses,
2388 state.keep_factors
2389 ),
2390 (
2391 FixedDecimal9::new(777_777_779),
2392 FixedDecimal9::new(777_777_777),
2393 vec![
2394 Status::Candidate,
2395 Status::Elected,
2396 Status::Candidate,
2397 Status::Elected,
2398 ],
2399 vec![
2400 FixedDecimal9::one(),
2401 FixedDecimal9::new(444_444_445),
2402 FixedDecimal9::one(),
2403 FixedDecimal9::one(),
2404 ]
2405 )
2406 );
2407 assert_eq!(
2408 count,
2409 VoteCount::new(
2410 [
2411 FixedDecimal9::zero(),
2412 FixedDecimal9::new(1_333_333_335),
2413 FixedDecimal9::zero(),
2414 FixedDecimal9::one(),
2415 ],
2416 FixedDecimal9::new(1_666_666_665),
2417 )
2418 );
2419 assert_eq!(
2420 std::str::from_utf8(&buf).unwrap(),
2421 r"Action: Elect: Date
2422 Elected: Banana (1.333333335)
2423 Elected: Date (1.000000000)
2424 Hopeful: Apple (0.000000000)
2425 Hopeful: Cherry (0.000000000)
2426 Quota: 0.777777779
2427 Votes: 2.333333335
2428 Residual: 1.666666665
2429 Total: 4.000000000
2430 Surplus: 1.666666666
2431"
2432 );
2433
2434 let election = make_election([Ballot::new(1, [vec![1]])]);
2436 let mut state = make_state(
2437 &election,
2438 [
2439 Status::Candidate,
2440 Status::Elected,
2441 Status::Candidate,
2442 Status::Candidate,
2443 ],
2444 );
2445 let mut count = make_count();
2446
2447 let mut buf = Vec::new();
2448 let iteration = state.iterate_droop(&mut buf, &mut count).unwrap();
2449 assert_eq!(iteration, DroopIteration::Stable);
2450 assert_eq!(
2451 (
2452 state.threshold,
2453 state.surplus,
2454 state.statuses,
2455 state.keep_factors
2456 ),
2457 (
2458 FixedDecimal9::new(17_438),
2459 FixedDecimal9::new(34_875),
2460 vec![
2461 Status::Candidate,
2462 Status::Elected,
2463 Status::Candidate,
2464 Status::Candidate,
2465 ],
2466 vec![
2467 FixedDecimal9::one(),
2468 FixedDecimal9::new(52_313),
2469 FixedDecimal9::one(),
2470 FixedDecimal9::one(),
2471 ]
2472 )
2473 );
2474 assert_eq!(
2475 count,
2476 VoteCount::new(
2477 [
2478 FixedDecimal9::zero(),
2479 FixedDecimal9::new(52_313),
2480 FixedDecimal9::zero(),
2481 FixedDecimal9::zero(),
2482 ],
2483 FixedDecimal9::new(999_947_687),
2484 )
2485 );
2486 assert_eq!(
2487 std::str::from_utf8(&buf).unwrap(),
2488 r" Stable state detected (0.000034875)
2489"
2490 );
2491 }
2492
2493 #[test]
2494 fn test_elect_candidates() {
2495 let election = Election::builder()
2496 .title("Vegetable contest")
2497 .num_seats(4)
2498 .candidates([
2499 Candidate::new("apple", false),
2500 Candidate::new("banana", true),
2501 Candidate::new("cherry", false),
2502 Candidate::new("date", false),
2503 Candidate::new("eggplant", false),
2504 Candidate::new("fig", false),
2505 Candidate::new("grape", false),
2506 ])
2507 .build();
2508 let mut state = State::builder()
2509 .election(&election)
2510 .statuses([
2511 Status::Candidate,
2512 Status::Withdrawn,
2513 Status::Elected,
2514 Status::NotElected,
2515 Status::Candidate,
2516 Status::Candidate,
2517 Status::Candidate,
2518 ])
2519 .keep_factors([FixedDecimal9::zero(); 7])
2520 .threshold(FixedDecimal9::ratio(3, 2))
2521 .surplus(FixedDecimal9::zero())
2522 .omega(FixedDecimal9::zero())
2523 .parallel(Parallel::No)
2524 .force_positive_surplus(false)
2525 .pascal(None)
2526 .build();
2527 let count = VoteCount::new(
2528 [
2529 FixedDecimal9::from_usize(1),
2530 FixedDecimal9::zero(),
2531 FixedDecimal9::from_usize(2),
2532 FixedDecimal9::zero(),
2533 FixedDecimal9::from_usize(3),
2534 FixedDecimal9::new(1_499_999_999),
2535 FixedDecimal9::ratio(3, 2),
2536 ],
2537 FixedDecimal9::zero(),
2538 );
2539
2540 let mut buf = Vec::new();
2541 state.elect_candidates(&mut buf, &count).unwrap();
2542
2543 assert_eq!(
2544 state.statuses,
2545 vec![
2546 Status::Candidate,
2547 Status::Withdrawn,
2548 Status::Elected,
2549 Status::NotElected,
2550 Status::Elected,
2551 Status::Candidate,
2552 Status::Elected,
2553 ]
2554 );
2555 assert_eq!(
2556 std::str::from_utf8(&buf).unwrap(),
2557 r"Action: Elect: Eggplant
2558 Elected: Cherry (2.000000000)
2559 Elected: Eggplant (3.000000000)
2560 Hopeful: Apple (1.000000000)
2561 Hopeful: Fig (1.499999999)
2562 Hopeful: Grape (1.500000000)
2563 Defeated: Date (0.000000000)
2564 Quota: 1.500000000
2565 Votes: 8.999999999
2566 Residual: 0.000000000
2567 Total: 8.999999999
2568 Surplus: 0.000000000
2569Action: Elect: Grape
2570 Elected: Cherry (2.000000000)
2571 Elected: Eggplant (3.000000000)
2572 Elected: Grape (1.500000000)
2573 Hopeful: Apple (1.000000000)
2574 Hopeful: Fig (1.499999999)
2575 Defeated: Date (0.000000000)
2576 Quota: 1.500000000
2577 Votes: 8.999999999
2578 Residual: 0.000000000
2579 Total: 8.999999999
2580 Surplus: 0.000000000
2581"
2582 );
2583 }
2584
2585 #[test]
2586 fn test_elect_candidates_exact() {
2587 let election = Election::builder()
2588 .title("Vegetable contest")
2589 .num_seats(4)
2590 .candidates([
2591 Candidate::new("apple", false),
2592 Candidate::new("banana", true),
2593 Candidate::new("cherry", false),
2594 Candidate::new("date", false),
2595 Candidate::new("eggplant", false),
2596 Candidate::new("fig", false),
2597 Candidate::new("grape", false),
2598 ])
2599 .build();
2600 let mut state = State::builder()
2601 .election(&election)
2602 .statuses([
2603 Status::Candidate,
2604 Status::Withdrawn,
2605 Status::Elected,
2606 Status::NotElected,
2607 Status::Candidate,
2608 Status::Candidate,
2609 Status::Candidate,
2610 ])
2611 .keep_factors([
2612 BigRational::zero(),
2613 BigRational::zero(),
2614 BigRational::zero(),
2615 BigRational::zero(),
2616 BigRational::zero(),
2617 BigRational::zero(),
2618 BigRational::zero(),
2619 ])
2620 .threshold(BigRational::ratio(3, 2))
2621 .surplus(BigRational::zero())
2622 .omega(BigRational::zero())
2623 .parallel(Parallel::No)
2624 .force_positive_surplus(false)
2625 .pascal(None)
2626 .build();
2627 let count = VoteCount::new(
2628 [
2629 BigRational::from_usize(1),
2630 BigRational::zero(),
2631 BigRational::from_usize(2),
2632 BigRational::zero(),
2633 BigRational::from_usize(3),
2634 BigRational::ratio(1_499_999_999, 1_000_000_000),
2635 BigRational::ratio(3, 2),
2636 ],
2637 BigRational::zero(),
2638 );
2639
2640 let mut buf = Vec::new();
2641 state.elect_candidates(&mut buf, &count).unwrap();
2642
2643 assert_eq!(
2644 state.statuses,
2645 vec![
2646 Status::Candidate,
2647 Status::Withdrawn,
2648 Status::Elected,
2649 Status::NotElected,
2650 Status::Elected,
2651 Status::Candidate,
2652 Status::Candidate,
2653 ]
2654 );
2655 assert_eq!(
2656 std::str::from_utf8(&buf).unwrap(),
2657 r"Action: Elect: Eggplant
2658 Elected: Cherry (2)
2659 Elected: Eggplant (3)
2660 Hopeful: Apple (1)
2661 Hopeful: Fig (1499999999/1000000000)
2662 Hopeful: Grape (3/2)
2663 Defeated: Date (0)
2664 Quota: 3/2
2665 Votes: 8999999999/1000000000
2666 Residual: 0
2667 Total: 8999999999/1000000000
2668 Surplus: 0
2669"
2670 );
2671 }
2672
2673 #[test]
2674 fn test_handle_remaining_candidates_elected() {
2675 let mut election = make_fake_election();
2676 election.num_seats = 4;
2677 let mut state = make_fake_state(&election);
2678 let mut count = make_fake_count();
2679
2680 let mut buf = Vec::new();
2681 state
2682 .handle_remaining_candidates(&mut buf, &mut count)
2683 .unwrap();
2684
2685 assert_eq!(
2686 state.statuses,
2687 vec![
2688 Status::Elected,
2689 Status::Withdrawn,
2690 Status::Elected,
2691 Status::NotElected,
2692 Status::Elected,
2693 Status::NotElected,
2694 Status::Elected,
2695 Status::NotElected,
2696 Status::NotElected,
2697 ]
2698 );
2699 assert_eq!(
2700 std::str::from_utf8(&buf).unwrap(),
2701 r"Action: Elect remaining: Apple
2702 Elected: Apple (0.777777777)
2703 Elected: Cherry (1.666666666)
2704 Elected: Grape (1.833333333)
2705 Hopeful: Eggplant (0.875000000)
2706 Defeated: Date (0.100000000)
2707 Defeated: Hazelnut (0.200000000)
2708 Defeated: Fig, Jalapeno (0.000000000)
2709 Quota: 1.500000000
2710 Votes: 5.452777776
2711 Residual: 0.547222224
2712 Total: 6.000000000
2713 Surplus: 0.100000000
2714Action: Elect remaining: Eggplant
2715 Elected: Apple (0.000000000)
2716 Elected: Cherry (0.000000000)
2717 Elected: Eggplant (0.000000000)
2718 Elected: Grape (0.000000000)
2719 Defeated: Date, Fig, Hazelnut, Jalapeno (0.000000000)
2720 Quota: 1.500000000
2721 Votes: 0.000000000
2722 Residual: 0.000000000
2723 Total: 0.000000000
2724 Surplus: 0.100000000
2725"
2726 );
2727 }
2728
2729 #[test]
2730 fn test_handle_remaining_candidates_defeated() {
2731 let mut election = make_fake_election();
2732 election.num_seats = 2;
2733 let mut state = make_fake_state(&election);
2734 let mut count = make_fake_count();
2735
2736 let mut buf = Vec::new();
2737 state
2738 .handle_remaining_candidates(&mut buf, &mut count)
2739 .unwrap();
2740
2741 assert_eq!(
2742 state.statuses,
2743 vec![
2744 Status::NotElected,
2745 Status::Withdrawn,
2746 Status::Elected,
2747 Status::NotElected,
2748 Status::NotElected,
2749 Status::NotElected,
2750 Status::Elected,
2751 Status::NotElected,
2752 Status::NotElected,
2753 ]
2754 );
2755 assert_eq!(
2756 std::str::from_utf8(&buf).unwrap(),
2757 r"Action: Defeat remaining: Apple
2758 Elected: Cherry (1.666666666)
2759 Elected: Grape (1.833333333)
2760 Hopeful: Eggplant (0.875000000)
2761 Defeated: Apple (0.777777777)
2762 Defeated: Date (0.100000000)
2763 Defeated: Hazelnut (0.200000000)
2764 Defeated: Fig, Jalapeno (0.000000000)
2765 Quota: 1.500000000
2766 Votes: 5.452777776
2767 Residual: 0.547222224
2768 Total: 6.000000000
2769 Surplus: 0.100000000
2770Action: Defeat remaining: Eggplant
2771 Elected: Cherry (0.000000000)
2772 Elected: Grape (0.000000000)
2773 Defeated: Apple, Date, Eggplant, Fig, Hazelnut, Jalapeno (0.000000000)
2774 Quota: 1.500000000
2775 Votes: 0.000000000
2776 Residual: 0.000000000
2777 Total: 0.000000000
2778 Surplus: 0.100000000
2779"
2780 );
2781 }
2782
2783 #[test]
2784 fn test_next_defeated_candidate() {
2785 let election = Election::builder()
2786 .title("Vegetable contest")
2787 .num_seats(2)
2788 .candidates([
2789 Candidate::new("apple", false),
2790 Candidate::new("banana", false),
2791 Candidate::new("cherry", false),
2792 Candidate::new("date", false),
2793 ])
2794 .build();
2795 let state = State::builder()
2796 .election(&election)
2797 .statuses([
2798 Status::Elected,
2799 Status::Candidate,
2800 Status::NotElected,
2801 Status::Candidate,
2802 ])
2803 .keep_factors([FixedDecimal9::zero(); 4])
2804 .threshold(FixedDecimal9::zero())
2805 .surplus(FixedDecimal9::ratio(1, 10))
2806 .omega(FixedDecimal9::zero())
2807 .parallel(Parallel::No)
2808 .force_positive_surplus(false)
2809 .pascal(None)
2810 .build();
2811
2812 let count = VoteCount::new(
2814 [
2815 FixedDecimal9::from_usize(1),
2816 FixedDecimal9::ratio(3, 10),
2817 FixedDecimal9::zero(),
2818 FixedDecimal9::ratio(5, 10),
2819 ],
2820 FixedDecimal9::zero(),
2821 );
2822
2823 let logger = ThreadLocalLogger::start();
2824 let mut buf = Vec::new();
2825 let next = state.next_defeated_candidate(&mut buf, &count).unwrap();
2826
2827 assert_eq!(next, 1);
2828 logger.check_target_level_logs(
2829 "stv_rs::meek",
2830 Debug,
2831 r"Lowest vote: 0.300000000 ~ 0.3
2832Low threshold: 0.400000000 ~ 0.4
2833Low candidates: [1]
2834",
2835 );
2836 assert!(buf.is_empty());
2837
2838 let count = VoteCount::new(
2840 [
2841 FixedDecimal9::from_usize(1),
2842 FixedDecimal9::ratio(3, 10),
2843 FixedDecimal9::zero(),
2844 FixedDecimal9::ratio(3, 10),
2845 ],
2846 FixedDecimal9::zero(),
2847 );
2848
2849 let logger = ThreadLocalLogger::start();
2850 let mut buf = Vec::new();
2851 let next = state.next_defeated_candidate(&mut buf, &count).unwrap();
2852
2853 assert_eq!(next, 1);
2854 logger.check_target_level_logs(
2855 "stv_rs::meek",
2856 Debug,
2857 r"Lowest vote: 0.300000000 ~ 0.3
2858Low threshold: 0.400000000 ~ 0.4
2859Low candidates: [1, 3]
2860",
2861 );
2862 assert_eq!(
2863 std::str::from_utf8(&buf).unwrap(),
2864 r"Action: Break tie (defeat): [Banana, Date] -> Banana
2865 Quota: 0.000000000
2866 Votes: 1.600000000
2867 Residual: 0.000000000
2868 Total: 1.600000000
2869 Surplus: 0.100000000
2870"
2871 );
2872 }
2873
2874 #[test]
2875 fn test_write_candidate_counts() {
2876 let election = make_fake_election();
2877 let state = make_fake_state(&election);
2878 let count = make_fake_count();
2879
2880 let mut buf = Vec::new();
2881 state.write_candidate_counts(&mut buf, &count).unwrap();
2882
2883 assert_eq!(
2884 std::str::from_utf8(&buf).unwrap(),
2885 r" Elected: Cherry (1.666666666)
2886 Elected: Grape (1.833333333)
2887 Hopeful: Apple (0.777777777)
2888 Hopeful: Eggplant (0.875000000)
2889 Defeated: Date (0.100000000)
2890 Defeated: Hazelnut (0.200000000)
2891 Defeated: Fig, Jalapeno (0.000000000)
2892"
2893 );
2894 }
2895
2896 #[test]
2897 fn test_debug_count() {
2898 let logger = ThreadLocalLogger::start();
2899
2900 let election = make_fake_election();
2901 let state = make_fake_state(&election);
2902 let count = make_fake_count();
2903 state.debug_count(&count);
2904
2905 logger.check_target_level_logs(
2906 "stv_rs::meek",
2907 Debug,
2908 r"Sums:
2909 [0] grape (Elected) ~ 1.833333333
2910 [1] cherry (Elected) ~ 1.666666666
2911 [2] eggplant (Candidate) ~ 0.875
2912 [3] apple (Candidate) ~ 0.777777777
2913 [4] hazelnut (NotElected) ~ 0.2
2914 [5] date (NotElected) ~ 0.1
2915 [6] jalapeno (NotElected) ~ 0
2916 [7] fig (NotElected) ~ 0
2917 [8] banana (Withdrawn) ~ 0
2918 @exhausted ~ 0.547222224
2919Sum = 5.452777776
2920Total = 6.000000000
2921Elected:
2922 [0] cherry
2923 [1] grape
2924Not elected:
2925 [8] date
2926 [7] fig
2927 [6] hazelnut
2928 [5] jalapeno
2929",
2930 );
2931 }
2932}