File Coverage

blib/lib/Vote/Count/Method/CondorcetVsIRV.pm
Criterion Covered Total %
statement 176 181 97.2
branch 45 50 90.0
condition 4 9 44.4
subroutine 25 25 100.0
pod 2 8 25.0
total 252 273 92.3


line stmt bran cond sub pod time code
1 2     2   1052 use strict;
  2         5  
  2         60  
2 2     2   12 use warnings;
  2         4  
  2         50  
3 2     2   32 use 5.024;
  2         8  
4 2     2   12 use feature qw /postderef signatures/;
  2         5  
  2         175  
5              
6             use namespace::autoclean;
7 2     2   12 use Moose;
  2         5  
  2         17  
8 2     2   256  
  2         3  
  2         15  
9             with 'Vote::Count::Log';
10              
11             use Storable 3.15 'dclone';
12 2     2   15162 use Vote::Count::ReadBallots qw/read_ballots write_ballots/;
  2         44  
  2         116  
13 2     2   16 use Vote::Count::Redact qw/RedactSingle RedactPair RedactBullet/;
  2         6  
  2         115  
14 2     2   1024 use Vote::Count::Method::CondorcetIRV;
  2         5  
  2         131  
15 2     2   803 use Try::Tiny;
  2         6  
  2         81  
16 2     2   16 use Data::Dumper;
  2         4  
  2         114  
17 2     2   14  
  2         4  
  2         155  
18             our $VERSION='2.02';
19              
20             # no warnings 'uninitialized';
21             no warnings qw/experimental/;
22 2     2   13  
  2         4  
  2         3708  
23             =head1 NAME
24              
25             Vote::Count::Method::CondorcetVsIRV
26              
27             =head1 VERSION 2.02
28              
29             =cut
30              
31             # ABSTRACT: Condorcet versus IRV
32              
33             =pod
34              
35             =head1 SYNOPSIS
36              
37             use Vote::Count::Method::CondorcetVsIRV;
38              
39             my $Election = Vote::Count::Method::CondorcetVsIRV->new( ... );
40             my $result = $Election->CondorcetVsIRV();
41             or
42             my $Election = Vote::Count->new( TieBreakMethod => 'approval' );
43             my $result = $Election->CondorcetVsIRV( relaxed => 1 );
44             equivalent to default:
45             my $result = $Election->CondorcetVsIRV( relaxed => 0, smithsetirv => 0 );
46              
47             say $result->{'winner'};
48              
49             $Election->WriteAllLogs();
50              
51             Returns a HashRef with a key for winner.
52              
53             =head1 Method Common Name: Condorcet vs IRV
54              
55             Condorcet vs IRV Methods determine if the Condorcet Winner needed votes from the IRV winner; electing the Condorcet Winner if there was not a later harm violation, electing the IRV winner if there was. If there is no Condorcet Winner the IRV Winner is chosen.
56              
57             To determine if there was a violation the ballots of one or more choices are redacted, with later choice on those ballots removed.
58              
59             With these methods it is also possible to allow a tolerance for Later Harm.
60              
61             =head3 Double Redaction
62              
63             The Double Redaction method (default) measures the later harm effect between a Condorcet Winner and the IRV Winner.
64              
65             Considering the Margin of the Condorcet Winner over the IRV Winner and the number of votes needed by the Condorcet Winner from the IRV winner as measures of Preference for the Condorcet Winner and of Later Harm, it is also possible to establish a Later Harm Tolerance Threshold.
66              
67             The Relaxed Later Harm option will select the Condorcet Winner when their margin of victory over the IRV Winner is greater than the number of later votes they need from the IRV Winner to be a Condorcet Winner. Although not presently implemented a different ratio or percentage could be used.
68              
69             Because in most cases where the IRV and Condorcet winners are different there are Later Harm effects, without relaxed this method will almost always confirm the IRV winner.
70              
71             =head3 Simple (Single Redaction)
72              
73             This variation only redacts the ballots that choose the IRV Winner as their first choice. This gives the voters confidence that if their first choice wins by the later harm safe method, that their vote will not be used against that choice.
74              
75             The simplest form is:
76              
77             1. Determine the IRV Winner
78              
79             2. Treating the ballots cast with the IRV Winner as their first choice as ballots cast only for the IRV Winner, determine the Condorcet Winner.
80              
81             3. Elect the Condorcet Winner, if there is none, elect the IRV Winner.
82              
83             Unfortunately, this simplest form, in cases where more than one choice defeats the IRV Winner in pairing and later choices of the IRV Winner's ballots determine which becomes the Condorcet Winner, removes the supporters of the IRV Winner from the final decision.
84              
85             The form implemented by Vote::Count is:
86              
87             =over
88              
89             1. Determine both the IRV and Condorcet Winner. If they are the same, elect that choice. If there is no Condorcet Winner, elect the IRV Winner.
90              
91             2. Treating the ballots cast with the IRV Winner as their first choice as ballots cast for only the IRV Winner determine the Condorcet Winner.
92              
93             3. If there is a Condorcet Winner, elect the first Condorcet Winner, if there is none, elect the IRV Winner. (The redaction cannot make the IRV Winner a Condorcet Winner if it isn't already one).
94              
95             =back
96              
97             =cut
98              
99             =head1 Criteria
100              
101             The double redaction version is later harm safe if the relaxed option is not used. The simple version later harm protects first choice votes only, it also does not protect the first Condorcet Winner's votes at all.
102              
103             =head2 Simplicity
104              
105             The simple version does not require Condorcet Loop resolution, and thus can be considered to be on par with Benham for complexity, and like Benham is Hand Countable. The double redaction version is more complex, but is perhaps more valuable as an approach for measuring later harm.
106              
107             =head2 Later Harm
108              
109             This method meets Later Harm with the default strict option.
110              
111             The relaxed option allows a finite Later Harm effect.
112              
113             Using the TCA Floor Rule and or Smith Set IRV add small Later Harm effects.
114              
115             =head2 Condorcet Criteria
116              
117             This method only meets Condorcet Loser, when the IRV winner is chosen instead of the Condorcet Winner, the winner may be outside the Smith Set.
118              
119             =head2 Consistency
120              
121             Because this method chooses between the outcomes of two different methods, it is subject to the consistency failings of both. Given that Cloning is an important consistency issue in real elections, the clone handling should be an improvement over IRV.
122              
123             =head1 Implementation
124              
125             Details specific to this implementation.
126              
127             The Tie Breaker is defaulted to (modified) Grand Junction for resolvability. Any Tie Breaker supported by Vote::Count::TieBreaker may be used, 'all' and 'none' are not recommended.
128              
129             =head2 Function Name: CondorcetVsIRV
130              
131             Runs the election, returns a hashref containing the winner, similar to how other Vote::Count Methods such as RunIRV behave.
132              
133             =head3 Arguments for CondorcetVsIRV()
134              
135             =over
136              
137             =item* relaxed
138              
139             =item* simple
140              
141             =item* smithsetirv
142              
143             =back
144              
145             =head2 LogTo, LogPath, LogBaseName, LogRedactedTo
146              
147             The first three behave as normal Vote::Count::Log methods, except that the default is /tmp/condorcetvsirv.
148              
149             LogRedactedTo defaults to appending _redacted into the log names for the redacted election, it can be overridden by setting a value (which should be /path/basename) like LogTo.
150              
151             =head2 WriteLog WriteAllLogs
152              
153             WriteLog behaves normally, there is a log set for the CondorcetVSIRV object as well as child logs for the Election and RedactedElection, each of which has a set of logs for PairMatrix as well. WriteAllLogs will write all of these logs.
154              
155             =cut
156              
157             # LogTo over-writes role LogTo changing default filename.
158             has 'LogTo' => (
159             is => 'rw',
160             isa => 'Str',
161             default => '/tmp/condorcetvsirv',
162             );
163              
164             has 'LogRedactedTo' => (
165             is => 'lazy',
166             is => 'rw',
167             isa => 'Str',
168             builder => '_setredactedlog',
169             );
170              
171             # There is a bug with LogTo being uninitialized despite having a default
172 22     22   63254 my $logto
  22         64  
  22         40  
173             = defined $self->LogTo()
174 22 100       751 ? $self->LogTo() . '_redacted'
175             : '/tmp/condorcetvsirv_redacted';
176             return $logto;
177             }
178 22         83  
179             has 'TieBreakMethod' => (
180             is => 'ro',
181             isa => 'Str',
182             default => 'grandjunction',
183             );
184              
185             has 'BallotSet' => ( is => 'ro', isa => 'HashRef', required => 1 );
186              
187             has 'Active' => (
188             is => 'rw',
189             isa => 'HashRef',
190             lazy => 1,
191             builder => '_InitialActive',
192             );
193              
194              
195             $I->{'Active'} = dclone $active;
196 22     22   53 $I->{'Election'}->SetActive($active);
  22         38  
  22         38  
  22         680  
197             if ( defined $I->{'RedactedElection'} ) {
198 4     4 0 11 $I->{'RedactedElection'}->SetActive($active);
  4         9  
  4         10  
  4         8  
199 4         130 }
200 4         44 }
201 4 100       21  
202 2         9 my $new = dclone $self->BallotSet()->{'choices'};
203             $self->SetActive($new);
204             return $new;
205             }
206 1     1 0 5  
  1         2  
  1         2  
207 1         42 # sub ResetActive ( $self ) { return dclone $self->BallotSet()->{'choices'} }
208 1         6  
209 1         3 my $WonIRV = undef;
210             my $irvresult = undef;
211             if ($smithsetirv) {
212             $irvresult = $I->SmithSetIRV( $I->TieBreakMethod() );
213             }
214 19     19   42 else {
  19         38  
  19         39  
  19         43  
  19         38  
215 19         38 $irvresult = $I->RunIRV( $active, $I->TieBreakMethod() );
216 19         46 }
217 19 100       60 $I->logd( 'IRV Result: ' . Dumper $irvresult );
218 2         64 return $irvresult->{'winner'} if $irvresult->{'winner'};
219             $I->logt("IRV ended with a Tie.");
220             $I->logt(
221 17         539 "Active (Tied) Choices are: " . join( ', ', $irvresult->{'tied'} ) );
222             $I->SetActiveFromArrayRef( $irvresult->{'tied'} );
223 19         150 return 0;
224 19 100       157 }
225 1         6  
226             my $self = shift;
227 1         11 $self->{'Election'} = Vote::Count::Method::CondorcetIRV->new(
228 1         23 BallotSet => $self->BallotSet(),
229 1         5 TieBreakMethod => $self->TieBreakMethod(),
230             Active => $self->Active(),
231             LogTo => $self->{'LogTo'} . '_unredacted',
232             );
233 22     22 0 47585 $self->{'RedactedElection'} = undef,;
234             }
235              
236             $I->WriteLog();
237             $I->Election()->WriteLog();
238 22         846 $I->RedactedElection()->WriteLog();
239             $I->Election()->PairMatrix()->WriteLog();
240 22         4082 $I->RedactedElection()->PairMatrix()->WriteLog();
241             }
242              
243 1     1 1 3  
  1         3  
  1         1  
