1use crate::arithmetic::{Integer, IntegerRef, Rational, RationalRef};
18use crate::types::{Election, ElectionResult};
19use log::{debug, info, trace};
20use std::io;
21
22pub fn plurality_block_voting<I, R>(
37 stdout: &mut impl io::Write,
38 election: &Election,
39 package_name: &str,
40 votes_per_ballot: Option<usize>,
41) -> io::Result<ElectionResult>
42where
43 I: Integer + Send + Sync,
44 for<'a> &'a I: IntegerRef<I>,
45 R: Rational<I> + Send + Sync,
46 for<'a> &'a R: RationalRef<&'a I, R>,
47{
48 let votes_per_ballot = votes_per_ballot.unwrap_or(election.num_seats);
49 info!(
50 "Plurality block voting with {} seats / {} candidates, votes per ballot = {votes_per_ballot}",
51 election.num_seats, election.num_candidates
52 );
53
54 writeln!(
55 stdout,
56 r"
57Election: {}
58
59 {package_name}
60 Rule: Simulated Plurality Block Voting
61 Arithmetic: {}
62 Seats: {}
63 Ballots: {}
64 Votes per ballot: {votes_per_ballot}
65",
66 election.title,
67 R::description(),
68 election.num_seats,
69 election.num_ballots,
70 )?;
71
72 let mut sum = vec![R::zero(); election.num_candidates];
73 for (i, ballot) in election.ballots.iter().enumerate() {
74 trace!("Processing ballot {i} = {:?}", ballot);
75
76 let mut votes_distributed = 0;
77 for ranking in ballot.order() {
78 let rank_len = ranking.len();
79 let weight = if votes_distributed + rank_len <= votes_per_ballot {
80 R::one()
82 } else {
83 R::from_usize(votes_per_ballot - votes_distributed) / I::from_usize(rank_len)
85 };
86 trace!(
87 " - {weight} * {:?}",
88 ranking.iter().map(|&x| x.into()).collect::<Vec<_>>()
89 );
90
91 let ballot_count = R::from_usize(ballot.count());
92 for &candidate in ranking {
93 sum[candidate.into()] += &weight * &ballot_count;
94 }
95
96 votes_distributed += rank_len;
97 if votes_distributed >= votes_per_ballot {
98 break;
99 }
100 }
101 }
102
103 let mut order: Vec<usize> = (0..election.num_candidates).collect();
104 order.sort_by(|&i, &j| {
105 sum[j].partial_cmp(&sum[i]).unwrap().then_with(|| {
106 election
107 .tie_order
108 .get(&i)
109 .unwrap()
110 .cmp(election.tie_order.get(&j).unwrap())
111 })
112 });
113
114 debug!("Sums:");
115 for (i, &c) in order.iter().enumerate() {
116 debug!(" [{i}] {} = {}", election.candidates[c].nickname, sum[c]);
117 }
118
119 let elected: Vec<usize> = order.iter().cloned().take(election.num_seats).collect();
120 let not_elected: Vec<usize> = order
121 .iter()
122 .cloned()
123 .rev()
124 .take(election.num_candidates - election.num_seats)
125 .filter(|&i| !election.candidates[i].is_withdrawn)
126 .collect();
127
128 writeln!(stdout, "Action: Count Complete")?;
129 info!("Elected:");
130 for (i, &id) in elected.iter().enumerate() {
131 info!(" [{}] {}", i, election.candidates[id].nickname);
132 writeln!(stdout, "Action: Elect: {}", election.candidates[id].name)?;
133 }
134
135 info!("Not elected:");
136 for (i, &id) in not_elected.iter().enumerate() {
137 info!(
138 " [{}] {}",
139 election.num_candidates - i - 1,
140 election.candidates[id].nickname
141 );
142 writeln!(stdout, "Action: Defeat: {}", election.candidates[id].name)?;
143 }
144
145 writeln!(stdout, "Action: Count Complete")?;
146 for &id in elected.iter() {
147 writeln!(
148 stdout,
149 "\tElected: {} ({})",
150 election.candidates[id].name, sum[id]
151 )?;
152 }
153 for &id in not_elected.iter().rev() {
154 writeln!(
155 stdout,
156 "\tDefeated: {} ({})",
157 election.candidates[id].name, sum[id]
158 )?;
159 }
160
161 let result = ElectionResult {
162 elected,
163 not_elected,
164 };
165 Ok(result)
166}
167
168#[cfg(test)]
169mod test {
170 use super::*;
171 use crate::arithmetic::{FixedDecimal9, Integer64};
172 use crate::types::{Ballot, Candidate};
173
174 fn make_election() -> Election {
175 Election::builder()
176 .title("Vegetable contest")
177 .num_seats(5)
178 .candidates([
179 Candidate::new("apple", false),
180 Candidate::new("banana", true),
181 Candidate::new("cherry", false),
182 Candidate::new("date", false),
183 Candidate::new("eggplant", false),
184 Candidate::new("fig", true),
185 Candidate::new("grape", false),
186 Candidate::new("hazelnut", false),
187 Candidate::new("jalapeno", false),
188 ])
189 .ballots([
190 Ballot::new(1, [vec![0], vec![2, 3, 4], vec![6]]),
191 Ballot::new(2, [vec![2], vec![3, 4], vec![6, 7]]),
192 Ballot::new(3, [vec![2, 3], vec![4, 6], vec![7, 8]]),
193 Ballot::new(4, [vec![3, 4], vec![6, 7], vec![8, 0]]),
194 Ballot::new(5, [vec![4], vec![6, 7, 8], vec![0]]),
195 Ballot::new(6, [vec![6], vec![7, 8, 0], vec![2]]),
196 Ballot::new(7, [vec![6, 7], vec![8, 0], vec![2, 3]]),
197 Ballot::new(8, [vec![7, 8], vec![0, 2], vec![3, 4]]),
198 Ballot::new(9, [vec![8, 0], vec![2, 3], vec![4]]),
199 ])
200 .build()
201 }
202
203 #[test]
204 fn test_plurality_block_voting() {
205 let election = make_election();
206
207 let mut buf = Vec::new();
208 let result = plurality_block_voting::<Integer64, FixedDecimal9>(
209 &mut buf,
210 &election,
211 "package name",
212 None,
213 )
214 .unwrap();
215 assert_eq!(
216 result,
217 ElectionResult {
218 elected: vec![8, 0, 7, 2, 4],
219 not_elected: vec![3, 6]
220 }
221 );
222 assert_eq!(
223 std::str::from_utf8(&buf).unwrap(),
224 r"
225Election: Vegetable contest
226
227 package name
228 Rule: Simulated Plurality Block Voting
229 Arithmetic: fixed-point decimal arithmetic (9 places)
230 Seats: 5
231 Ballots: 45
232 Votes per ballot: 5
233
234Action: Count Complete
235Action: Elect: Jalapeno
236Action: Elect: Apple
237Action: Elect: Hazelnut
238Action: Elect: Cherry
239Action: Elect: Eggplant
240Action: Defeat: Date
241Action: Defeat: Grape
242Action: Count Complete
243 Elected: Jalapeno (38.500000000)
244 Elected: Apple (38.000000000)
245 Elected: Hazelnut (33.500000000)
246 Elected: Cherry (32.500000000)
247 Elected: Eggplant (28.000000000)
248 Defeated: Grape (28.000000000)
249 Defeated: Date (26.500000000)
250"
251 );
252 }
253
254 #[test]
255 fn test_plurality_block_voting_1() {
256 let election = make_election();
257
258 let mut buf = Vec::new();
259 let result = plurality_block_voting::<Integer64, FixedDecimal9>(
260 &mut buf,
261 &election,
262 "package name",
263 Some(1),
264 )
265 .unwrap();
266 assert_eq!(
267 result,
268 ElectionResult {
269 elected: vec![6, 8, 7, 4, 0],
270 not_elected: vec![3, 2]
271 }
272 );
273 assert_eq!(
274 std::str::from_utf8(&buf).unwrap(),
275 r"
276Election: Vegetable contest
277
278 package name
279 Rule: Simulated Plurality Block Voting
280 Arithmetic: fixed-point decimal arithmetic (9 places)
281 Seats: 5
282 Ballots: 45
283 Votes per ballot: 1
284
285Action: Count Complete
286Action: Elect: Grape
287Action: Elect: Jalapeno
288Action: Elect: Hazelnut
289Action: Elect: Eggplant
290Action: Elect: Apple
291Action: Defeat: Date
292Action: Defeat: Cherry
293Action: Count Complete
294 Elected: Grape (9.500000000)
295 Elected: Jalapeno (8.500000000)
296 Elected: Hazelnut (7.500000000)
297 Elected: Eggplant (7.000000000)
298 Elected: Apple (5.500000000)
299 Defeated: Cherry (3.500000000)
300 Defeated: Date (3.500000000)
301"
302 );
303 }
304
305 #[test]
306 fn test_plurality_block_voting_2() {
307 let election = make_election();
308
309 let mut buf = Vec::new();
310 let result = plurality_block_voting::<Integer64, FixedDecimal9>(
311 &mut buf,
312 &election,
313 "package name",
314 Some(2),
315 )
316 .unwrap();
317 assert_eq!(
318 result,
319 ElectionResult {
320 elected: vec![8, 7, 6, 0, 4],
321 not_elected: vec![2, 3]
322 }
323 );
324 assert_eq!(
325 std::str::from_utf8(&buf).unwrap(),
326 r"
327Election: Vegetable contest
328
329 package name
330 Rule: Simulated Plurality Block Voting
331 Arithmetic: fixed-point decimal arithmetic (9 places)
332 Seats: 5
333 Ballots: 45
334 Votes per ballot: 2
335
336Action: Count Complete
337Action: Elect: Jalapeno
338Action: Elect: Hazelnut
339Action: Elect: Grape
340Action: Elect: Apple
341Action: Elect: Eggplant
342Action: Defeat: Cherry
343Action: Defeat: Date
344Action: Count Complete
345 Elected: Jalapeno (20.666666663)
346 Elected: Hazelnut (18.666666663)
347 Elected: Grape (14.666666665)
348 Elected: Apple (11.999999998)
349 Elected: Eggplant (10.333333333)
350 Defeated: Date (8.333333333)
351 Defeated: Cherry (5.333333333)
352"
353 );
354 }
355
356 #[test]
357 fn test_plurality_block_voting_3() {
358 let election = make_election();
359
360 let mut buf = Vec::new();
361 let result = plurality_block_voting::<Integer64, FixedDecimal9>(
362 &mut buf,
363 &election,
364 "package name",
365 Some(3),
366 )
367 .unwrap();
368 assert_eq!(
369 result,
370 ElectionResult {
371 elected: vec![8, 7, 0, 6, 2],
372 not_elected: vec![4, 3]
373 }
374 );
375 assert_eq!(
376 std::str::from_utf8(&buf).unwrap(),
377 r"
378Election: Vegetable contest
379
380 package name
381 Rule: Simulated Plurality Block Voting
382 Arithmetic: fixed-point decimal arithmetic (9 places)
383 Seats: 5
384 Ballots: 45
385 Votes per ballot: 3
386
387Action: Count Complete
388Action: Elect: Jalapeno
389Action: Elect: Hazelnut
390Action: Elect: Apple
391Action: Elect: Grape
392Action: Elect: Cherry
393Action: Defeat: Eggplant
394Action: Defeat: Date
395Action: Count Complete
396 Elected: Jalapeno (27.833333326)
397 Elected: Hazelnut (24.333333326)
398 Elected: Apple (21.499999996)
399 Elected: Grape (19.833333330)
400 Elected: Cherry (14.166666666)
401 Defeated: Date (14.166666666)
402 Defeated: Eggplant (13.166666666)
403"
404 );
405 }
406
407 #[test]
408 fn test_plurality_block_voting_4() {
409 let election = make_election();
410
411 let mut buf = Vec::new();
412 let result = plurality_block_voting::<Integer64, FixedDecimal9>(
413 &mut buf,
414 &election,
415 "package name",
416 Some(4),
417 )
418 .unwrap();
419 assert_eq!(
420 result,
421 ElectionResult {
422 elected: vec![8, 0, 7, 6, 2],
423 not_elected: vec![4, 3]
424 }
425 );
426 assert_eq!(
427 std::str::from_utf8(&buf).unwrap(),
428 r"
429Election: Vegetable contest
430
431 package name
432 Rule: Simulated Plurality Block Voting
433 Arithmetic: fixed-point decimal arithmetic (9 places)
434 Seats: 5
435 Ballots: 45
436 Votes per ballot: 4
437
438Action: Count Complete
439Action: Elect: Jalapeno
440Action: Elect: Apple
441Action: Elect: Hazelnut
442Action: Elect: Grape
443Action: Elect: Cherry
444Action: Defeat: Eggplant
445Action: Defeat: Date
446Action: Count Complete
447 Elected: Jalapeno (35.000000000)
448 Elected: Apple (31.000000000)
449 Elected: Hazelnut (31.000000000)
450 Elected: Grape (26.000000000)
451 Elected: Cherry (23.000000000)
452 Defeated: Date (19.000000000)
453 Defeated: Eggplant (15.000000000)
454"
455 );
456 }
457
458 #[test]
459 fn test_plurality_block_voting_6() {
460 let election = make_election();
461
462 let mut buf = Vec::new();
463 let result = plurality_block_voting::<Integer64, FixedDecimal9>(
464 &mut buf,
465 &election,
466 "package name",
467 Some(6),
468 )
469 .unwrap();
470 assert_eq!(
471 result,
472 ElectionResult {
473 elected: vec![8, 0, 2, 7, 3],
474 not_elected: vec![6, 4]
475 }
476 );
477 assert_eq!(
478 std::str::from_utf8(&buf).unwrap(),
479 r"
480Election: Vegetable contest
481
482 package name
483 Rule: Simulated Plurality Block Voting
484 Arithmetic: fixed-point decimal arithmetic (9 places)
485 Seats: 5
486 Ballots: 45
487 Votes per ballot: 6
488
489Action: Count Complete
490Action: Elect: Jalapeno
491Action: Elect: Apple
492Action: Elect: Cherry
493Action: Elect: Hazelnut
494Action: Elect: Date
495Action: Defeat: Grape
496Action: Defeat: Eggplant
497Action: Count Complete
498 Elected: Jalapeno (42.000000000)
499 Elected: Apple (40.000000000)
500 Elected: Cherry (36.000000000)
501 Elected: Hazelnut (35.000000000)
502 Elected: Date (34.000000000)
503 Defeated: Eggplant (32.000000000)
504 Defeated: Grape (28.000000000)
505"
506 );
507 }
508}