4 # Author : Manoj Srivastava ( srivasta@glaurung.green-gryphon.com )
5 # Created On : Thu Oct 16 12:08:43 2003
6 # Created On Node : glaurung.green-gryphon.com
7 # Last Modified By : Manoj Srivastava
8 # Last Modified On : Sat Mar 10 09:42:54 2007
9 # Last Machine Used: glaurung.internal.golden-gryphon.com
11 # Status : Unknown, Use with caution!
14 # arch-tag: 1a48504a-0668-4790-aa72-d4359a3c41e2
16 # This program is free software; you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation; either version 2 of the License, or
19 # (at your option) any later version.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 use Carp qw(carp croak);
37 use Params::Validate qw(validate_with :types);
39 #use Math::BigInt ':constant';
44 pocket_devotee - Given a tally sheet, calculate the Condorcet winner
50 pocket_devotee --option 'A:foo' --option 'B:bar' < tally_sheet
51 pocket_devotee --num-options 3 < tally_sheet
57 Produce the results, taking into consideration the tally, quorum
58 requirements, and the per option majority requirements, if any.
59 This routine is the heart of the voting system. It takes into account
60 quorum requirements (reading the output file produced by dvt-quorum),
61 and also the configured majority requirements, if any.
63 It reads the tally sheet produced by dvt-tally, and creates the
64 initial beat matrix; and the pairwise defeat list, and finally the
65 schwartz set. If there are defeats between the members of the schwartz
66 set, it drops the weakest defeat and repeats, until there is a winner.
68 It puts the results in the configured output file.
72 =head2 Internal Implementation
76 This is the workhorse routine.
77 This routine pays attention to the configuration variables
78 Quorum_File, Tally_File, Max_Choices, and Results.
85 our %option_to_number;
86 our %number_to_option;
90 @number_to_option{((0..9),(10..35))} = (('0'..'9'),('A'..'Z'));
91 %option_to_number = reverse %number_to_option;
101 'default_option|default-option!',
103 if (not exists $options{option} or not ref($options{option}) or
104 @{$options{option}} < 2
106 die "you must give at least two options in a --option argument";
109 for my $option (@{$options{option}}) {
110 my ($key,$name,$majority) = split /:/,$option;
112 push @options,{key => $key,
114 majority => $majority,
117 winner(options=>\@options,
118 default_option => exists $options{default_option} ? $options{default_option} : 1,
123 return map {$option_to_number{$_}} @_;
127 return map {$number_to_option{$_}} @_;
131 my %params = validate_with(params => \@_,
132 spec => {quorum => {default => 5,
135 options => {type => ARRAYREF,
137 tally_fh => {type => HANDLE,
140 default_option => {default => 1,
145 # options is an array to keep it ordered
146 my @options = @{$params{options}};
148 my %order_to_options;
151 my $valid_options = '';
152 for my $option (@options) {
153 $options{$option->{key}} =
154 {key => $option->{key},
155 name => $option->{name},
156 majority => $option->{majority} // 1,
159 $order_to_options{$order} = $options{$option->{key}};
160 $valid_options .= $option->{key};
161 $max_choices = $order;
165 # The constitution defines the maximum value of K to be 5
166 my $K = 2; # Math::BigFloat->new($params{quorum});
168 # 1. Given two options A and B, V(A,B) is the number of voters who
169 # prefer option A over option B.
170 my @Beat_Matrix = ();
171 for my $row (0..$max_choices) {
172 for my $col (0..$max_choices) {
173 $Beat_Matrix[$row][$col] = 0; #Math::BigFloat->bzero();
177 my $RESULTS = \*STDOUT;
179 my $now_string = gmtime;
180 print {$RESULTS} "Starting results calculation at $now_string\n\n";
182 if (not defined $params{tally_fh}) {
183 $params{tally_fh} = \*STDIN;
186 # This is where we get our input data from
187 while (defined ($_ = $params{tally_fh}->getline)) {
191 if (m/^V:\s+(\S+)\s+(\S)/) { # standard devotee syntax
194 } elsif (m/^(.+) # the voter, can have spaces
195 \s*:\s+ # needs a colon and a space
196 ((?:[$valid_options] # the vote
197 \s*(?:,|=|>)?\s*)*) # allow > and , or =
198 \s*$/x # useless trailing spaces
202 # now, because this format has the options ranked instead of
203 # the rank of the option, figure out the rank of the option,
204 # and give that to devotee
205 my $current_rank = 1;
207 while ($vote =~ /([$valid_options]) # the vote
208 \s*((?:,|=|>)?)\s*/xg) {
209 my ($option,$relationship) = ($1,$2);
210 $option_rank{$option} = $current_rank;
211 if ($relationship ne '=') {
216 for my $opt (@options) {
217 $vote .= exists $option_rank{$opt->{key}} ? $option_rank{$opt->{key}} : '-';
219 print {$RESULTS} "V: $vote $voter\n";
222 print STDERR "ignoring line '$_'; this is probably wrong!\n";
226 # my @rank = unpack "a" x $max_choices, $vote;
228 foreach my $rank (split //, uc $vote) {
233 push(@rank,encode_base64($rank));
236 foreach my $i (0..($max_choices - 1)) {
237 foreach my $j (($i + 1)..($max_choices - 1)) {
238 if ($rank[$i] eq '-' && $rank[$j] eq '-') {
239 next; # Both unranked
240 } elsif ($rank[$i] eq '-' && $rank[$j] ne '-') {
241 $Beat_Matrix[$j][$i]++;
242 } elsif ($rank[$i] ne '-' && $rank[$j] eq '-') {
243 $Beat_Matrix[$i][$j]++;
244 } elsif ($rank[$i] < $rank[$j]) {
245 $Beat_Matrix[$i][$j]++;
246 } elsif ($rank[$i] > $rank[$j]) {
247 $Beat_Matrix[$j][$i]++;
249 next; # Equally ranked
254 for my $opt (0..($max_choices - 1)) {
255 print {$RESULTS} "Option ", $order_to_options{$opt+1}{key}, " \"",
256 $order_to_options{$opt+1}{name},
259 print {$RESULTS} <<EOF;
261 In the following table, tally[row x][col y] represents the votes that
262 option x received over option y.
267 print {$RESULTS} " " x18, "Option\n";
268 print {$RESULTS} " " x10;
269 for my $col (0..($max_choices - 1)) {
270 printf {$RESULTS} " % 4s ", $order_to_options{$col + 1}{key};
273 print {$RESULTS} "\n";
274 print {$RESULTS} " " x10, " === " x $max_choices, "\n";
276 for my $row (0..($max_choices - 1)) {
277 printf {$RESULTS} "Option %s ", $order_to_options{$row + 1}{key};
278 for my $col (0..($max_choices - 1)) {
280 printf {$RESULTS} " ", $Beat_Matrix[$row][$col];
282 printf {$RESULTS} " % 4d ", $Beat_Matrix[$row][$col];
285 print {$RESULTS} "\n";
287 print {$RESULTS} "\n\n";
289 print {$RESULTS} <<EOM;
291 Looking at row 2, column 1, $order_to_options{2}{key}
292 received $Beat_Matrix[1][0] votes over $order_to_options{1}{key}
294 Looking at row 1, column 2, $order_to_options{1}{key}
295 received $Beat_Matrix[0][1] votes over $order_to_options{2}{key}.
302 if ($params{default_option}) {
303 foreach my $i (0..($max_choices - 2)) {
304 if ($K > $Beat_Matrix[$i][$max_choices - 1]) {
306 print {$RESULTS} "Dropping Option", $order_to_options{$i + 1}{key},
307 " \"", $order_to_options{$i + 1}{name},
308 "\" because of Quorum\n";
310 print {$RESULTS} "Option ", $order_to_options{$i + 1}{key},
311 " Reached quorum: $Beat_Matrix[$i][$max_choices - 1] > $K\n";
314 print {$RESULTS} "\n\n";
321 # 2. An option A defeats the default option D by a majority ratio N,
322 # if V(A,D) is strictly greater than N * V(D,A).
323 # 3. If a supermajority of S:1 is required for A, its majority ratio
324 # is S; otherwise, its majority ratio is 1.
325 # Any (non-default) option which does not defeat the default option
326 # by its required majority ratio is dropped from consideration.
327 foreach my $i (0..($max_choices - 2)) {
328 next unless $Beat_Matrix[$max_choices - 1][$i];
331 my $ratio = 1.0 * $Beat_Matrix[$i][$max_choices - 1] /
332 $Beat_Matrix[$max_choices - 1][$i];
333 $Ratio{$i} = sprintf("%.2f", $ratio);
334 if ($ratio < $order_to_options{$i + 1}{majority}) {
335 # If the next line is commented out, we get a more verbose set of results
337 print {$RESULTS} "Dropping Option ", $order_to_options{$i + 1}{key},
338 " because of Majority. ($ratio)";
339 printf {$RESULTS} " %6.3f (%d/%d) < %d\n",
340 $ratio, $Beat_Matrix[$i][$max_choices - 1],
341 $Beat_Matrix[$max_choices - 1][$i],
342 $order_to_options{$i + 1}{majority};
344 print {$RESULTS} "Option ", $order_to_options{$i + 1}{key}, " passes Majority.";
345 print {$RESULTS} " ";
346 printf {$RESULTS} " %6.3f (%d/%d) >= %d\n",
347 $ratio, $Beat_Matrix[$i][$max_choices - 1],
348 $Beat_Matrix[$max_choices - 1][$i],
349 $order_to_options{$i + 1}{majority};
352 print {$RESULTS} "\n\n";
356 # Initialize the Defeats matrix
357 foreach my $i (0..($max_choices - 1)) {
359 foreach my $j (($i + 1)..($max_choices - 1)) {
361 if ($Beat_Matrix[$i][$j] > $Beat_Matrix[$j][$i]) {
363 $Defeats{$i}{$j}{for} = $Beat_Matrix[$i][$j];
364 $Defeats{$i}{$j}{against} = $Beat_Matrix[$j][$i];
366 print {$RESULTS} " Option ", $order_to_options{$i + 1}{key}, " defeats Option ",
367 $order_to_options{$j + 1}{key},
368 sprintf(" by (% 4d - % 4d) = %4d votes.\n",
369 $Beat_Matrix[$i][$j], $Beat_Matrix[$j][$i],
370 $Beat_Matrix[$i][$j] - $Beat_Matrix[$j][$i]);
371 } elsif ($Beat_Matrix[$i][$j] < $Beat_Matrix[$j][$i]) {
373 $Defeats{$j}{$i}{for} = $Beat_Matrix[$j][$i];
374 $Defeats{$j}{$i}{against} = $Beat_Matrix[$i][$j];
376 print {$RESULTS} " Option ", $order_to_options{$j + 1}{key}, " defeats Option ",
377 $order_to_options{$i + 1}{key},
378 sprintf(" by (% 4d - % 4d) = %4d votes.\n",
379 $Beat_Matrix[$j][$i], $Beat_Matrix[$i][$j],
380 $Beat_Matrix[$j][$i] - $Beat_Matrix[$i][$j]);
384 print {$RESULTS} "\n\n";
387 # Ok, here is what we are here for.
390 # From the list of [undropped] pairwise defeats, we generate a set of
391 # transitive defeats.
393 # Initialize the Beatpath
395 foreach my $i (0..($max_choices - 1)) {
397 foreach my $j (0..($max_choices - 1)) {
399 $BeatPath{$i}{$j}{for} = $Defeats{$i}{$j}{for};
400 $BeatPath{$i}{$j}{against} = $Defeats{$i}{$j}{against};
404 # 1. An option A transitively defeats an option C if A defeats C or
405 # if there is some other option B where A defeats B AND B
406 # transitively defeats C.
409 foreach my $i (0..($max_choices - 1)) {
411 foreach my $j (0..($max_choices - 1)) {
414 foreach my $k (0..($max_choices - 1)) {
418 if (!$BeatPath{$i}{$j}{for}) {
419 if ($BeatPath{$i}{$k}{for} && $BeatPath{$k}{$j}{for} ) {
420 if ($BeatPath{$i}{$k}{for} == $BeatPath{$k}{$j}{for}) {
421 $BeatPath{$i}{$j}{for} = ($BeatPath{$i}{$k}{against} >
422 $BeatPath{$k}{$j}{against}) ?
423 $BeatPath{$i}{$k}{for}:$BeatPath{$k}{$j}{for};
424 $BeatPath{$i}{$j}{against} = ($BeatPath{$i}{$k}{against} >
425 $BeatPath{$k}{$j}{against}) ?
426 $BeatPath{$i}{$k}{against} :
427 $BeatPath{$k}{$j}{against};
429 $BeatPath{$i}{$j}{for} = ($BeatPath{$i}{$k}{for} <
430 $BeatPath{$k}{$j}{for}) ?
431 $BeatPath{$i}{$k}{for} :
432 $BeatPath{$k}{$j}{for};
433 $BeatPath{$i}{$j}{against} = ($BeatPath{$i}{$k}{for} <
434 $BeatPath{$k}{$j}{for}) ?
435 $BeatPath{$i}{$k}{against} :
436 $BeatPath{$k}{$j}{against};
447 # We construct the Schwartz set from the set of transitive defeats.
448 foreach my $i (0..($max_choices - 1)) {
456 foreach my $i (0..($max_choices - 1)) {
457 foreach my $j (0..($max_choices - 1)) {
459 # An option A is in the Schwartz set if for all options B, either
460 # A transitively defeats B, or B does not transitively defeat A
461 # Here, we throw out any option $i that does not meet the above
463 if (! ($BeatPath{$i}{$j}{for} || ! $BeatPath{$j}{$i}{for})) {
468 print {$RESULTS} "The Schwartz Set contains:\n";
469 foreach my $i (0 ..$#Schwartz) {
470 next unless $Schwartz[$i];
471 print {$RESULTS} "\t Option ", $order_to_options{$i + 1}{key}, " \"",
472 $order_to_options{$i + 1}{name}, "\"\n";
474 print {$RESULTS} "\n\n";
476 # If there are defeats between options in the Schwartz set, we drop
477 # the weakest such defeats from the list of pairwise defeats, and
480 # 1. A defeat (A,X) is weaker than a defeat (B,Y) if V(A,X) is
481 # less than V(B,Y). Also, (A,X) is weaker than (B,Y) if V(A,X) is
482 # equal to V(B,Y) and V(X,A) is greater than V(Y,B).
484 # 2. A weakest defeat is a defeat that has no other defeat weaker
485 # than it. There may be more than one such defeat.
487 # Check to see if there is anything in the Schwartz set that has no
491 foreach my $i (0 ..$#Schwartz) {
492 next unless $Schwartz[$i];
493 foreach my $j (0..$#Schwartz) {
494 next unless $Schwartz[$j];
496 if (defined $Defeats{$i}{$j}{'for'}) {
498 $Weakest{$weak_count}{'for'} = $Defeats{$i}{$j}{for};
499 $Weakest{$weak_count}{'against'} = $Defeats{$i}{$j}{against};
500 $Weakest{$weak_count}{'Winner'} = $i;
501 $Weakest{$weak_count}{'Loser'} = $j;
503 } elsif ($Weakest{0}{'for'} > $Defeats{$i}{$j}{for}) {
506 $Weakest{$weak_count}{'for'} = $Defeats{$i}{$j}{for};
507 $Weakest{$weak_count}{'against'} = $Defeats{$i}{$j}{against};
508 $Weakest{$weak_count}{'Winner'} = $i;
509 $Weakest{$weak_count}{'Loser'} = $j;
511 } elsif ($Weakest{0}{'for'} == $Defeats{$i}{$j}{'for'}) {
512 if ($Weakest{0}{'against'} < $Defeats{$i}{$j}{against}) {
515 $Weakest{$weak_count}{'for'} = $Defeats{$i}{$j}{for};
516 $Weakest{$weak_count}{'against'} = $Defeats{$i}{$j}{against};
517 $Weakest{$weak_count}{'Winner'} = $i;
518 $Weakest{$weak_count}{'Loser'} = $j;
521 $Weakest{$weak_count}{'for'} = $Defeats{$i}{$j}{'for'};
522 $Weakest{$weak_count}{'against'} = $Defeats{$i}{$j}{'against'};
523 $Weakest{$weak_count}{'Winner'} = $i;
524 $Weakest{$weak_count}{'Loser'} = $j;
529 if (defined $Defeats{$j}{$i}{'for'}) {
531 $Weakest{$weak_count}{'for'} = $Defeats{$j}{$i}{'for'};
532 $Weakest{$weak_count}{'against'} = $Defeats{$j}{$i}{'against'};
533 $Weakest{$weak_count}{'Winner'} = $j;
534 $Weakest{$weak_count}{'Loser'} = $i;
536 } elsif ($Weakest{0}{'for'} > $Defeats{$j}{$i}{'for'}) {
539 $Weakest{$weak_count}{'for'} = $Defeats{$j}{$i}{'for'};
540 $Weakest{$weak_count}{'against'} = $Defeats{$j}{$i}{'against'};
541 $Weakest{$weak_count}{'Winner'} = $j;
542 $Weakest{$weak_count}{'Loser'} = $i;
544 } elsif ($Weakest{0}{'Low'} == $Defeats{$j}{$i}{'for'}) {
545 if ($Weakest{0}{'against'} < $Defeats{$j}{$i}{'against'}) {
548 $Weakest{$weak_count}{'for'} = $Defeats{$j}{$i}{'for'};
549 $Weakest{$weak_count}{'against'} = $Defeats{$j}{$i}{'against'};
550 $Weakest{$weak_count}{'Winner'} = $j;
551 $Weakest{$weak_count}{'Loser'} = $i;
554 $Weakest{$weak_count}{'for'} = $Defeats{$j}{$i}{'for'};
555 $Weakest{$weak_count}{'against'} = $Defeats{$j}{$i}{'against'};
556 $Weakest{$weak_count}{'Winner'} = $j;
557 $Weakest{$weak_count}{'Loser'} = $i;
565 print {$RESULTS} "\n", "-=" x 35, "\n";
566 print {$RESULTS} "-=" x 35, "\n\n";
567 print {$RESULTS} "The winners are:\n";
568 foreach my $i (0 ..$#Schwartz) {
569 next unless $Schwartz[$i];
570 print {$RESULTS} "\t Option ", $order_to_options{$i + 1}{key}, " \"",
571 $order_to_options{$i + 1}{name}, "\"\n";
573 print {$RESULTS} "\n", "-=" x 35, "\n";
574 print {$RESULTS} "-=" x 35, "\n\n";
577 print {$RESULTS} "Weakest Defeat(s): \n";
578 foreach my $k (sort keys %Weakest) {
579 print {$RESULTS} "\tOption ", $order_to_options{$Weakest{$k}{'Winner'} + 1}{key},
580 " beats Option ", $order_to_options{$Weakest{$k}{'Loser'} + 1}{key}, " by ",
581 " ($Beat_Matrix[$Weakest{$k}{'Winner'}][$Weakest{$k}{'Loser'}] - ",
582 "$Beat_Matrix[$Weakest{$k}{'Loser'}][$Weakest{$k}{'Winner'}])\t",
583 "= ", $Weakest{$k}{'for'} - $Weakest{$k}{'against'}, " votes\n";
585 print {$RESULTS} "Deleting weakest defeat(s)\n\n";
586 foreach my $k (sort keys %Weakest) {
587 delete $Defeats{$Weakest{$k}{'Winner'}}{$Weakest{$k}{'Loser'}};
602 This is very inchoate, at the moment, and needs testing.
614 Manoj Srivastava <srivasta@debian.org>
616 =head1 COPYRIGHT AND LICENSE
618 This script is a part of the Devotee package, and is
620 Copyright (c) 2002, 2003, 2004, 2005 Manoj Srivastava <srivasta@debian.org>
622 This program is free software; you can redistribute it and/or modify
623 it under the terms of the GNU General Public License as published by
624 the Free Software Foundation; either version 2 of the License, or
625 (at your option) any later version.
627 This program is distributed in the hope that it will be useful,
628 but WITHOUT ANY WARRANTY; without even the implied warranty of
629 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
630 GNU General Public License for more details.
632 You should have received a copy of the GNU General Public License
633 along with this program; if not, write to the Free Software
634 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA