X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=appendix-a6.git;a=blobdiff_plain;f=compute-scottish-stv;h=6fde10308b409753d9f877e1df709300d2558fa3;hp=e3d5d7770a6b9f81ea701ddb58c3bd444e8dcf4c;hb=a4f2fc687bb9e16c33a59619acafe2cc21833ee1;hpb=781d40da5cf41034debb17d3b7f4f62d5665a97e;ds=sidebyside diff --git a/compute-scottish-stv b/compute-scottish-stv index e3d5d77..6fde103 100755 --- a/compute-scottish-stv +++ b/compute-scottish-stv @@ -4,6 +4,9 @@ # The Scottish Local Government Elections Order 2007 use strict; +use Carp; +use Data::Dumper; +use Math::BigRat; use bigrat; # Data structures: @@ -29,6 +32,18 @@ our $stage=0; our $quota; our %tie; +open DEBUG, ">.compute.log" or die $!; +DEBUG->autoflush(1); + +$SIG{__WARN__} = sub { + print DEBUG Dumper(\%tie, + \@non_transferable, + \%cands, + \@allvotes, + $stage, $quota); + confess $_[0]; +}; + sub unkopt ($$) { my ($what,$opt) = @_; if ($opt =~ m/^[A-Z]/) { @@ -55,12 +70,12 @@ for (;;) { } } } elsif (m/^(\w+) = (\S*) \|/) { - my ($cand,$desc) = @_; + my ($cand,$desc) = ($1,$2); unkopt "candidate $cand", $_ foreach split / /, $'; $cands{$cand}{Desc} = $desc; } elsif (m/^(\w*) :\s*(.*)\s*\|(.*)/) { my ($voter,$prefs,$opts) = ($1,$2,$3); - my $v = { Voter => $voter }; + my $v = { Voter => $voter, Prefs => [ ] }; push @{ $v->{Prefs} }, [ $_ =~ m/\w+/g ] foreach split /\s+/, $prefs; foreach $_ (split / /, $opts) { @@ -81,12 +96,15 @@ for (;;) { } $cands{$_}{Cand} = $_ foreach keys %cands; +$_->{Weight} //= 1/1 foreach @allvotes; +$_->{TransferredSurplus} //= [ ] foreach @allvotes; sub votelog ($$) { my ($vote,$m) = @_; push @{ $vote->{Log} }, "stage $stage: $m"; } +sub sortballots (@); sub sortballots (@) { # Takes each argument, which should be a ballot, sorts # it into $cand{CAND}{Votes} according to first preference. @@ -118,7 +136,7 @@ sub sortballots (@) { my $noncont = $c->{NonCont}; if ($noncont) { votelog $v, "dropping pref $fp, $noncont"; - sortvallots $v; + sortballots $v; next; } votelog $v, "sorted into pile for candidate $fp weight $w"; @@ -128,7 +146,7 @@ sub sortballots (@) { sub prf { my $fmt = shift; - printf "stage %d: ".$_, $stage, @_; + printf "stage %d: ".$fmt, $stage, @_; } sub countballots () { @@ -136,22 +154,23 @@ sub countballots () { my $c = $cands{$cand}; next if $c->{NonCont}; $c->{Total} = 0; - $c->{Total} += $_->{Weight} foreach @{ $c->{Voters} }; - prf "cand %s: %s votes\n", $stage, $cand, $c->{Total}; + $c->{Total} += $_->{Weight} foreach @{ $c->{Votes} }; + prf "candidate %-10s: %10s votes\n", $cand, $c->{Total}; $c->{History}[$stage-1] = $c->{Total}; } } sub computequota () { my $totalvalid = 0; - $totalvalid += $_->{Total} foreach keys %cands; - $quota = floor($totalvalid / (1 + $seats)); + $totalvalid += $_->{Total} foreach values %cands; + $quota = ($totalvalid / (1 + $seats)) -> bfloor(); + prf "quota %10s\n", $quota; } sub total_history_cmp () { my $ha = $a->{History}; my $hb = $b->{History}; - foreach my $s (reverse 1 .. $stage) { + foreach my $s (reverse 0 .. $stage-1) { my $d = $ha->[$s] <=> $hb->[$s]; next unless $d; print DEBUG "history cmp $a->{Cand} $b->{Cand}". @@ -206,13 +225,14 @@ sub select_best_worst ($$$$) { if ($nequal > 1) { my @all = map { $_->{Cand} } @maybe[0 .. $nequal-1]; my $tiekey = $signum > 0 ? 'Win' : 'Lose'; - my $win = $tie{"@all"}{$tiekey}; - die "need tie break, want $tiekey from @all" unless defined $win; + $selectcand = $tie{"@all"}{$tiekey}; + die "need tie break, want $tiekey from @all" + unless defined $selectcand; prf "$what %s due to tie break amongst %s\n", $selectcand, "@all"; } else { - $selectcand = $maybe[0]; - prf "$what %s\n"; + $selectcand = $maybe[0]{Cand}; + prf "$what %s\n", $selectcand; } return $cands{$selectcand}; @@ -224,13 +244,17 @@ sub elect_core ($) { $c->{NonCont} = 'Elected'; } +$stage = 1; + sortballots @allvotes; +countballots(); +computequota(); for (;;) { $stage++; my $seats_remain = $seats - - grep { $_->{NonCont} eq 'Elected' } values %cands; + - grep { ($_->{NonCont} // '') eq 'Elected' } values %cands; if (continuing() <= $seats_remain) { foreach my $c (continuing()) { prf "electing %s to fill remaining place(s)\n", $c->{Cand}; @@ -258,16 +282,30 @@ for (;;) { next; } - my $B = $c->{Weight}; + my $B = $c->{Total}; + my %tspr; + + prf "surplus %10s\n", $surplus; foreach my $v (@{ $c->{Votes} }) { + my $previously = $v->{TransferredSurplus}; + push @$previously, $c->{Cand}; + my $A = $surplus * $v->{Weight}; my $F = 100000; - my $xfervalue = floor(($A * $F) / $B) / $F; + my $xfervalue = ((($A * $F) / $B) -> bfloor() ) / $F; # SLGEO 48(3): we do arithmetic to 5 d3ecimal places, # but always rounding down votelog $v, "transferring with value $xfervalue (A=$A B=$B)"; $v->{Weight} = $xfervalue; + + if (defined $tspr{"@$previously"}) { + die unless $tspr{"@$previously"} == $xfervalue; + } else { + $tspr{"@$previously"} = $xfervalue; + prf "transfer value of ballots %s: %10s\n", + "@$previously", $xfervalue; + } sortballots $v; } $c->{Votes} = { }; # will crash if we access it again