244 1         6 return $self->{'RedactedElection'};
245 1         528 }
246 1         439  
247 1         418 my $ballotset = $simpleflag
248 1         422 ? RedactBullet ( $self->BallotSet(), $WonIRV )
249             : RedactPair( $self->BallotSet(), $WonCondorcet, $WonIRV );
250             $self->{'RedactedElection'} = Vote::Count->new(
251 35     35 0 79 BallotSet => $ballotset,
  35         70  
  35         56  
  35         158  
252             TieBreakMethod => $self->TieBreakMethod(),
253 15     15 0 30 Active => $self->Active(),
  15         28  
  15         33  
  15         27  
  15         26  
254 15         168 LogTo => $self->LogRedactedTo(),
255             );
256             $self->logd(
257 11     11 0 35 'Created Redacted Election.',
  11         23  
  11         29  
  11         20  
  11         30  
  11         21  
258 11 100       462 $self->{'RedactedElection'}->PairMatrix()->PairingVotesTable(),
259             $self->{'RedactedElection'}->PairMatrix()->MatrixTable(),
260             );
261 11         511 }
262              
263             my $smithsetirv
264             = $options->{'smithsetirv'} ? 1 : 0;
265             my $relaxed = $options->{'relaxed'} ? $options->{'relaxed'} : 0;
266             my $simpleflag = $options->{'simple'} ? 1 : 0;
267             my $E = $I->Election();
268             my $R = $I->RedactedElection();
269             my $ConfirmC = $R->PairMatrix->CondorcetWinner();
270 11         312 my $ConfirmI = _CVI_IRV( $R, $active, $smithsetirv );
271             if ( $ConfirmC ) {
272             if ( $simpleflag ) {
273             $I->logt("Elected $WonCondorcet, Redacted Ballots had a Condorcet Winner.");
274 10     10   25 $I->logv("The Redacted Condorcet Winner was $ConfirmC.")
  10         23  
  10         24  
  10         23  
  10         21  
  10         21  
  10         17  
275             if ( $ConfirmC ne $WonCondorcet);
276 10 50       47 return $WonCondorcet ;
277 10 100       39 } elsif ( $ConfirmC eq $WonCondorcet or $ConfirmC eq $WonIRV ) {
278 10 100       42 $I->logt("Elected $ConfirmC, Redacted Ballots Condorcet Winner.");
279 10         42 return $ConfirmC;
280 10         34 }
281 10         297 } else {
282 10         62 $ConfirmI = _CVI_IRV( $R, $active, $smithsetirv );
283 10 100       40 if ( $ConfirmI eq $WonCondorcet or $ConfirmI eq $WonIRV ) {
284 9 100 66     76 $I->logt("Elected $ConfirmI, Redacted Ballots IRV Winner.");
    100          
285 3         19 return $ConfirmI;
286 3 100       21 }
287             }
288 3         11 $ConfirmC = 'NONE' unless $ConfirmC;
289             $I->logt("Neither $WonCondorcet nor $WonIRV were confirmed.");
290 3         21 $I->logt(
291 3         14 "Redacted Ballots Winners: Condorcet = $ConfirmC, IRV = $ConfirmI");
292             if ($relaxed) {
293             my $GreatestLoss = $R->PairMatrix()->GreatestLoss($WonCondorcet);
294 1         4 my $Margin = $E->PairMatrix()->GetPairResult( $WonCondorcet, $WonIRV )
295 1 50 33     25 ->{'margin'};
296 0         0 $I->logt(
297 0         0 "The margin of the Condorcet over the IRV winner was: $Margin");
298             $I->logt(
299             "$WonCondorcet\'s greatest loss with redacted ballots was $GreatestLoss."
300 4 100       17 );
301 4         32 if ( $Margin > $GreatestLoss ) {
302 4         25 $I->logt("Elected: $WonCondorcet");
303             return $WonCondorcet;
304 4 100       37 }
305 2         58 }
306             $I->logt("Elected: $WonIRV");
307 2         58 return $WonIRV;
308 2         13 }
309              
310 2         14 my $E = $self->Election();
311             my $smithsetirv = defined $args{'smithsetirv'} ? $args{'smithsetirv'} : 0;
312             my $simpleflag = defined $args{'simple'} ? $args{'simple'} : 0;
313 2 100       8 my $active = $self->Active();
314 1         5 # check for majority winner.
315 1         5 my $majority = $E->EvaluateTopCountMajority()->{'winner'};
316             return $majority if $majority;
317             my $WonIRV = undef;
318 3         25 my $WonCondorcet = $E->PairMatrix()->CondorcetWinner();
319 3         13 if ($WonCondorcet) {
320             $self->logt("Condorcet Winner is $WonCondorcet");
321             # Even if SmithSetIRV requested, it would return the condorcet winner
322 21     21 1 3026 # We need to know if a different choice would win IRV.
  21         42  
  21         77  
  21         45  
323 21         74 $WonIRV = $E->RunIRV( $active, $E->TieBreakMethod() )->{'winner'};
324 21 100       85 }
325 21 100       71 else {
326 21         776 $self->logt("No Condorcet Winner");
327             $WonIRV = _CVI_IRV( $E, $active, $smithsetirv );
328 21         118 if ($WonIRV) {
329 21 50       85 $self->logt("Electing IRV Winner $WonIRV");
330 21         50 return { 'winner' => $WonIRV };
331 21         687 }
332 21 100       78 else { ;
333 13         88 $self->logt("There is no Condorcet or IRV winner.");
334             return { 'winner' => 0 };
335             }
336 13         410 }
337              
338             # IRV private already logged tie, now return the false value.
339 8         52 # Edge case IRV tie with Condorcet Winner, I guess CW wins?
340 8         40 unless ($WonIRV) {
341 8 100       36 if ($WonCondorcet) {
342 7         39 $self->logt("Electing Condorcet Winner $WonCondorcet, IRV tied.");
343 7         84 return { 'winner' => $WonCondorcet};
344             }
345             return { 'winner' => 0 };
346 1         4 }
347 1         18 if ( $WonIRV eq $WonCondorcet ) {
348             $self->logt("Electing $WonIRV the winner by both Condorcet and IRV.");
349             return { 'winner' => $WonIRV };
350             }
351             if ( $WonIRV and !$WonCondorcet ) {
352             $self->logt(
353 13 100       59 "Electing IRV Winner $WonIRV. There was no Condorcet Winner.");
354 1 50       5 return { 'winner' => $WonIRV };
355 1         7 }
356 1         15 $self->CreateRedactedElection( $WonCondorcet, $WonIRV, $simpleflag );
357             my $winner
358 0         0 = $self->_CVI_RedactRun( $WonCondorcet, $WonIRV, $active, \%args );
359             return { 'winner' => $winner };
360 12 100       44  
361 2         14 }
362 2         17  
363             1;
364 10 50 33     67  
365 0         0 #FOOTER
366              
367 0         0 =pod
368              
369 10         63 BUG TRACKER
370 10         206  
371             L<https://github.com/brainbuz/Vote-Count/issues>
372 10         69  
373             AUTHOR
374              
375             John Karr (BRAINBUZ) brainbuz@cpan.org
376              
377             CONTRIBUTORS
378              
379             Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org.
380              
381             LICENSE
382              
383             This module is released under the GNU Public License Version 3. See license file for details. For more information on this license visit L<http://fsf.org>.
384              
385             SUPPORT
386              
387             This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author.
388              
389             =cut
390