stv_rs/
meek.rs

1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Single Transferable Vote implementation of Meek's algorithm. This aims to
16//! give consistent results with Droop.py.
17
18use 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/// Runs an election according to Meek's rules. This aims to produce
31/// reproducible results w.r.t. Droop.py.
32#[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/// Status of a candidate during the vote-counting process.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141enum Status {
142    /// Candidate that can still be either elected or defeated.
143    Candidate,
144    /// Candidate that has withdrawn from the election, and will not be elected.
145    Withdrawn,
146    /// Candidate that was elected.
147    Elected,
148    /// Candidate that was not elected.
149    NotElected,
150}
151
152/// Result of a Droop iteration.
153#[derive(Debug, PartialEq, Eq)]
154enum DroopIteration {
155    /// A candidate was elected in this iteration.
156    Elected,
157    /// The iteration reached a stable state of counted votes and keep factors.
158    Stable,
159    /// The surplus decreased below the omega threshold.
160    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
173/// Running state while computing the election results.
174pub struct State<'scope, 'e, I, R> {
175    /// Election input.
176    election: &'e Election,
177    /// Status of each candidate.
178    statuses: Vec<Status>,
179    /// List of elected candidates.
180    elected: Vec<usize>,
181    /// List of non-elected candidates.
182    not_elected: Vec<usize>,
183    /// Number of candidates remaining to elect.
184    to_elect: usize,
185    /// Keep factor of each candidate.
186    keep_factors: Vec<R>,
187    /// Current threshold for a candidate to be elected.
188    threshold: R,
189    /// Surplus of voting power distributed to elected candidates, beyond the
190    /// required threshold.
191    surplus: R,
192    /// When the surplus becomes smaller than this paramter, an election
193    /// iteration is considered stabilized.
194    omega: R,
195    /// Whether parallel ballot counting (based on the rayon crate) is enabled.
196    parallel: Parallel,
197    /// Whether to use a positive surplus calculation (which avoids potential
198    /// crashes if the surplus otherwise becomes negative.
199    force_positive_surplus: bool,
200    /// Pre-computed Pascal triangle. Set only if the "equalized counting" is
201    /// enabled.
202    pascal: Option<&'e [Vec<I>]>,
203    /// Thread pool where to schedule vote counting. Set only if the parallel
204    /// mode is set to "custom".
205    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    /// Runs the main loop of Meek's rules.
260    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    /// Elects a candidate, and updates the state accordingly.
308    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    /// Defeats a candidate, and updates the state accordingly.
320    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    /// Returns the number of candidates.
330    fn num_candidates(&self) -> usize {
331        self.election.num_candidates
332    }
333
334    /// Counts the ballots based on the current keep factors.
335    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    /// Performs the initial counting to start the election.
346    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        // Droop somehow doesn't report exhausted count on the initial count.
388        count.exhausted = R::zero();
389
390        self.write_action(stdout, "Begin Count", &count, true)?;
391
392        Ok(count)
393    }
394
395    /// Returns true if the election is complete.
396    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    /// Runs one election round, which either elects or defeats exactly one
438    /// candidate.
439    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    /// Iterates the vote counting process until either a candidate is elected,
478    /// the surplus becomes lower than omega, or the count has stabilized.
479    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            // Update keep factors of elected candidates.
517            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    /// Elect candidates that exceed the threshold, returning true if any
541    /// candidate was elected.
542    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                // Elected candidates must always keep at least the threshold of ballots.
551                // Otherwise their keep factor was too low, i.e. too much of
552                // their ballots were unduly transfered to other candidates.
553                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                // Already non-elected candidates must have no ballot left.
566                Status::NotElected => assert!(count.sum[i].is_zero()),
567                Status::Candidate => {
568                    // Elect candidates that exceeded the threshold.
569                    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                        // We cannot elect more candidates than seats.
576                        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    /// Elects or defeat all remaining candidates, once a quorum is defeated or
594    /// elected (respectively).
595    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    /// Returns the next candidate to defeat.
629    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    /// Writes an action to the given output.
693    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    /// Writes current candidate counts to the given output.
709    fn write_candidate_counts(
710        &self,
711        stdout: &mut impl io::Write,
712        count: &VoteCount<I, R>,
713    ) -> io::Result<()> {
714        // Sort candidates: elected first, then candidates and lastly non-elected.
715        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        // Find the first defeated candidate to have a zero count (if any).
729        let split = droop_sorted_candidates
730            .iter()
731            .position(|&i| self.statuses[i] == Status::NotElected && count.sum[i] == R::zero());
732
733        // Simply print the candidates with a non-zero count.
734        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        // Candidates beyond the first defeated one with a zero count must all be
749        // defeated and with a zero count.
750        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    /// Displays a debug output of the current vote counts.
767    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            /* num_threads = */ 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                /* num_threads = */ Some(NonZeroUsize::new(2).unwrap()),
1625                false,
1626                false,
1627                false,
1628            )
1629        });
1630
1631        // Unfortunately, Rust's test harness and/or the `catch_unwind()` function seem
1632        // to interfere with panic propagation, so we cannot test all the details of the
1633        // double-panic (worker thread + main thread) here in a unit test.
1634        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        // All remaining candidates should be defeated.
1841        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        // All remaining candidates should be elected.
1871        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        // The election is not completed yet.
1901        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        // No ballot.
1971        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        // One candidate is elected.
2033        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        // Omega threshold.
2092        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        // Stable iteration.
2169        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        // No ballot.
2285        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        // TODO: Use expectations rather than assertions.
2293        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        // One candidate is elected.
2314        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        // Second candidate is elected.
2368        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        // Iteration stabilizes.
2435        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        // One defeated candidate.
2813        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        // Tie break.
2839        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}