File Coverage

blib/lib/Vote/Count/Method/STAR.pm
Criterion Covered Total %
statement 67 67 100.0
branch 10 10 100.0
condition n/a
subroutine 12 12 100.0
pod 1 1 100.0
total 90 90 100.0


line stmt bran cond sub pod time code
1 1     1   896 use strict;
  1         2  
  1         37  
2 1     1   7 use warnings;
  1         2  
  1         45  
3 1     1   30 use 5.024;
  1         4  
4 1     1   6 use feature qw /postderef signatures/;
  1         2  
  1         146  
5              
6             package Vote::Count::Method::STAR;
7 1     1   558 use namespace::autoclean;
  1         21866  
  1         5  
8 1     1   911 use Moose;
  1         498360  
  1         7  
9             extends 'Vote::Count';
10              
11             our $VERSION='2.00';
12              
13             =head1 NAME
14              
15             Vote::Count::Method::STAR
16              
17             =head1 VERSION 2.00
18              
19             =cut
20              
21             # ABSTRACT: STAR Voting.
22              
23             =pod
24              
25             =head1 SYNOPSIS
26              
27             use Vote::Count::Method::STAR;
28              
29             my $tennessee = Vote::Count::Method::STAR->new(
30             BallotSet => read_range_ballots('t/data/tennessee.range.json'), );
31             my $winner = $tennessee->STAR() ;
32              
33             say $Election->logv();
34              
35             =head1 Description
36              
37             Implements the STAR method for resolving Range Ballots.
38              
39             =head1 Method Common Name: STAR (Score Then Automatic Runoff)
40              
41             Scores the Range Ballots, then holds a runoff between the two highest scored choices. The method is named for the acronym for Score Then Automatic Runoff.
42              
43             =head2 Function Name: STAR
44              
45             Conducts and Logs STAR.
46              
47             Beginning with version 1.08 the STAR() method returns a Hash Ref similar to other Vote::Count Methods. The key 'tie' is true for a tie false otherwise, the key 'winner' contains the winning choice or 0 if there is a tie. When there is a tie an additional key 'tied' contains an Array Ref of the tied choices.
48              
49             When more than 2 choices are in a tie for the automatic runoff STAR() returns them as a tie.
50              
51             =head2 Criteria
52              
53             =head3 Simplicity
54              
55             The Range Ballot is more complex for voters than the Ranked Choice Ballot. The scoring and runoff are both very simple.
56              
57             =head3 Later Harm
58              
59             By ranking the preferred choice with the maximum score, and alternate choices very low, the voter is able to minimuze the later harm impact of those later choices. With 10 choices in regular Borda, the second choice would recieve 90% of the first choice's score, by ranking later choices at the bottom of the scale the impact is much lower.
60              
61             =head3 Condorcet Criteria
62              
63             STAR only meets the Condorcet Loser Criteria. The runoff prevents a Condorcet Loser from winning.
64              
65             STAR does not meet the Smith and Condorcet Winner Criteria.
66              
67             =head3 Consistency
68              
69             STAR should meet Monotonacity. Adding a non-winning choice will have no impact on the outcome unless they can score high enough to reach and lose the runoff phase. Clone handling is dependent on the behavior of the clone group supporters, if they rank the clones far apart, the clone that attracts later support from non-clone supporters is likely to not reach the runoff.
70              
71             =head3 Strategic Voting
72              
73             STAR creates strong incentive for strategic voting. The voter must decide to either mitigate later harm, or to show strong support for their secondary choices. Even when the voter decides to rate the choices accurately, it is a greater effort than ranking them.
74              
75             =cut
76              
77 1     1   7700 no warnings 'experimental';
  1         3  
  1         60  
78             # use YAML::XS;
79              
80 1     1   7 use Carp;
  1         16  
  1         100  
81 1     1   7 use List::Util qw( min max sum );
  1         3  
  1         82  
82             # use Data::Dumper;
83 1     1   713 use Sort::Hash;
  1         950  
  1         586  
84              
85             # Similar needs will arise elsewhere. this method should be generalized
86             # and put in a shared role. the aability to resolve ties internally will
87             # also be desired.
88              
89 10     10   15 sub _best_two ( $I, $scores ) {
  10         17  
  10         14  
  10         15  
90 10         31 my %sv = $scores->RawCount()->%*;
91 10         40 my @order = sort_hash( 'desc', \%sv );
92 10         1641 my @toptwo = ( shift @order, shift @order );
93 10         19 my %tied = ( map { $_ => $sv{$_} } @toptwo );
  20         51  
94 10         22 my $lastval = $sv{ $toptwo[1] };
95 10         29 while ( $sv{ $order[0] } == $lastval ) {
96 3         7 my $tieit = shift @order;
97 3         10 $tied{$tieit} = $sv{tieit};
98             }
99 10 100       24 if ( scalar( keys %tied ) > 2 ) {
100 3         17 $I->logt(
101             "Unhandled Situation, there is a tie in determining the top two for Automatic Runoff."
102             );
103 3         20 $I->logt( join( ', ', ( sort keys %tied ) ) );
104 3         15 $I->logd( $scores->RankTable() );
105             # $I->logd( Dumper $I );
106 3         24 return ( keys %tied );
107             }
108 7         35 return @toptwo;
109             }
110              
111 8     8 1 3645 sub STAR ( $self, $active = undef ) {
  8         17  
  8         14  
  8         14  
112 8 100       193 $active = $self->Active() unless defined $active;
113 8         26 my $scores = $self->Score($active);
114 8         26 $self->logv( $scores->RankTable() );
115 8         44 my @best_two = $self->_best_two($scores);
116 8 100       21 if ( scalar( @best_two ) > 2 ) {
117 2         46 return { 'tie' => 1, 'winner' => 0, 'tied' => \@best_two };
118             }
119 6         14 my ( $A, $B ) = @best_two;
120 6         24 my ( $countA, $countB ) = $self->RangeBallotPair( $A, $B );
121 6 100       24 if ( $countA > $countB ) {
    100          
122 4         25 $self->logt("Automatic Runoff Winner: $A [ $A: $countA -- $B: $countB ]");
123 4         43 return { 'tie' => 0, 'winner' => $A };
124             }
125             elsif ( $countA < $countB ) {
126 1         8 $self->logt("Automatic Runoff Winner: $B [ $B: $countB -- $A: $countA ]");
127 1         12 return { 'tie' => 0, 'winner' => $B };
128             }
129             else {
130 1         8 $self->logt("Automatic Runoff TIE: [ $A: $countA -- $B: $countB ]");
131 1         12 return { 'tie' => 1, 'winner' => 0, 'tied' => [ $A, $B ] };
132             }
133             }
134              
135             1;
136              
137             #FOOTER
138              
139             =pod
140              
141             BUG TRACKER
142              
143             L<https://github.com/brainbuz/Vote-Count/issues>
144              
145             AUTHOR
146              
147             John Karr (BRAINBUZ) brainbuz@cpan.org
148              
149             CONTRIBUTORS
150              
151             Copyright 2019-2021 by John Karr (BRAINBUZ) brainbuz@cpan.org.
152              
153             LICENSE
154              
155             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>.
156              
157             SUPPORT
158              
159             This software is provided as is, per the terms of the GNU Public License. Professional support and customisation services are available from the author.
160              
161             =cut
162