User method guide by Kevin Venzke, on and for votingmethods.net
All code in this text file can be used freely for any purpose without restriction.
CONCEPTS
The concept of writing your own methods is that you will write a JavaScript class
called either "all_winners" or "one_winner" depending on the requirements of the
page. At time of writing, /calc, /trunc, and /yee use "all_winners" and /iiastrat
requires "one_winner."
The differences:
1. "all_winners" classes MAY return more than one winner. But this is potentially
wasteful if the page (like /trunc) will not display more than one winner.
"one_winner" classes must return exactly ONE winner. (In an array or by itself,
doesn't matter.)
2. "all_winners" classes MAY use their own randomness. "one_winner" classes MAY NOT
introduce their own randomness, and the code will get rejected if you use terms
like "shuffle" and "random." Instead of these, you may use the "tiebreak" array
which provides a random value for each candidate.
3. "one_winner" classes are acceptable to use, as is, on all pages, but pages that
require "one_winner" won't attempt to use an "all_winners" class.
You can use multiple methods at once, generally. Just paste them in sequence. If
you have trouble with this, you can try using a ||| delimiter between the classes,
but if you use ||| at all you must use it between each pair of classes.
Your class can have a few noteworthy properties, demonstrated here:
class all_winners {
name = 'Elect A and B';
allows_equal_rank = true;
_solve_ {
this.notes = 'Hi there, this is in italics
This is a new line';
return [0,1];
}
}
If you don't specify a "name," there's a default value. In some cases if you give
methods duplicated names, your names will get edited.
"allows_equal_rank" will be false if you don't specify it. Currently only the /calc
page generates scenarios with equal ranking, and if "allows_equal_rank" is false it
will refrain from sending such scenarios to your method.
"notes" is any text that you want to see that might explain what happened in the
course of running your method. It will be displayed as HTML, so you can format it
as you like. Some features may not display the "notes" depending on whether it's
practical.
You may not override the constructor, and don't put parentheses after _solve_ to
try to correct the faulty-looking JavaScript.
You may create other functions besides _solve_, but they won't have the same access
to the "hooks" provided to you. For example, if you want the "num_cands" variable
to be accessible to another function, you will have to use one of these two
strategies:
class all_winners {
name = 'Elect Last Candidate';
_solve_ {
// Copy num_cands to be an attribute of this object:
this.num_cands = num_cands;
return this.get_the_last_candidate();
}
get_the_last_candidate() {
return this.num_cands - 1;
}
}
class all_winners {
name = 'Elect Last Candidate';
_solve_ {
// Send num_cands to the other function as a parameter:
return this.get_the_last_candidate( num_cands );
}
get_the_last_candidate( nc ) {
return nc - 1;
}
}
HOOKS
A number of variables and functions are provided to you so that you can solve an
election. They are divided into categories.
1. Basic information
num_cands : an integer. How many candidates are in the election?
num_blocs : an integer. How many voting blocs are in the election?
cand_name : an array with each candidate's name indexed by number.
total_size : a float or integer. The total size of all voting blocs. You may want
this to check for majorities, which would exceed (total_size / 2.0).
cw : an integer saying which candidate is the Condorcet winner, or -1 if there is
none.
equal_ranking_occurs : true/false, whether any of the blocs are using above-bottom
equal ranking.
cand_list : an array of integers. cand_list[0] will be 0, [1] will be 1, etc. This
is what you will probably use to sort candidates by some attribute.
bloc_list : an array of integers, like the above, but for voting blocs. You can use
this if you want to sort blocs for some reason.
tied_outcomes_shown : true/false, whether the feature running your method will even
do anything with additional winners if you return a tied result with multiple
winners. You can use this to save time on searching for possible ties, because
currently only /calc can display multiple winners.
2. Info on the cast votes of the blocs
bloc_size : an array of floats or integers, saying how many voters are represented
by each bloc.
rank_for_cand : a two-dimensional array of integers to tell you at what rank
position a given bloc ranks a given candidate. Example: rank_for_cand[0][1] could
contain 2 if voting bloc 0 ranks candidate 1 in the third position (third, because
ranking starts at zero). "Third position" does not tell you how many candidates are
ranked above the candidate, unless equal ranking is known to not be involved.
cand_for_rank : a three-dimensional array of integers to tell you which
candidate(s) a given bloc ranks at a given position. There is always a possibility
of multiple candidates at a position because, even if equal ranking isn't
supported, truncation is always supported for the final position. So for example,
cand_for_rank[2][1][0] might contain 3 if candidate 3 is the first (i.e. index 0)
candidate that bloc 2 ranks in the second position (i.e. index 1). There will
always be at least one candidate, unless you check a position beyond the end of the
bloc's ranking, but that is an error and you must not do that.
bottom_rank : an array telling you the final position for a given bloc. So if
bottom_rank[4] == 1, this tells you that bloc 4 ranked all candidate(s) in position
0, with all other candidates at position 1, the bottom.
bloc_approves : an array saying whether a given bloc approves a given candidate
index. For instance bloc_approves[2][3] would be "true" if bloc 2 approves
candidate 3.
3. Scores indexed by candidate number:
first_prefs : The number of first preferences received, not necessarily strict. So
this would better be called "top rankings." Example: first_prefs[0] contains the
first preference count of candidate 0.
appr : Approval scores, which currently means above-bottom rankings. See also
"votes_in_total" below.
tiebreak : This is a random number for each candidate which is the only random
element a method may use in a "one_winner" class. This is because we will be
checking for a change in result based on strategy, and the random elements must
come out the same way.
max_oppos_to : An integer for each candidate equal to the largest number of votes
against them in any one pairwise contest.
votes_in_total : How many above-bottom votes a candidate received. Currently, this
is equivalent to approval. But in the future, explicit approval cutoffs may be
supported by some feature, and "appr" will refer to that kind of approval, not
votes in total. When you write code now, you can decide whether your method would
ever use an explicit cutoff, or if that wouldn't make sense.
strict_first_prefs : like first_prefs, but a candidate receives no credit for top
rankings shared with other candidates. Usually this is only interesting when
enforcing the Plurality criterion, not when computing a method.
4. Sets of candidates, which are all arrays of integers:
smith : the Smith set.
schwartz : the Schwartz set.
uncovered : the uncovered candidates.
cdtt : Woodall's CDTT i.e. Condorcet Doubly Augmented Gross Top Tier. This is the
Schwartz set defined using only pairwise full majorities. Markus Schulze proposed
the same concept earlier, as the "Generalized Majority Criterion."
clean_schwartz : an experimental attempt to remove noise from the Smith set. I have
not proposed this anywhere at this point but the idea is you disregard a pairwise
win of some X over some Y if there exists some other candidate Z such that the
number of voters who top-rank Z and are indifferent between X and Y exceeds the
number of voters who prefer X over Y. Find the Schwartz set for the defeats that
remain.
plurality_dq : the candidates disqualified by the Plurality criterion.
weak_plurality_dq : the candidates disqualified by the "weak" Plurality criterion
(concept introduced at /cce, necessary for a CCE method).
cce_top_tier : the CCE top tier (from /cce page).
5. 2D matrices:
mat : This is the pairwise matrix. So mat[0][1] contains the number of voters who
strictly prefer candidate 0 to candidate 1.
cce_claim_mat : CCE claim matrix. For example cce_claim_mat[0][1] would be either 0
or 1 according to whether candidate 0 has a CCE claim against candidate 1.
appr_oppos : The approval opposition of one candidate against another. This is used
for Approval-Weighted Pairwise. So appr_oppos[3][4] would be the number of voters
who approve candidate 3 and don't approve candidate 4.
6. Data for a second round:
Both of these two enable you to incorporate a "true" second round if the feature
supports it (currently only /calc does not). This means that you have the ability
to go back to original, pre-strategy preferences to conduct a sincere second round.
Otherwise, these arrays just contain a result based on the pairwise matrix.
round2_runoff : an array indexed by the two frontrunners whose value is an array of
winners. So round2_runoff[0][1] might equal [0], or [1], or [0,1] based on which of
candidates 0 and 1 would win a runoff, or (in the last case) if it would be a tie.
round2_majcheck : an array indexed by the "tentative winner" candidate index, whose
value is an array containing every candidate with the best score in the second
round. There is no shuffling on this result. If the best score was below a
majority, then the tentative winner will be the value in the array. For example,
round2_majcheck[3] could be equal to [3] (if and only if no candidate could obtain
majority approval in the second round), or it could be equal to [0], or perhaps to
[0,1,2,4,5...] with each candidate who attained the greatest score.
7. Functions you can call:
shuffle : shuffle(myarray) will shuffle it. It is not allowed in a one_winner
class.
new_2d_matrix : new_2d_matrix(4), for example, will give you a 4x4 array populated
with 0s.
get_top_prefs_excluding : A function to give you a bloc's favorite preference(s)
excluding some set of candidates, such as ones who have already been eliminated.
A typical call would be: get_top_prefs_excluding( cand_for_rank[b], elimd )
Where cand_for_rank refers to the hook described earlier, for a specific bloc
number b, and with elimd containing an array of candidate indices of candidates to
rule out. The function returns an array of the best preference(s), but may also
return an empty array, if the bloc has no above-bottom preferences that qualify.
sort_by : A handy sort, usually for candidates. It returns nothing, sorting a list
in place. sort_by(cand_list, tiebreak, true) would sort the cand_list according to
the tiebreak array (floats indexed by candidate number), and due to the "true" it
will sort in reverse order so that the candidate with the highest tiebreak score
will come first.
do_river : This performs the River method for you. You pass two parameters: An
array with the candidates (like cand_list), and an array of propositions that you
have already sorted strongest to weakest. Each element of the array of propositions
must have index 0 as the defeating candidate and index 1 as the defeated. There may
be other elements in your proposition arrays (which you perhaps use earlier for
sorting), but they will be ignored. The return value is an integer for each
candidate, telling you which candidate's "bin" they ended up in. (Under normal
River rules, a candidate is eligible for election if they ended up in their own
bin.)
get_schwartz_for : Give this function a 2D matrix, let's call it "m," in which
m[0][1] > m[1][0] if candidate (or whatever it is) number 0 should have a "win"
over candidate 1 in the Schwartz set sense. Alternatively, m[0][1] can just be set
to true. You receive back an array with two elements. Element 0 is an array
containing the candidate indices of the Schwartz set (such as [0,1] for those two
candidates), and element 1 is the 2D matrix showing which candidates have beatpaths
to which other candidates.
get_borda_scores : To get Borda scores you will call like this:
get_borda_scores( rank_for_cand, bloc_size, equal_rank_multiplier, truncated_multiplier )
rank_for_cand and bloc_size are aforementioned values provided to you. The two
multipliers control how equal ranking and truncation get scores. You may see the
sample Borda implementation for an idea of how to use these.
get_cand_sets : Give this function a candidate count, and it returns an array
containing all possible sets of candidates for purposes of solving a method like
DAC or DSC. Each element in this array actually has two elements: The first element
is the array containing the indices of the candidates in this set, and the second
element is just a zero, which you can overwrite with whatever you need in order to
score the set.
NOTE: Using this can make the method quite slow, and maxes out at 12 candidates for
your own protection.
do_chain_climb : Do chain climbing as in TACC or BPW(chain). You provide a sorted
cand_list (so that the first candidate will automatically enter the "chain") and a
2D matrix, probably "mat." You get back not an array but a candidate index of the
last candidate who could be added to the chain, who is normally then elected.
random_choice : Provide a list, and it will return a random element from the list.
Not allowed in one_winner classes.
tiebreak_choice : Provide a list and also a tiebreak array (such as tiebreak
itself). This assumes the tiebreak array has at least as many elements as the
largest value in the list of options.
Example: tiebreak_choice( [3,4], [.9,.8,.7,.6,.5,.4] ) would return 3 because .6 is
greater than .5.
EXAMPLES
"all_winners" examples come first, followed by some "one_winner" examples.
ALL_WINNERS EXAMPLES
// FPP
class all_winners {
name = 'FPP (plurality)';
_solve_ {
// In order to have a primary and secondary sort order, you perform the sorts in
// reverse order of priority. So shuffle first and then sort by first preferences.
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
// This says that the winners are every candidate whose first prefs equal those of the first candidate.
// We sorted the list so first_prefs[cand_list[0]] will be the greatest number of first preferences.
// If we were content to just return one winner and not worry about ties, we could simply return cand_list[0].
let winners = cand_list.filter( a => ( first_prefs[a] == first_prefs[cand_list[0]] ) );
return winners;
}
}
// Approval
class all_winners {
name = 'Approval';
allows_equal_rank = true;
_solve_ {
// We can do exactly the same thing as FPP.
shuffle( cand_list );
sort_by( cand_list, appr, true );
let winners = cand_list.filter( a => ( appr[a] == appr[cand_list[0]] ) );
return winners;
}
}
// Top-Two Runoff: performs a true second round if available, else uses the cast votes
class all_winners {
name = 'Top-Two Runoff';
_solve_ {
let winners = [];
// Here we have a loop to try running the method 8 times to see if we get different outcomes,
// hoping that we can find all possible outcomes for this election.
for( let trials=0; trials<8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
// round2_runoff tells us which candidate between the two provided can win the second round.
let cur_winners = round2_runoff[ cand_list[0] ][ cand_list[1] ];
for( let i=0; i mat[i][j] + tmat[i][j] ) {
dqd_list.push( i );
break;
}
}
}
}
let elig;
// If all candidates are DQd then eligible winners are everyong.
if( dqd_list.length == cand_list.length )
elig = [...cand_list];
else
// otherwise it is only those who are not DQd.
elig = cand_list.filter( x => ( dqd_list.indexOf( x ) == -1 ) );
shuffle( elig ); // just so there's no bias possible from the order of winners
sort_by( elig, appr, true );
// Winners are every candidate with the max approval among the eligible candidates.
let winners = elig.filter( x => ( appr[x] == appr[ elig[0] ] ) );
return winners;
}
}
// AW Majority Check: performs a true second round if available, else uses the cast votes
class all_winners {
name = 'AWMajCheck';
allows_equal_rank = true;
_solve_ {
let winners = [];
for( let trials=0; trials<8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, appr, true );
let cur_winners = round2_majcheck[ cand_list[0] ];
shuffle( cur_winners );
for( let i=0; i total_size / 2.0 )
return [ cand_list[0] ];
let cur_winners = round2_majcheck[ cand_list[0] ];
shuffle( cur_winners );
for( let i=0; i -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let defeats = [];
for(let i=0; i < num_cands; i++) {
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
// For every pairwise win, get the candidates, magnitude, margin, and a random tiebreaker
defeats.push( [ i, j, mat[i][j], mat[i][j] - mat[j][i], Math.random() ] );
}
}
}
}
// Sort properties in descending order of importance
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
// Run the River shortcut method
let rvr = do_river( cand_list, defeats );
for( let i = 0; i < num_cands; i++ ) {
// Every candidate still in their bin is a River winner
if( rvr[i] == i )
if( winners.indexOf( rvr[i] ) == -1 )
winners.push( rvr[i] );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// River(WV), WITHOUT using do_river function
class all_winners {
name = 'River(WV)pure';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let defeats = [];
for(let i=0; i < num_cands; i++) {
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
defeats.push( [ i, j, mat[i][j], mat[i][j] - mat[j][i], Math.random() ] );
}
}
}
}
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
let curbin = [];
// Everyone starts in their own bin
for( let i=0; i < num_cands; i++ ) {
curbin[i] = i;
}
for( let i=0; i < defeats.length; i++ ) {
// For every defeat, move every candidate in the loser's original bin to the winner's current bin.
for( let j=0; j < num_cands; j++ ) {
if( curbin[j] == defeats[i][1] ) {
curbin[j] = curbin[ defeats[i][0] ];
}
}
}
for( let i = 0; i < num_cands; i++ ) {
if( curbin[i] == i )
if( winners.indexOf( curbin[i] ) == -1 )
winners.push( curbin[i] );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// River(AWP) (Approval-Weighted Pairwise)
class all_winners {
name = 'River(AWP)';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let defeats = [];
for(let i=0; i < num_cands; i++) {
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
defeats.push( [ i, j, appr_oppos[i][j], appr[i], Math.random() ] );
}
}
}
}
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
let rvr = do_river( cand_list, defeats );
for( let i = 0; i < num_cands; i++ ) {
if( rvr[i] == i )
if( winners.indexOf( rvr[i] ) == -1 )
winners.push( rvr[i] );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// River(CCE) making use of helper functions
class all_winners {
name = 'River(CCE)';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
// Eligible candidates are electable under weak plurality and CCE top tier
let eligible = cand_list.filter( x => ( weak_plurality_dq.indexOf(x) == -1 ) );
let ccetoptier = cce_top_tier;
// alternative:
// let ccetoptier = get_schwartz_for( cce_claim_mat )[0];
eligible = eligible.filter( x => ( ccetoptier.indexOf( x ) > -1 ) );
let winners = [];
// Now run River 8 times to try to get all possible winners
for( let trials = 0; trials < 8; trials++ ) {
let defeats = [];
for(let i=0; i < num_cands; i++) {
if( eligible.indexOf( i ) == -1 ) continue;
for(let j=0; j < num_cands; j++) {
if( eligible.indexOf( j ) == -1 ) continue;
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
defeats.push( [ i, j, mat[i][j], mat[i][j] - mat[j][i], Math.random() ] );
}
}
}
}
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
let rvr = do_river( cand_list, defeats );
for( let i = 0; i < num_cands; i++ ) {
if( eligible.indexOf( i ) == -1 ) continue;
if( rvr[i] == i )
if( winners.indexOf( rvr[i] ) == -1 )
winners.push( rvr[i] );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// River(CCE) without using any helper functions
class all_winners {
name = 'River(CCE)pure';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let wk_plur_dq = [];
let best_first_prefs = 0;
// We could use strict_first_prefs array, but we can do it ourselves for a demonstration
let str_first_prefs = Array( num_cands ).fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
if( cand_for_rank[b][0].length == 1 ) {
let topcand = cand_for_rank[b][0][0];
str_first_prefs[ topcand ] += bloc_size[b];
if( str_first_prefs[ topcand ] > best_first_prefs )
best_first_prefs = str_first_prefs[ topcand ];
}
}
// Let's demonstrate how to check that our str_first_prefs are the same as the provided strict_first_prefs:
for( let i=0; i appr[c] ) {
wk_plur_dq.push( c );
break;
}
}
}
}
}
let eligible = cand_list.filter( x => ( wk_plur_dq.indexOf(x) == -1 ) );
// Need 3d matrices
let mat3d = [];
let mat3dequal = [];
for( let i = 0; i < num_cands; i++ ) {
mat3d[i] = [];
mat3dequal[i] = [];
for( let j = 0; j < num_cands; j++ ) {
mat3d[i][j] = [];
mat3dequal[i][j] = [];
for( let k = 0; k < num_cands; k++ ) {
mat3d[i][j][k] = 0.0;
mat3dequal[i][j][k] = 0.0;
}
}
}
for( let b = 0; b < num_blocs; b++ ) {
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
let brfc = rank_for_cand[b];
for( let k = 0; k < num_cands; k++ ) {
if( i!=j && i!=k && j!=k) {
if( brfc[i] < brfc[j] && brfc[j] < brfc[k] ) {
mat3d[i][j][k] += bloc_size[b];
} else if ( brfc[i] == brfc[j] && brfc[j] < brfc[k] ) {
mat3dequal[i][j][k] += bloc_size[b];
}
}
}
}
}
}
let claimmat = new_2d_matrix( num_cands );
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
if( i != j && mat[i][j] > mat[j][i] ) {
let anylosses = false;
for( let k = 0; k < num_cands; k++ ) {
if( k != i && k != j ) {
let vfi = mat[i][k];
let vfk = mat[k][i];
vfi += mat3d[k][i][j] + mat3dequal[k][i][j];
vfk -= mat3d[k][i][j];
if( vfk >= vfi ) {
anylosses = true;
}
}
}
if( !anylosses ) {
claimmat[i][j] = 1.0;
}
}
}
}
let beatpathmat = new_2d_matrix( num_cands );
for( let i=0; i claimmat[j][i] )
beatpathmat[i][j] = 1;
}
}
let cont = true;
while( cont ) {
cont = false;
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ )
if( i != j ) {
for( let k = 0; k < num_cands; k++ )
if( j != k && i != k ) {
let couldcopy = ( beatpathmat[i][j] > 0 && beatpathmat[j][k] > 0 ) ? 1 : 0;
if( couldcopy > beatpathmat[i][k] ) {
beatpathmat[i][k] = couldcopy;
cont = true;
}
}
}
}
}
let anyloss = Array(num_cands).fill(false);
let ccetoptier = [];
for( let i=0; i < num_cands; i++ ) {
for( let j=0; j < num_cands; j++ ) {
if( beatpathmat[j][i] > beatpathmat[i][j] ) {
anyloss[i] = true;
break;
}
}
if( !anyloss[i] ) ccetoptier.push( i );
}
eligible = eligible.filter( x => ( ccetoptier.indexOf( x ) > -1 ) );
let winners = [];
// Now run River 8 times to try to get all possible winners
for( let trials = 0; trials < 8; trials++ ) {
let defeats = [];
for(let i=0; i < num_cands; i++) {
if( eligible.indexOf( i ) == -1 ) continue;
for(let j=0; j < num_cands; j++) {
if( eligible.indexOf( j ) == -1 ) continue;
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
defeats.push( [ i, j, mat[i][j], mat[i][j] - mat[j][i], Math.random() ] );
}
}
}
}
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
let curbin = [];
for( let i=0; i < num_cands; i++ ) {
curbin[i] = i;
}
for( let i=0; i < defeats.length; i++ ) {
for( let j=0; j < num_cands; j++ ) {
if( curbin[j] == defeats[i][1] ) {
curbin[j] = curbin[ defeats[i][0] ];
}
}
}
for( let i = 0; i < num_cands; i++ ) {
if( curbin[i] == i && eligible.indexOf( i ) != -1 )
if( winners.indexOf( curbin[i] ) == -1 )
winners.push( curbin[i] );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// TACC
class all_winners {
name = 'TACC';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
shuffle( cand_list );
// primary sort is approval ascending
sort_by( cand_list, appr, false );
let x = do_chain_climb( cand_list, mat );
if( winners.indexOf( x ) == -1 )
winners.push( x );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// BPW(chain)
class all_winners {
name = 'BPW(chain)';
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
let x = do_chain_climb( cand_list, mat );
if( winners.indexOf( x ) == -1 )
winners.push( x );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// BPW(max)
class all_winners {
name = 'BPW(max)';
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
let first_pref_winner = cand_list[0];
// Get every candidate who beats or ties the first pref winner
let beat_winner = cand_list.filter( x => ( mat[x][first_pref_winner] >= mat[first_pref_winner][x] && x!=first_pref_winner ) );
sort_by( beat_winner, first_prefs, true );
// Everyone who had that max first prefs among those beat/tying the first pref winner
let crt_winners = beat_winner.filter( x => ( first_prefs[x] == first_prefs[ beat_winner[0] ] ) );
for( let i=0; i -1 ) return [ cw ];
shuffle( cand_list );
sort_by( cand_list, appr, true );
return cand_list.filter( x => ( appr[x] == appr[ cand_list[0] ] ) );
}
}
// Condorcet//FPP
class all_winners {
name = 'C//FPP';
_solve_ {
if( cw > -1 ) return [ cw ];
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
return cand_list.filter( x => ( first_prefs[x] == first_prefs[ cand_list[0] ] ) );
}
}
// Smith//Approval
class all_winners {
name = 'Smith//Approval';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
shuffle( cand_list );
sort_by( smith, appr, true );
return smith.filter( x => ( appr[x] == appr[ smith[0] ] ) );
}
}
// Borda (three options)
class all_winners {
name = 'Borda';
allows_equal_rank = true;
_solve_ {
// if you unremark this line you can have Black instead:
// if( cw > -1 ) return [ cw ];
// ** Several treatments of Borda, pick one of the three: **
// * Later-no-harm Borda:
let equal_rank_multiplier = 1.0, truncated_multiplier = 1.0;
// * Symmetrically completed, including truncation:
//let equal_rank_multiplier = 0.5, truncated_multiplier = 1.0;
// * Symmetrically completed, but truncated candidates receive zero points:
//let equal_rank_multiplier = 0.5, truncated_multiplier = 0.0;
// name customization
if( equal_rank_multiplier == 1.0 && truncated_multiplier == 1.0 )
this.name += '(LNH)';
else if( equal_rank_multiplier == 0.5 && truncated_multiplier == 1.0 )
this.name += '(SC)';
else if( equal_rank_multiplier == 0.5 && truncated_multiplier == 0.0 )
this.name += '(SC,ZFT)';
let sco = get_borda_scores( rank_for_cand, bloc_size, equal_rank_multiplier, truncated_multiplier );
sort_by( cand_list, sco, true );
return cand_list.filter( x => ( sco[x] == sco[ cand_list[0] ] ) );
}
}
// IBIFA
class all_winners {
name = 'IBIFA';
allows_equal_rank = true;
_solve_ {
this.nc = num_cands;
this.nb = num_blocs;
this.sort_by = sort_by;
this.cand_list = cand_list;
let winners = [];
for( let trials=0; trials<8; trials++ ) {
let worstbottom = 0;
for( let b = 0; b worstbottom ) worstbottom = bottom_rank[b];
let votes = Array( this.nc ).fill(0.0);
let winrank = Array( this.nc ).fill(999);
let qualifierct = 0;
for( let c = 0; c < this.nc; c++ ) {
for( let i = 0; i < worstbottom; i++ ) {
let x = this.testcand( bottom_rank, c, i, cand_for_rank, rank_for_cand, bloc_size );
if( x > 0.0 ) {
winrank[c] = i;
votes[c] = x;
qualifierct++;
break;
}
}
}
shuffle( cand_list );
if( qualifierct > 0 ) {
sort_by( cand_list, votes, true );
sort_by( cand_list, winrank, false );
} else {
sort_by( cand_list, votes_in_total, true );
}
let crtwinner = cand_list[0];
if( qualifierct > 0 )
this.notes = this.notes + cand_name[ crtwinner ]+' wins with ' + winrank[crtwinner] + ' rank and ' + votes[crtwinner] + ' votes.
';
else
this.notes = this.notes + cand_name[ crtwinner ]+' wins by having greatest approval.
';
if( winners.indexOf( crtwinner ) == -1 )
winners.push( crtwinner );
if( !tied_outcomes_shown ) break;
}
return winners;
}
getblocthrurank( r, btm, cfr ) {
// go through until you've seen all the ranks
let candspassed = 0;
for(let i=0; i= r + 1 ) return i;
}
return btm - 1;
}
testcand( btm, cand, r, cfr, rfc, bsize ) {
// returns a score or -1
let tally = Array( this.nc ).fill(0.0);
let thrurank = Array( this.nb );
for( let i=0; i tally[ this.cand_list[1] ] )
return tally[cand];
return -1.0;
}
}
// DAC
class all_winners {
name = 'DAC';
allows_equal_rank = true;
_solve_ {
let winners = [];
let candsets = get_cand_sets( num_cands );
for( let trials=0; trials<8; trials++ ) {
if( trials > 0 ) {
for( let i=0; i ( included.indexOf(x) == -1 ) );
for( let e=0; e btmforincl ) {
btmforincl = rank_for_cand[b][ included[e] ];
if( btmforincl > topforexcl ) {
anycontrary = true;
break;
}
}
}
if( !anycontrary )
cs[1] += bloc_size[b];
}
} // blocs
shuffle( candsets );
candsets.sort( (x,y) => ( y[1] - x[1] ) );
let alive = [...cand_list];
let curelim = 1; // 1 mng the first elimination
for( let s=0; s < candsets.length; s++ ) {
let thisset = candsets[s][0];
let anyelig = false;
for(let j=0; j 0 ) {
for( let i=0; i ( included.indexOf(x) == -1 ) );
for( let e=0; e btmforincl ) {
btmforincl = rank_for_cand[b][ included[e] ];
if( btmforincl >= topforexcl ) {
anycontrary = true;
break;
}
}
}
if( !anycontrary )
cs[1] += bloc_size[b];
}
} // blocs
shuffle( candsets );
candsets.sort( (x,y) => ( y[1] - x[1] ) );
let alive = [...cand_list];
let curelim = 1; // 1 mng the first elimination
for( let s=0; s < candsets.length; s++ ) {
// we only need to know incl, not even score
let thisset = candsets[s][0];
let anyelig = false;
for(let j=0; j -1 ) return [ cw ];
this.nc = num_cands;
this.mat = mat;
this.mod_mat = new_2d_matrix( this.nc );
for( let i = 0; i < this.nc; i++ ) {
for( let j = 0; j < this.nc; j++ ) {
this.mod_mat[i][j] = this.mat[i][j];
}
}
let alive = [...cand_list];
while( true ) {
let newalive = this.getSchwartzFor( alive );
alive = newalive;
if( alive.length == 1 ) break;
if( ! this.anyDefeatsLeft( alive ) ) {
break;
}
let weakdef = this.getWeakestDefeats( alive );
if( weakdef.length < 1 ) {
break;
} else {
for( let i = 0; i < weakdef.length; i++ ) {
this.mod_mat[ weakdef[i][0] ][ weakdef[i][1] ] = 0.0;
this.mod_mat[ weakdef[i][1] ][ weakdef[i][0] ] = 0.0;
}
}
}
let winner = [...alive];
return winner;
}
getSchwartzFor( alive ) {
let bpmat = [];
let nc = this.nc;
for( let i = 0; i < nc; i++ ) {
bpmat[i] = [];
for( let j = 0; j < nc; j++ ) {
// if i is dead then no wins for him
if( alive.indexOf(i) == -1 )
bpmat[i][j] = 0;
else if( alive.indexOf(j) == -1 )
// just say everyone alive beats j
bpmat[i][j] = 1;
else if( i == j )
// you can beat yourself if alive
bpmat[i][j] = 1;
else if( this.mod_mat[i][j] > this.mod_mat[j][i] )
bpmat[i][j] = 1;
else
bpmat[i][j] = 0;
}
}
let anychanges = true;
while( anychanges ) {
anychanges = false;
for( let i = 0; i < nc; i++ ) {
for( let j = 0; j < nc; j++ ) {
if( i != j && alive.indexOf(i) > -1 && alive.indexOf(j) > -1 ) {
for( let k = 0; k < nc; k++ ) {
if( j != k && i != k && alive.indexOf(k) > -1 ) {
let couldcopy = Math.min( bpmat[i][j], bpmat[j][k] );
if( couldcopy > bpmat[i][k] ) {
bpmat[i][k] = couldcopy;
anychanges = true;
}
}
}
}
}
}
}
let schwartz = [];
for( let ii = 0; ii < alive.length; ii++ ) {
let i = alive[ii];
let anyfails = false;
for( let jj = 0; jj < alive.length; jj++ ) {
let j = alive[jj];
if( bpmat[i][j] == 0 && bpmat[j][i] == 1 ) {
anyfails = true;
break;
}
}
if( !anyfails ) {
schwartz.push( i );
}
}
return schwartz;
}
anyDefeatsLeft( alive ) {
let anydefeats = false;
for( let i = 0; i < alive.length; i++ ) {
for( let j = 0; j < alive.length; j++ ) {
if( i!=j ) {
if( this.mod_mat[ alive[i] ][ alive[j] ] != this.mod_mat[ alive[j] ][ alive[i] ] ) {
anydefeats = true;
break;
}
}
}
if( anydefeats ) break;
}
return anydefeats;
}
getWeakestDefeats( alive ) {
// compile all defeats among alive cands, then sort, margin first then WV. no tiebreakers needed
let pcontests = [];
let mat = this.mod_mat;
let nc = this.nc;
for( let i = 0; i -1 && alive.indexOf(j) > -1 && i != j ) {
if( mat[i][j] > mat[j][i] ) {
pcontests.push( [ i, j, mat[i][j], ( mat[i][j] - mat[j][i] ) ] );
}
}
}
}
pcontests.sort( (a,b) => ( a[3] - b[3] ) );
pcontests.sort( (a,b) => ( a[2] - b[2] ) );
let ret = [];
for( let i = 0; i < pcontests.length; i++ ) {
if( pcontests[i][2] == pcontests[0][2] && pcontests[i][3] == pcontests[0][3] ) {
ret.push( [ pcontests[i][0], pcontests[i][1] ] );
} else break;
}
return ret;
}
}
// Schulze(WV), beatpath algorithm
class all_winners {
name = 'Schulze(WV)';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let schxmat = new_2d_matrix( num_cands );
for( let i=0; i < num_cands; i++ ) {
for( let j=0; j < num_cands; j++ ) {
if( mat[i][j] > mat[j][i] )
schxmat[i][j] = [ mat[i][j], mat[i][j] - mat[j][i] ];
else
schxmat[i][j] = [ 0, 0 ];
}
}
let cont = true;
while( cont ) {
cont = false;
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
if( i != j ) {
for( let k = 0; k < num_cands; k++ ) {
if( j != k && i != k ) {
let opt = new Array(3);
opt[0] = [ schxmat[i][k][0], schxmat[i][k][1] ];
opt[1] = [ schxmat[i][j][0], schxmat[i][j][1] ];
opt[2] = [ schxmat[j][k][0], schxmat[j][k][1] ];
if( opt[1][0] > opt[2][0] || ( opt[1][0] == opt[2][0] && opt[1][1] > opt[2][1] ) ) {
opt[1][0] = opt[2][0];
opt[1][1] = opt[2][1];
}
let bestopt = 0;
if( opt[1][0] > opt[bestopt][0] || ( opt[1][0] == opt[bestopt][0] && opt[1][1] > opt[bestopt][1] ) ) {
bestopt = 1;
}
if( bestopt != 0 ) {
schxmat[i][k][0] = opt[bestopt][0];
schxmat[i][k][1] = opt[bestopt][1];
cont = true;
}
}
}
}
}
}
}
let winners = [];
for(let i = 0; i < num_cands; i++) {
let anylosses = false;
for(let j = 0; j < num_cands; j++) {
if( i != j ) {
let isworse = false;
if( schxmat[i][j][0] < schxmat[j][i][0] )
isworse = true;
else if( schxmat[i][j][0] == schxmat[j][i][0] && schxmat[i][j][1] < schxmat[j][i][1] )
isworse = true;
if( isworse ) {
anylosses = true;
break;
}
}
}
if(!anylosses) {
winners.push(i);
}
}
return winners;
}
}
// Schulze(pairwise opposition)
class all_winners {
name = 'Schulze(PO)';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let schxmat = new_2d_matrix( num_cands );
for( let i=0; i < num_cands; i++ ) {
for( let j=0; j < num_cands; j++ ) {
schxmat[i][j] = mat[i][j], mat[i][j] - mat[j][i];
}
}
let cont = true;
while( cont ) {
cont = false;
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
if( i != j ) {
for( let k = 0; k < num_cands; k++ ) {
if( j != k && i != k ) {
let opt = new Array(3);
opt[0] = schxmat[i][k];
opt[1] = schxmat[i][j];
opt[2] = schxmat[j][k];
if( opt[1] > opt[2] ) {
opt[1] = opt[2];
}
let bestopt = 0;
if( opt[1] > opt[bestopt] ) {
bestopt = 1;
}
if( bestopt != 0 ) {
schxmat[i][k] = opt[bestopt];
cont = true;
}
}
}
}
}
}
}
let winners = [];
for(let i = 0; i < num_cands; i++) {
let anylosses = false;
for(let j = 0; j < num_cands; j++) {
if( i != j ) {
let isworse = false;
if( schxmat[i][j] < schxmat[j][i] )
isworse = true;
if( isworse ) {
anylosses = true;
break;
}
}
}
if(!anylosses) {
winners.push(i);
}
}
return winners;
}
}
// BTP (Beats or Ties Preferred), KV method during brainstorming with FS
class all_winners {
name = 'BTP';
allows_equal_rank = true;
_solve_ {
// New "sco" array for each candidate initialized to zeros
let sco = Array( num_cands ).fill( 0.0 );
for( let b = 0; b < num_blocs; b++ ) {
for( let i = 0; i < num_cands; i++ ) {
let defeat_by_preferred = false;
for( let j = 0; j < num_cands; j++ ) {
if( i!=j ) {
if( mat[j][i] > mat[i][j] && rank_for_cand[b][j] < rank_for_cand[b][i] ) {
defeat_by_preferred = true;
break;
}
}
}
// Bloc votes for candidate i if no candidate j who pairwise defeats i is ranked
// higher than i by this bloc.
if( !defeat_by_preferred )
sco[i] += bloc_size[b];
}
}
shuffle( cand_list );
sort_by( cand_list, sco, true );
let winners = cand_list.filter( a => ( sco[a] == sco[cand_list[0]] ) );
return winners;
}
}
// Voice of Reason
class all_winners {
name = 'Voice of Reason';
// probably OK to allow equal ranking
allows_equal_rank = true;
_solve_ {
let bscore = [];
for( let b=0; b < num_blocs; b++ ) {
let toadd = 0.0;
let brfc = rank_for_cand[b];
// for every unique pair of candidates (i 0 through num_cands minus 2, and j i+1 through num_cands minus 1)
for( let i=0; imat[j][i] ? 1 : mat[i][j]brfc[j] ? -1 : 0 );
// Do nothing if it's a pairwise tie
if( sought == 0 ) ;
// Give 1 point if voter agrees, 0.5 if voter rated equal, 0 if voter disagreed.
else if( ( sought==1 && actual==1 ) || ( sought==-1 && actual==-1 ) )
toadd+=1.0;
else if( actual==0 )
toadd+=0.5;
}
}
bscore.push( [ b, toadd ] );
}
bscore.sort( (x,y) => ( y[1] - x[1] ) );
// Best score achieved by any bloc?
let winscore = bscore[0][1];
let newfps = Array(num_cands).fill( 0.0 );
for( let i=0; i ( newfps[a] == newfps[cand_list[0]] ) );
return winners;
}
}
// IRV, with ER-fractional
class all_winners {
name = 'IRV';
// Fractional treatment
allows_equal_rank = true;
_solve_ {
let winners = [];
let ties_encountered = false;
for( let trials=0; trials<8; trials++ ) {
let elimd = [];
let trial_winner;
while( true ) {
let tally = Array( num_cands ).fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
let vote_for = get_top_prefs_excluding( cand_for_rank[b], elimd );
let vote_for_ct = vote_for.length;
for( let i=0; i ( elimd.indexOf(x) == -1 ) );
shuffle( current_cands );
sort_by( current_cands, tally, false );
if( tally[ current_cands[ current_cands.length - 1 ] ] > total_size / 2.0 ) {
trial_winner = current_cands[ current_cands.length - 1 ];
if( winners.indexOf( trial_winner ) == -1 )
winners.push( trial_winner );
break;
} else if( current_cands.length == 2 ) {
//maybe add both
for( let i=0; i < 2; i++ ) {
if( winners.indexOf( current_cands[i] ) == -1 && tally[ current_cands[i] ] == tally[ current_cands[1] ] )
winners.push( current_cands[i] );
}
break;
} else { // otherwise 3+ cands and no majority, so elim and check for ties
if( tally[ current_cands[ 0 ] ] == tally[ current_cands[ 1 ] ] )
ties_encountered = true;
let elimd_cand = current_cands[ 0 ];
elimd.push( elimd_cand );
}
}
if( !ties_encountered ) break;
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// Smith//IRV, with ER-fractional
class all_winners {
name = 'Smith//IRV';
// Fractional treatment
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
let ties_encountered = false;
for( let trials=0; trials<8; trials++ ) {
let elimd = [];
let trial_winner;
for( let i=0; i < num_cands; i++ ) {
if( smith.indexOf( i ) == -1 ) {
elimd.push( i );
}
}
while( true ) {
let tally = Array( num_cands ).fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
let vote_for = get_top_prefs_excluding( cand_for_rank[b], elimd );
let vote_for_ct = vote_for.length;
for( let i=0; i ( elimd.indexOf(x) == -1 ) );
shuffle( current_cands );
sort_by( current_cands, tally, false );
if( tally[ current_cands[ current_cands.length - 1 ] ] > total_size / 2.0 ) {
trial_winner = current_cands[ current_cands.length - 1 ];
if( winners.indexOf( trial_winner ) == -1 )
winners.push( trial_winner );
break;
} else if( current_cands.length == 2 ) {
//maybe add both
for( let i=0; i < 2; i++ ) {
if( winners.indexOf( current_cands[i] ) == -1 && tally[ current_cands[i] ] == tally[ current_cands[1] ] )
winners.push( current_cands[i] );
}
break;
} else { // otherwise 3+ cands and no majority, so elim and check for ties
if( tally[ current_cands[ 0 ] ] == tally[ current_cands[ 1 ] ] )
ties_encountered = true;
let elimd_cand = current_cands[ 0 ];
elimd.push( elimd_cand );
}
}
if( !ties_encountered ) break;
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// Margins Sorted Approval
class all_winners {
name = 'Marg. Sorted Appr.';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials=0; trials<8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, appr, true );
let crt = [];
for(let i=0; i mat[ hghr ][ lowr ] ) {
notsorted = true;
possiblepairs.push( [ i, crt[i][1] - crt[i+1][1] ] );
}
}
if( notsorted ) {
possiblepairs.sort( (x,y) => ( x[1] - y[1] ) );
let swap_i = possiblepairs[0][0];
let tmp = [ crt[swap_i][0], crt[swap_i][1] ];
crt[swap_i] = crt[swap_i+1];
crt[swap_i+1] = tmp;
}
}
if( winners.indexOf( crt[0][0] ) == -1 ) {
winners.push( crt[0][0] );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// RMPA (River Majority Pass Approval)
// I could use the do_river function but won't, in order to demonstrate that
// defeats don't need to be individually sorted. You can just go down the list
// of candidates and do all majority defeats at once.
class all_winners {
name = 'RMPA';
allows_equal_rank = true;
_solve_ {
// unremark this line to get Condorcet//RMPA, which performs well for a Condorcet
// method in /trunc sim
///// if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, appr, true );
let appr_winner = cand_list[0];
let currentbin = [];
for( let i=0; i total_size / 2.0 ) {
for( let k=0; k total_size / 2.0 ) {
for( let k=0; k -1 ) return [ cw ];
let winners = [];
let tiesfound = false;
for( let trials = 0; trials < 8; trials++ ) {
let transmat = new_2d_matrix( num_cands );
let defeats = [];
for(let i=0; i < num_cands; i++) {
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
defeats.push( [ i, j, mat[i][j], mat[i][j] - mat[j][i], Math.random() ] );
}
}
}
}
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
let cur_winners = [];
for( let d = 0; d < defeats.length; d++ ) {
let thisdef = defeats[d];
if( !tiesfound && d < defeats.length - 1 ) {
let nextdef = defeats[d+1];
if( thisdef[2] == nextdef[2] && thisdef[3] == nextdef[3] )
tiesfound = true;
}
let pwinner = thisdef[0];
let ploser = thisdef[1];
if( transmat[ploser][pwinner] == 0.0 && transmat[pwinner][ploser] == 0.0 ) {
transmat[pwinner][ploser] = 1.0;
transmat[ploser][pwinner] = -1.0;
let anychanges = true;
while( anychanges ) {
anychanges = false;
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
if( i != j ) {
for( let k = 0; k < num_cands; k++ ) {
if( i != k && j != k ) {
if( transmat[i][j] != 0.0 && transmat[i][j] == transmat[j][k]
&& transmat[i][k] == 0.0 ) {
anychanges = true;
transmat[i][k] = transmat[i][j];
}
}
}
}
}
}
}
}
}
for( let i = 0; i < num_cands; i++ ) {
let haslosses = false;
for( let j = 0; j < num_cands; j++ ) {
if( i != j && transmat[i][j] < 0.0 ) {
haslosses = true;
break;
}
}
if( !haslosses )
cur_winners.push( i );
}
for( let i = 0; i < cur_winners.length; i++ ) {
if( winners.indexOf( cur_winners[i] ) == -1 )
winners.push( cur_winners[i] );
}
if( !tiesfound ) break;
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// Etjon Basha's Iterated Bucklin
// Doesn't support equal ranking
// As per usual I don't implement the rules on ties occurring in a round, because
// this causes the method to fail to resolve.
class all_winners {
name = 'IterBucklin';
_solve_ {
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let tieb = new Array( num_cands );
// Generate a consistent tiebreaker for all rounds
for( let i=0; i < num_cands; i++ ) tieb[i] = Math.random();
let votes = new Array( num_cands );
let curthresh = new Array( num_blocs ).fill(0);
let rndwinner = null;
let currnd = 1;
while( true ) {
votes.fill( 0.0 );
for( let b=0; b < num_blocs; b++ ) {
let votethru = curthresh[b];
for( let i = 0; i <= votethru; i++ ) {
for( let j = 0; j < cand_for_rank[b][i].length; j++ ) {
votes[ cand_for_rank[b][i][j] ] += bloc_size[b];
}
}
}
sort_by( cand_list, tieb, true );
sort_by( cand_list, votes, true );
rndwinner = cand_list[0];
currnd++;
let anychanges = false;
for( let b = 0; b < num_blocs; b++ ) {
if( rank_for_cand[b][ rndwinner ] == curthresh[ b ] ) ;
else if( rank_for_cand[b][ rndwinner ] > curthresh[ b ] && curthresh[ b ]+1 < bottom_rank[b] ) {
curthresh[ b ]++;
anychanges = true;
} else if( rank_for_cand[b][ rndwinner ] < curthresh[ b ] ) {
curthresh[ b ]--;
anychanges = true;
}
}
if( !anychanges ) break;
if( currnd > 200 ) {
alert('IterBucklin went 200 rounds');
break;
}
}
if( winners.indexOf( rndwinner ) == -1 )
winners.push( rndwinner );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// CdlA
class all_winners {
name = 'CdlA';
allows_equal_rank = true;
_solve_ {
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
// Generate a consistent tiebreaker for all rounds
let tieb = new Array( num_cands );
for( let i=0; i < num_cands; i++ ) tieb[i] = Math.random();
let rndwinner = null;
let rndwinners = [];
let votes = new Array( num_cands );
while( true ) {
votes.fill( 0.0 );
for( let b = 0; b < num_blocs; b++ ) {
let votethru = 0;
if( rndwinners.length > 0 ) {
for( let i = 0; i < rndwinners.length; i++ ) {
if( votethru + 1 < rank_for_cand[b][ rndwinners[i] ] ) {
votethru = rank_for_cand[b][ rndwinners[i] ] - 1;
}
}
}
for( let i = 0; i <= votethru; i++ ) {
for( let j = 0; j < cand_for_rank[b][i].length; j++ ) {
votes[ cand_for_rank[b][i][j] ] += bloc_size[b];
}
}
}
sort_by( cand_list, tieb, true );
sort_by( cand_list, votes, true );
rndwinner = cand_list[0];
if( rndwinners.indexOf( rndwinner ) != -1 ) {
break;
}
rndwinners.push( rndwinner );
}
if( winners.indexOf( rndwinner ) == -1 )
winners.push( rndwinner );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// MDDA
class all_winners {
name = 'MDDA';
allows_equal_rank = true;
_solve_ {
let dqd_list = [];
for(let i=0; i < num_cands; i++) {
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[j][i] > total_size / 2.0 ) {
dqd_list.push( i );
break;
}
}
}
}
let elig;
if( dqd_list.length == cand_list.length )
elig = [...cand_list];
else
elig = cand_list.filter( x => ( dqd_list.indexOf( x ) == -1 ) );
sort_by( elig, appr, true );
let winners = elig.filter( x => ( appr[x] == appr[ elig[0] ] ) );
return winners;
}
}
// MAMPO
class all_winners {
name = 'MAMPO';
allows_equal_rank = true;
_solve_ {
let winners = [];
shuffle( cand_list );
sort_by( cand_list, appr, true );
let majappr = [];
for( let i = 0; i < num_cands; i++ ) {
if( appr[i] > total_size / 2.0 ) {
majappr.push( i );
}
}
let nummaj = majappr.length;
if( majappr.length < 2 ) {
for( let i = 0; i < num_cands; i++ ) {
if( appr[ i ] == appr[ cand_list[0] ] ) {
if( winners.indexOf( i ) == -1 ) {
winners.push( i );
}
}
}
} else {
shuffle( majappr );
sort_by( majappr, appr, true );
sort_by( majappr, max_oppos_to, false );
for( let i = 0; i < num_cands; i++ ) {
if( appr[ i ] == appr[ majappr[0] ] && max_oppos_to[ i ] == max_oppos_to[ majappr[0] ] ) {
if( winners.indexOf( i ) == -1 ) {
winners.push( i );
}
}
}
}
return winners;
}
}
// Bucklin (ER, Whole)
class all_winners {
name = 'Bucklin';
allows_equal_rank = true;
_solve_ {
let globalbottom = -1;
for(let b=0; b < num_blocs; b++) {
if( bottom_rank[b] > globalbottom ) globalbottom = bottom_rank[b];
}
let score = new Array( num_cands );
let currnd = 0;
let thrurank = 0;
let havemaj = false;
while( true ) {
score.fill( 0.0 );
for( let b = 0; b < num_blocs; b++ ) {
thrurank = bottom_rank[b] - 1;
let candspassed = 0;
for(let i=0; i < bottom_rank[b]; i++) {
candspassed += cand_for_rank[b][i].length;
if( candspassed >= currnd + 1 ) {
thrurank = i;
break;
}
}
for( let h = 0; h <= thrurank; h++ ) {
for( let i = 0; i < cand_for_rank[b][h].length; i++ ) {
let thiscand = cand_for_rank[b][h][i];
score[ thiscand ] += bloc_size[b];
if( score[ thiscand ] > total_size / 2.0 ) {
havemaj = true;
}
}
}
}
if( havemaj || currnd == globalbottom - 1 ) {
shuffle( cand_list );
sort_by( cand_list, score, true );
break;
}
currnd++;
}
let winners = cand_list.filter( x => ( score[x] == score[ cand_list[0] ] ) );
return winners;
}
}
// Adjusted Condorcet Plurality
class all_winners {
name = 'ACP';
_solve_ {
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
let fpw = cand_list[0];
let adjustmat = new_2d_matrix( num_cands );
for( let b = 0; b < num_blocs; b++ ) {
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
let brfc = rank_for_cand[b];
let fpwpreferred = ( brfc[fpw] < brfc[i] && brfc[fpw] < brfc[j] );
if( !fpwpreferred ) {
if( brfc[i] < brfc[j] ) {
adjustmat[i][j] += bloc_size[b];
}
}
}
}
}
let newcw = -1;
for( let i = 0; i < num_cands; i++ ) {
let anynonwins = false;
for( let j = 0; j < num_cands; j++ ) {
if( i != j ) {
if( adjustmat[j][i] >= adjustmat[i][j] ) {
anynonwins = true;
break;
}
}
}
if( !anynonwins ) newcw = i;
}
if( newcw != -1 ) {
if( winners.indexOf( newcw ) == -1 ) {
winners.push( newcw );
}
} else {
if( winners.indexOf( cand_list[0] ) == -1 ) {
winners.push( cand_list[0] );
}
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// AER (Approval Elimination Runoff)
class all_winners {
name = 'AER';
_solve_ {
let winners = [];
let votes = Array( num_cands );
for( let trials = 0; trials < 8; trials++ ) {
let elimd = [];
let currnd = 1;
let ourwinner;
while( true ) {
votes.fill( 0.0 );
let votescast = 0.0;
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
for( let b = 0; b < num_blocs; b++ ) {
for( let i = 0; i < bottom_rank[b]; i++ ) {
let votestoliving = false;
// equal-ranking not supported
if( elimd.indexOf( cand_for_rank[b][i][0] ) == -1 ) {
votes[ cand_for_rank[b][i][0] ] += bloc_size[b];
votescast += bloc_size[b];
votestoliving = true;
}
if( votestoliving ) break;
}
}
sort_by( cand_list, votes, true );
if( votes[ cand_list[0] ] > ( votescast / 2.0 ) ) {
ourwinner = cand_list[0];
break;
}
let ee = [];
for( let i = 0; i < num_cands; i++ ) {
if( elimd.indexOf(i) == -1 ) {
ee.push( i );
}
}
shuffle( ee );
sort_by( ee, appr, false );
elimd.push( ee[0] );
currnd++;
}
if( winners.indexOf( ourwinner ) == -1 )
winners.push( ourwinner );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// No-Elim IRV type 1
class all_winners {
name = 'NoElimIRV#1';
_solve_ {
let winners = [];
let votes = Array( num_cands );
for( let trials = 0; trials < 8; trials++ ) {
let ourwinner = null;
let elimd = [];
while( true ) {
votes.fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
for( let i = 0; i < bottom_rank[b]; i++ ) {
let votestoliving = false;
votes[ cand_for_rank[b][i][0] ] += bloc_size[b];
if( elimd.indexOf( cand_for_rank[b][i][0] ) == -1 ) {
votestoliving = true;
}
if( votestoliving ) break;
}
}
shuffle( cand_list );
sort_by( cand_list, votes, true );
let rndwinner = cand_list[0];
if( votes[ rndwinner ] > ( total_size / 2.0 ) ) {
ourwinner = rndwinner;
break;
}
let ee = [];
for( let i = 0; i < num_cands; i++ ) {
if( elimd.indexOf(i) == -1 ) {
ee.push( i );
}
}
shuffle( ee );
ee.sort( (x,y) => ( votes[x] - votes[y] ) );
if( ee.length == 0 ) {
ourwinner = rndwinner;
break;
}
if( ee[0] == rndwinner ) {
ourwinner = rndwinner;
break;
}
elimd.push( ee[0] );
}
if( winners.indexOf( ourwinner ) == -1 )
winners.push( ourwinner );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// No-Elim IRV type 2
class all_winners {
name = 'NoElimIRV#2';
_solve_ {
let winners = [];
let votes = Array( num_cands );
for( let trials = 0; trials < 8; trials++ ) {
let ourwinner = null;
let elimd = [];
while( true ) {
votes.fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
for( let i = 0; i < bottom_rank[b]; i++ ) {
let votestoliving = false;
votes[ cand_for_rank[b][i][0] ] += bloc_size[b];
if( elimd.indexOf( cand_for_rank[b][i][0] ) == -1 ) {
votestoliving = true;
}
if( votestoliving ) break;
}
}
shuffle( cand_list );
sort_by( cand_list, votes, true );
let rndwinner = cand_list[0];
if( votes[ rndwinner ] > ( total_size / 2.0 ) ) {
ourwinner = rndwinner;
break;
}
let ee = [];
for( let i = 0; i < num_cands; i++ ) {
if( elimd.indexOf(i) == -1 ) {
ee.push( i );
}
}
shuffle( ee );
ee.sort( (x,y) => ( votes[x] - votes[y] ) );
if( ee.length == 0 ) {
ourwinner = rndwinner;
break;
}
elimd.push( ee[0] );
}
if( winners.indexOf( ourwinner ) == -1 )
winners.push( ourwinner );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// King of the Hill (KOTH)
class all_winners {
name = 'KingOfTheHill';
_solve_ {
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let curwinner = -1;
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
let fpw = cand_list[0];
for( let i=1; i < num_cands; i++ ) {
if( mat[fpw][ cand_list[i] ] > total_size / 2.0 || mat[ cand_list[i] ][fpw] > total_size / 2.0 ) {
if( mat[fpw][ cand_list[i] ] > total_size / 2.0 ) {
curwinner = fpw;
break;
} else {
curwinner = cand_list[i];
break;
}
}
}
if( curwinner == -1 ) curwinner = fpw;
if( winners.indexOf( curwinner ) == -1 )
winners.push( curwinner );
// check if more trials are unneeded
if( trials == 0 ) {
let needmore = false;
for( let i = 0; i < num_cands-1; i++ ) {
if( first_prefs[ cand_list[ i ] ] == first_prefs[ cand_list[ i+1 ] ] ) {
needmore = true;
break;
}
}
if( !needmore ) break;
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// Chain Runoff (or "QR" for Quick Runoff)
class all_winners {
name = 'Chain Runoff';
_solve_ {
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let curwinner = -1;
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
curwinner = cand_list[0];
for( let i = 0; i < num_cands - 1; i++ ) {
let higherc = cand_list[i];
let lowerc = cand_list[i+1];
if( mat[lowerc][higherc] <= total_size / 2.0 )
break;
curwinner = lowerc;
}
if( winners.indexOf( curwinner ) == -1 )
winners.push( curwinner );
// check if more trials are unneeded
if( trials == 0 ) {
let needmore = false;
for( let i = 0; i < num_cands-1; i++ ) {
if( first_prefs[ cand_list[ i ] ] == first_prefs[ cand_list[ i+1 ] ] ) {
needmore = true;
break;
}
}
if( !needmore ) break;
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// Cross Max
class all_winners {
name = 'Cross Max';
allows_equal_rank = true;
_solve_ {
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
let fpw = cand_list[0];
let scores = [];
for( let i = 0; i < num_cands; i++ )
if( i != fpw )
scores.push( [ i, mat[i][fpw] ] );
for( let i = 0; i < num_cands; i++ )
if( i != fpw )
scores.push( [ fpw, mat[fpw][i] ] );
shuffle( scores );
scores.sort( (x,y) => ( y[1] - x[1] ) );
if( winners.indexOf( scores[0][0] ) == -1 )
winners.push( scores[0][0] );
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// BRBO (Best Response to Best Opposition)
class all_winners {
name = 'BRBO';
allows_equal_rank = true;
_solve_ {
let winners = [];
let bestopposing = Array( num_cands );
let bestresponse = Array( num_cands );
for(let i=0; i < num_cands; i++) {
bestopposing[i] = Array();
bestresponse[i] = -100;
let bestval = -100;
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[j][i] > bestval ) {
bestopposing[i] = [];
bestval = mat[j][i];
}
if( mat[j][i] == bestval ) {
bestopposing[i].push( [ j, mat[j][i], mat[i][j] ] );
}
}
}
for(let k=0; k < bestopposing[i].length; k++) {
if( bestopposing[i][k][2] > bestresponse[i] ) {
bestresponse[i] = bestopposing[i][k][2];
}
}
}
let winscore = -100;
for(let i=0; i < num_cands; i++)
if( bestresponse[ i ] > winscore )
winscore = bestresponse[i];
shuffle( cand_list );
for(let i=0; i < num_cands; i++)
if( bestresponse[ cand_list[i] ] == winscore )
winners.push( cand_list[i] );
return winners;
}
}
// Benham
class all_winners {
name = 'Benham';
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let elimd = [];
let loops = 0;
let effcw;
while( true ) {
let curvotes = Array(num_cands).fill(0);
effcw = null;
let alive = cand_list.filter( x => ( elimd.indexOf( x ) == -1 ) );
for( let b=0; b < num_blocs; b++ ) {
let ourvote = null;
for( let j=0; j < cand_for_rank[b].length-1; j++ ) {
let anyremaining = false;
if( elimd.indexOf( cand_for_rank[b][j][0] ) == -1 ) {
anyremaining = true;
ourvote = cand_for_rank[b][j][0];
}
if( anyremaining ) {
curvotes[ourvote] += bloc_size[b];
break;
}
}
// if no rank can grant votes then we just don't add any and nothing else happens.
}
// check for cw excluding elimd cands
for(let i=0; i < num_cands; i++) {
if( elimd.indexOf( i ) > -1 ) continue;
let anynonwin = false;
for(let j=0; j < num_cands; j++) {
if( elimd.indexOf( j ) > -1 ) continue;
if(i!=j) {
if( mat[i][j] <= mat[j][i] ) {
anynonwin = true;
break
}
}
}
if( !anynonwin ) {
effcw = i;
break;
}
}
if( effcw !== null ) break;
loops++;
if(loops>50) throw('Benham no resolution');
shuffle( alive );
sort_by( alive, curvotes, false );
elimd.push( alive[0] );
}
if( winners.indexOf( effcw ) == -1 ) {
winners.push( effcw );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// Approval-Elim Condorcet (AEC)
class all_winners {
name = 'AEC';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let elimd = [];
let loops = 0;
let effcw;
while( true ) {
effcw = null;
let alive = cand_list.filter( x => ( elimd.indexOf( x ) == -1 ) );
for(let i=0; i < num_cands; i++) {
if( elimd.indexOf( i ) > -1 ) continue;
let anynonwin = false;
for(let j=0; j < num_cands; j++) {
if( elimd.indexOf( j ) > -1 ) continue;
if(i!=j) {
if( mat[i][j] <= mat[j][i] ) {
anynonwin = true;
break
}
}
}
if( !anynonwin ) {
effcw = i;
break;
}
}
if( effcw !== null ) break;
loops++;
if(loops>50) throw('AEC no resolution');
shuffle( alive );
sort_by( alive, appr, false );
elimd.push( alive[0] );
}
if( winners.indexOf( effcw ) == -1 ) {
winners.push( effcw );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// BTR-IRV
class all_winners {
name = 'BTR-IRV';
// Fractional treatment
allows_equal_rank = true;
_solve_ {
let winners = [];
for( let trials=0; trials<8; trials++ ) {
let elimd = [];
let trial_winner;
while( true ) {
let tally = Array( num_cands ).fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
let vote_for = get_top_prefs_excluding( cand_for_rank[b], elimd );
let vote_for_ct = vote_for.length;
for( let i=0; i ( elimd.indexOf(x) == -1 ) );
shuffle( current_cands );
sort_by( current_cands, tally, false );
if( tally[ current_cands[ current_cands.length - 1 ] ] > total_size / 2.0 ) {
trial_winner = current_cands[ current_cands.length - 1 ];
if( winners.indexOf( trial_winner ) == -1 )
winners.push( trial_winner );
break;
} else if( current_cands.length == 2 ) {
//maybe add both
for( let i=0; i < 2; i++ ) {
if( winners.indexOf( current_cands[i] ) == -1 && tally[ current_cands[i] ] == tally[ current_cands[1] ] )
winners.push( current_cands[i] );
}
break;
} else { // otherwise 3+ cands and need to do a pairwise comparison
let elimd_cand;
let e1 = current_cands[0];
let e2 = current_cands[1];
if( mat[e1][e2] == mat[e2][e1] )
elimd_cand = ( Math.random() < 0.5 ? e1 : e2 );
else if( mat[e1][e2] > mat[e2][e1] )
elimd_cand = e2;
else
elimd_cand = e1;
elimd.push( elimd_cand );
}
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// RCIPE
class all_winners {
name = 'RCIPE';
allows_equal_rank = true;
_solve_ {
let winners = [];
for( let trials=0; trials<8; trials++ ) {
let elimd = [];
let trial_winner;
while( true ) {
let tally = Array( num_cands ).fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
let vote_for = get_top_prefs_excluding( cand_for_rank[b], elimd );
let vote_for_ct = vote_for.length;
for( let i=0; i ( elimd.indexOf(x) == -1 ) );
shuffle( current_cands );
sort_by( current_cands, tally, false );
if( tally[ current_cands[ current_cands.length - 1 ] ] > total_size / 2.0 ) {
trial_winner = current_cands[ current_cands.length - 1 ];
if( winners.indexOf( trial_winner ) == -1 )
winners.push( trial_winner );
break;
} else if( current_cands.length == 2 ) {
//maybe add both
for( let i=0; i < 2; i++ ) {
if( winners.indexOf( current_cands[i] ) == -1 && tally[ current_cands[i] ] == tally[ current_cands[1] ] )
winners.push( current_cands[i] );
}
break;
} else { // otherwise 3+ cands and need to do a pairwise comparison
let elimd_cand = this.checkpwloser( current_cands, mat );
if( elimd_cand == -1 )
elimd_cand = current_cands[0];
elimd.push( elimd_cand );
}
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
checkpwloser( alive, mat ) {
for(let i=0; i= mat[ alive[j] ][ alive[i] ] ) {
anynonloss = true;
break;
}
}
}
if( !anynonloss ) return alive[i];
}
return -1;
}
}
// Raynaud(WV)
class all_winners {
name = 'Raynaud(WV)';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let winners = [];
for( let trials = 0; trials < 8; trials++ ) {
let elimd = [];
let loops = 0;
let curwinners = [];
while( true ) {
let alive = cand_list.filter( x => ( elimd.indexOf( x ) == -1 ) );
// check for cw excluding elimd cands
for(let i=0; i < num_cands; i++) {
if( elimd.indexOf( i ) > -1 ) continue;
let anynonwin = false;
for(let j=0; j < num_cands; j++) {
if( elimd.indexOf( j ) > -1 ) continue;
if(i!=j) {
if( mat[i][j] <= mat[j][i] ) {
anynonwin = true;
break
}
}
}
if( !anynonwin ) {
curwinners = [ i ];
break;
}
}
if( curwinners.length > 0 ) break;
// Find strongest contest among remaining candidates
let defeats = [];
for(let i=0; i < num_cands; i++) {
if( elimd.indexOf( i ) > -1 ) continue;
for(let j=0; j < num_cands; j++) {
if( elimd.indexOf( j ) > -1 ) continue;
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
defeats.push( [ i, j, mat[i][j], mat[i][j] - mat[j][i], Math.random() ] );
}
}
}
}
if( defeats.length == 0 ) {
curwinners = [...alive];
break;
}
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
elimd.push( defeats[0][1] );
}
for( let i=0; i < curwinners.length; i++ ) {
if( winners.indexOf( curwinners[i] ) == -1 ) {
winners.push( curwinners[i] );
}
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// MaxMin(PS), an FBC-satisfying version of MinGS
class all_winners {
name = 'MaxMin(PS)';
allows_equal_rank = true;
_solve_ {
let worstscores = Array( num_cands ).fill( null );
let tmat = new_2d_matrix( num_cands );
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
if( i!=j ) {
for( let b = 0; b < num_blocs; b++ ) {
if( rank_for_cand[b][i] < bottom_rank[b] && rank_for_cand[b][i] == rank_for_cand[b][j] ) {
tmat[i][j] += bloc_size[b];
}
}
}
}
}
for(let i=0; i < num_cands; i++) {
let worst = null;
for(let j=0; j < num_cands; j++) {
if(i!=j) {
let useval = mat[i][j];
// remove this one line if you just want MinGS:
useval += tmat[i][j];
if( useval < worst || worst == null ) {
worst = useval;
}
}
}
worstscores[i] = worst;
}
sort_by( cand_list, worstscores, true );
return cand_list.filter( x => ( worstscores[x] == worstscores[ cand_list[0] ] ) );
}
}
// fpA-max(fpC)
class all_winners {
name = 'fpA-max(fpC)';
_solve_ {
if( cw > -1 ) return [ cw ];
let score = new Array( num_cands );
for(let i=0; i < num_cands; i++) {
let bestval = 0.0;
for(let j=0; j < num_cands; j++)
if( i!=j && mat[j][i] >= mat[i][j] )
if( first_prefs[j] > bestval )
bestval = first_prefs[j];
score[i] = first_prefs[i] - bestval;
}
sort_by( cand_list, score, true );
return cand_list.filter( x => ( score[x] == score[ cand_list[0] ] ) );
}
}
// Single Contest
class all_winners {
name = 'Single Contest';
allows_equal_rank = true;
_solve_ {
let winners = [];
for( let trials=0; trials<8; trials++ ) {
let ourwinners = [];
sort_by( cand_list, first_prefs, true );
if( first_prefs[ cand_list[0] ] > total_size / 2.0 )
return [ cand_list[0] ];
let contestoptions = [];
for( let i=0; i < num_cands; i++ ) {
for( let j=0; j < num_cands; j++ ) {
if( i != j ) {
let relevance = 0.0;
for( let b=0; b < num_blocs; b++ ) {
if( rank_for_cand[b][i] < bottom_rank[b] || rank_for_cand[b][j] < bottom_rank[b] )
relevance += bloc_size[b];
}
contestoptions.push( [ i, j, relevance, appr[i], appr[j] ] );
}
}
}
shuffle(contestoptions);
contestoptions.sort( (x,y) => ( y[4] - x[4] ) );
contestoptions.sort( (x,y) => ( y[3] - x[3] ) );
contestoptions.sort( (x,y) => ( y[2] - x[2] ) );
let c1 = contestoptions[0][0];
let c2 = contestoptions[0][1];
this.notes = 'finalists '+cand_name[c1]+' vs '+cand_name[c2];
if( mat[c1][c2] > mat[c2][c1] )
ourwinners = [ c1 ];
else if( mat[c1][c2] == mat[c2][c1] )
ourwinners = [ c1, c2 ];
else
ourwinners = [ c2 ];
for( let i=0; i < ourwinners.length; i++ ) {
if( winners.indexOf( ourwinners[i] ) == -1 ) {
winners.push( ourwinners[i] );
}
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// MinMax(WV)
class all_winners {
name = 'MinMax(WV)';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let scores = Array(num_cands);
for( let i=0; i < num_cands; i++ ) {
let worstdefeat = [ 0, 0 ];
for( let j=0; j < num_cands; j++ ) {
if( i!=j && mat[j][i] > mat[i][j] ) {
if( mat[j][i] > worstdefeat[0] || ( mat[j][i] == worstdefeat[0] && mat[j][i] - mat[i][j] > worstdefeat[1] ) ) {
worstdefeat[0] = mat[j][i];
worstdefeat[1] = mat[j][i] - mat[i][j];
}
}
}
scores[i] = worstdefeat;
}
cand_list.sort( (x,y) => ( scores[x][1] - scores[y][1] ) );
cand_list.sort( (x,y) => ( scores[x][0] - scores[y][0] ) );
return cand_list.filter( x => ( scores[x][0] == scores[cand_list[0]][0] && scores[x][1] == scores[cand_list[0]][1] ) );
}
}
// Smith//MinMax(WV)
class all_winners {
name = 'Smith//MinMax(WV)';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
let scores = Array(num_cands);
for( let i=0; i < num_cands; i++ ) {
let worstdefeat = [ 0, 0 ];
if( smith.indexOf( i ) == -1 ) {
scores[i] = worstdefeat;
continue;
}
for( let j=0; j < num_cands; j++ ) {
if( smith.indexOf( j ) == -1 ) continue;
if( i!=j && mat[j][i] > mat[i][j] ) {
if( mat[j][i] > worstdefeat[0] || ( mat[j][i] == worstdefeat[0] && mat[j][i] - mat[i][j] > worstdefeat[1] ) ) {
worstdefeat[0] = mat[j][i];
worstdefeat[1] = mat[j][i] - mat[i][j];
}
}
}
scores[i] = worstdefeat;
}
cand_list = [...smith];
cand_list.sort( (x,y) => ( scores[x][1] - scores[y][1] ) );
cand_list.sort( (x,y) => ( scores[x][0] - scores[y][0] ) );
return cand_list.filter( x => ( scores[x][0] == scores[cand_list[0]][0] && scores[x][1] == scores[cand_list[0]][1] ) );
}
}
// MinMax(PO) with FPP tiebreaker
// Unlike MAMPO, we will not use max_oppos_to as a shortcut here
class all_winners {
name = 'MMPO,FPP';
allows_equal_rank = true;
_solve_ {
let scores = Array(num_cands);
for( let i=0; i < num_cands; i++ ) {
let worstoppos = 0;
for( let j=0; j < num_cands; j++ )
if( i!=j )
if( mat[j][i] > worstoppos )
worstoppos = mat[j][i];
scores[i] = worstoppos;
}
sort_by( cand_list, first_prefs, true );
sort_by( cand_list, scores, false );
return cand_list.filter( x => ( scores[x] == scores[cand_list[0]] && first_prefs[x] == first_prefs[cand_list[0]] ) );
}
}
// NWL: No Worse Losses, 2004 KV method
class all_winners {
name = 'NWL';
_solve_ {
let winners = [];
for( let trials=0; trials<8; trials++ ) {
let ourwinner = null;
shuffle( cand_list );
sort_by( cand_list, appr, true );
let aw = cand_list[0];
shuffle( cand_list );
sort_by( cand_list, first_prefs, true );
let fpw = cand_list[0];
let chkstr = 0.0;
let dqgcands = [];
if( fpw == aw ) {
ourwinner = fpw;
} else {
if( mat[ fpw ][ aw ] <= mat[ aw ][ fpw ] ) {
ourwinner = aw;
} else {
chkstr = mat[ fpw ][ aw ];
for( let i=0; i < num_cands; i++ ) {
if( i != fpw ) {
if( mat[ i ][ fpw ] > chkstr && mat[ i ][ fpw ] > mat[ fpw ][ i ] ) {
dqgcands.push( i );
}
}
}
if( dqgcands.length == 0 ) {
ourwinner = fpw;
} else {
ourwinner = aw;
}
}
}
if( winners.indexOf( ourwinner ) == -1 ) {
winners.push( ourwinner );
}
if( !tied_outcomes_shown ) break;
}
return winners;
}
}
// CDTT,FPP (elect CDTT member with the most first preferences)
class all_winners {
name = 'CDTT,FPP';
_solve_ {
cand_list = [...cdtt];
sort_by( cand_list, first_prefs, true );
return cand_list.filter( x => ( first_prefs[x] == first_prefs[cand_list[0]] ) );
}
}
// For demonstration only:
// Try to elect a candidate dq'd by the Plurality criterion, favoring most first prefs
class all_winners {
name = 'PlurDQd';
_solve_ {
let new_cand_list = [...plurality_dq];
if( new_cand_list.length == 0 ) new_cand_list = cand_list;
shuffle( new_cand_list );
sort_by( new_cand_list, first_prefs, true );
return new_cand_list.filter( x => ( first_prefs[x] == first_prefs[new_cand_list[0]] ) );
}
}
// Elect first candidate if equal ranking is used, else the second candidate
class all_winners {
name = 'ERtest';
allows_equal_rank = true;
_solve_ {
return [ equal_ranking_occurs ? 0 : 1 ];
}
}
// elect cand with most first preferences who is allowed by minimal defense and weak plurality
// (i.e. the criteria for a method included on the /misc calculator)
class all_winners {
name = 'MiscCalcFPP';
allows_equal_rank = true;
_solve_ {
let sub_maj_opp = [];
// For MD, get voters who vote for one cand and not another
let implic_appr_oppos = new_2d_matrix( num_cands );
for( let i=0; i total_size / 2.0 ) {
is_ok = false;
break;
}
}
if( is_ok ) sub_maj_opp.push( i );
}
// Intersect the MD-permitted and the weak plurality-permitted
let elig = cand_list.filter( x => ( sub_maj_opp.indexOf(x)>-1 && weak_plurality_dq.indexOf(x)==-1 ) );
shuffle( elig ); // I like shuffling in case the feature will just use the first winner
sort_by( elig, first_prefs, true );
// The winners are every eligible candidate whose first preference count matches the top-sorted candidate's
let winners = elig.filter( a => first_prefs[a] == first_prefs[elig[0]] );
return winners;
}
}
ONE_WINNER EXAMPLES
Not all methods are repeated here. The basic principles are:
1. Do not use random or shuffle, use tiebreak instead. If your all_winners class says:
shuffle( cand_list );
You will probably change it to say:
sort_by( cand_list, tiebreak, true );
(or "..., false" if you want ascending tiebreaker sort.)
2. Do not use trial loops to search for all winners, as you're only returning one. If
you're in a hurry to modify a class, just "break;" at the end of the first loop.
// FPP
class one_winner {
name = 'FPP (plurality)';
_solve_ {
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, first_prefs, true );
return cand_list[0];
}
}
// Approval
class one_winner {
name = 'Approval';
allows_equal_rank = true;
_solve_ {
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, appr, true );
return cand_list[0];
}
}
// Top-Two Runoff: performs a true second round if available, else uses the cast votes
class one_winner {
name = 'Top-Two Runoff';
_solve_ {
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, first_prefs, true );
let cur_winners = round2_runoff[ cand_list[0] ][ cand_list[1] ];
return cur_winners[0];
}
}
// AW Majority Check: performs a true second round if available, else uses the cast votes
// Compare the brevity of this to the all_winners version
class one_winner {
name = 'AWMajCheck';
allows_equal_rank = true;
_solve_ {
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, appr, true );
let cur_winners = round2_majcheck[ cand_list[0] ];
sort_by( cur_winners, tiebreak, true );
return cur_winners[0];
}
}
// River(WV), using do_river function
class one_winner {
name = 'River(WV)';
allows_equal_rank = true;
_solve_ {
// Note below how it is still allowed to return an array:
if( cw > -1 ) return [ cw ];
let defeats = [];
for(let i=0; i < num_cands; i++) {
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[i][j] > mat[j][i] ) {
defeats.push( [ i, j, mat[i][j], mat[i][j] - mat[j][i], tiebreak[i] ] );
}
}
}
}
defeats.sort( (x,y) => ( y[4] - x[4] ) );
defeats.sort( (x,y) => ( y[3] - x[3] ) );
defeats.sort( (x,y) => ( y[2] - x[2] ) );
let rvr = do_river( cand_list, defeats );
sort_by( rvr, tiebreak, true );
return rvr[0];
}
}
// TACC
class one_winner {
name = 'TACC';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, appr, false );
// do_chain_climb returns only one winner, so it's easy:
return do_chain_climb( cand_list, mat );
}
}
// BPW(max)
class one_winner {
name = 'BPW(max)';
_solve_ {
if( cw > -1 ) return [ cw ];
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, first_prefs, true );
let first_pref_winner = cand_list[0];
// Get every candidate who beats or ties the first pref winner
let beat_winner = cand_list.filter( x => ( mat[x][first_pref_winner] >= mat[first_pref_winner][x] && x!=first_pref_winner ) );
sort_by( beat_winner, first_prefs, true );
return beat_winner[0];
}
}
// Condorcet//Approval
class one_winner {
name = 'C//A';
allows_equal_rank = true;
_solve_ {
if( cw > -1 ) return [ cw ];
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, appr, true );
return cand_list[0];
}
}
// Borda (three options)
class one_winner {
name = 'Borda';
allows_equal_rank = true;
_solve_ {
// if you unremark this line you can have Black instead:
// if( cw > -1 ) return [ cw ];
// ** Several treatments of Borda, pick one of the three: **
// * Later-no-harm Borda:
let equal_rank_multiplier = 1.0, truncated_multiplier = 1.0;
// * Symmetrically completed, including truncation:
//let equal_rank_multiplier = 0.5, truncated_multiplier = 1.0;
// * Symmetrically completed, but truncated candidates receive zero points:
//let equal_rank_multiplier = 0.5, truncated_multiplier = 0.0;
// name customization
if( equal_rank_multiplier == 1.0 && truncated_multiplier == 1.0 )
this.name += '(LNH)';
else if( equal_rank_multiplier == 0.5 && truncated_multiplier == 1.0 )
this.name += '(SC)';
else if( equal_rank_multiplier == 0.5 && truncated_multiplier == 0.0 )
this.name += '(SC,ZFT)';
let sco = get_borda_scores( rank_for_cand, bloc_size, equal_rank_multiplier, truncated_multiplier );
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, sco, true );
return cand_list[0];
}
}
// IBIFA
class one_winner {
name = 'IBIFA';
allows_equal_rank = true;
_solve_ {
this.nc = num_cands;
this.nb = num_blocs;
this.sort_by = sort_by;
this.cand_list = cand_list;
let worstbottom = 0;
for( let b = 0; b worstbottom ) worstbottom = bottom_rank[b];
let votes = Array( this.nc ).fill(0.0);
let winrank = Array( this.nc ).fill(999);
let qualifierct = 0;
for( let c = 0; c < this.nc; c++ ) {
for( let i = 0; i < worstbottom; i++ ) {
let x = this.testcand( bottom_rank, c, i, cand_for_rank, rank_for_cand, bloc_size );
if( x > 0.0 ) {
winrank[c] = i;
votes[c] = x;
qualifierct++;
break;
}
}
}
sort_by( cand_list, tiebreak, true );
if( qualifierct > 0 ) {
sort_by( cand_list, votes, true );
sort_by( cand_list, winrank, false );
} else {
sort_by( cand_list, votes_in_total, true );
}
let crtwinner = cand_list[0];
if( qualifierct > 0 )
this.notes = this.notes + cand_name[ crtwinner ]+' wins with ' + winrank[crtwinner] + ' rank and ' + votes[crtwinner] + ' votes.
';
else
this.notes = this.notes + cand_name[ crtwinner ]+' wins by having greatest approval.
';
return crtwinner;
}
getblocthrurank( r, btm, cfr ) {
// go through until you've seen all the ranks
let candspassed = 0;
for(let i=0; i= r + 1 ) return i;
}
return btm - 1;
}
testcand( btm, cand, r, cfr, rfc, bsize ) {
// returns a score or -1
let tally = Array( this.nc ).fill(0.0);
let thrurank = Array( this.nb );
for( let i=0; i tally[ this.cand_list[1] ] )
return tally[cand];
return -1.0;
}
}
// IRV, with ER-fractional
class one_winner {
name = 'IRV';
// Fractional treatment
allows_equal_rank = true;
_solve_ {
let winners = [];
let elimd = [];
while( true ) {
let tally = Array( num_cands ).fill(0.0);
for( let b = 0; b < num_blocs; b++ ) {
let vote_for = get_top_prefs_excluding( cand_for_rank[b], elimd );
let vote_for_ct = vote_for.length;
for( let i=0; i ( elimd.indexOf(x) == -1 ) );
// last parameter is false because being first is bad here
sort_by( current_cands, tiebreak, false );
sort_by( current_cands, tally, false );
if( tally[ current_cands[ current_cands.length - 1 ] ] > total_size / 2.0 ) {
return current_cands[ current_cands.length - 1 ];
} else if( current_cands.length == 2 ) {
// tie with final two, but still can only return one
return current_cands[ current_cands.length - 1 ];
} else { // otherwise 3+ cands and no majority, so elim and check for ties
let elimd_cand = current_cands[ 0 ];
elimd.push( elimd_cand );
}
}
}
}
// CdlA
class one_winner {
name = 'CdlA';
allows_equal_rank = true;
_solve_ {
// Instead of generating a consistent tiebreaker for all rounds, we can
// just use tiebreak.
let tieb = tiebreak;
let rndwinner = null;
let rndwinners = [];
let votes = new Array( num_cands );
while( true ) {
votes.fill( 0.0 );
for( let b = 0; b < num_blocs; b++ ) {
let votethru = 0;
if( rndwinners.length > 0 ) {
for( let i = 0; i < rndwinners.length; i++ ) {
if( votethru + 1 < rank_for_cand[b][ rndwinners[i] ] ) {
votethru = rank_for_cand[b][ rndwinners[i] ] - 1;
}
}
}
for( let i = 0; i <= votethru; i++ ) {
for( let j = 0; j < cand_for_rank[b][i].length; j++ ) {
votes[ cand_for_rank[b][i][j] ] += bloc_size[b];
}
}
}
sort_by( cand_list, tieb, true );
sort_by( cand_list, votes, true );
rndwinner = cand_list[0];
if( rndwinners.indexOf( rndwinner ) != -1 ) {
break;
}
rndwinners.push( rndwinner );
}
return rndwinner;
}
}
// MAMPO
class one_winner {
name = 'MAMPO';
allows_equal_rank = true;
_solve_ {
let winners = [];
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, appr, true );
let majappr = [];
for( let i = 0; i < num_cands; i++ ) {
if( appr[i] > total_size / 2.0 ) {
majappr.push( i );
}
}
let nummaj = majappr.length;
if( majappr.length < 2 ) {
return cand_list[0];
}
sort_by( majappr, tiebreak, true );
sort_by( majappr, appr, true );
sort_by( majappr, max_oppos_to, false );
return majappr[0];
}
}
// Adjusted Condorcet Plurality
class one_winner {
name = 'ACP';
_solve_ {
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, first_prefs, true );
let fpw = cand_list[0];
let adjustmat = new_2d_matrix( num_cands );
for( let b = 0; b < num_blocs; b++ ) {
for( let i = 0; i < num_cands; i++ ) {
for( let j = 0; j < num_cands; j++ ) {
let brfc = rank_for_cand[b];
let fpwpreferred = ( brfc[fpw] < brfc[i] && brfc[fpw] < brfc[j] );
if( !fpwpreferred ) {
if( brfc[i] < brfc[j] ) {
adjustmat[i][j] += bloc_size[b];
}
}
}
}
}
let newcw = -1;
for( let i = 0; i < num_cands; i++ ) {
let anynonwins = false;
for( let j = 0; j < num_cands; j++ ) {
if( i != j ) {
if( adjustmat[j][i] >= adjustmat[i][j] ) {
anynonwins = true;
break;
}
}
}
if( !anynonwins ) newcw = i;
}
if( newcw != -1 )
return newcw;
return cand_list[0];
}
}
// King of the Hill (KOTH)
class one_winner {
name = 'KingOfTheHill';
_solve_ {
let curwinner = -1;
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, first_prefs, true );
let fpw = cand_list[0];
for( let i=1; i < num_cands; i++ ) {
if( mat[fpw][ cand_list[i] ] > total_size / 2.0 || mat[ cand_list[i] ][fpw] > total_size / 2.0 ) {
if( mat[fpw][ cand_list[i] ] > total_size / 2.0 )
return fpw;
else
return cand_list[i];
}
}
return fpw;
}
}
// Chain Runoff (or "QR" for Quick Runoff)
class one_winner {
name = 'Chain Runoff';
_solve_ {
let curwinner = -1;
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, first_prefs, true );
curwinner = cand_list[0];
for( let i = 0; i < num_cands - 1; i++ ) {
let higherc = cand_list[i];
let lowerc = cand_list[i+1];
if( mat[lowerc][higherc] <= total_size / 2.0 )
break;
curwinner = lowerc;
}
return curwinner;
}
}
// Cross Max
class one_winner {
name = 'Cross Max';
allows_equal_rank = true;
_solve_ {
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, first_prefs, true );
let fpw = cand_list[0];
// in this array tiebreak is added as a new element to the scores
let scores = [];
for( let i = 0; i < num_cands; i++ )
if( i != fpw )
scores.push( [ i, mat[i][fpw], tiebreak[i] ] );
for( let i = 0; i < num_cands; i++ )
if( i != fpw )
scores.push( [ fpw, mat[fpw][i], tiebreak[fpw] ] );
scores.sort( (x,y) => ( y[2] - x[2] ) );
scores.sort( (x,y) => ( y[1] - x[1] ) );
return scores[0][0];
}
}
// BRBO (Best Response to Best Opposition)
class one_winner {
name = 'BRBO';
allows_equal_rank = true;
_solve_ {
let bestopposing = Array( num_cands );
let bestresponse = Array( num_cands );
for(let i=0; i < num_cands; i++) {
bestopposing[i] = Array();
bestresponse[i] = -100;
let bestval = -100;
for(let j=0; j < num_cands; j++) {
if(i!=j) {
if( mat[j][i] > bestval ) {
bestopposing[i] = [];
bestval = mat[j][i];
}
if( mat[j][i] == bestval ) {
bestopposing[i].push( [ j, mat[j][i], mat[i][j] ] );
}
}
}
for(let k=0; k < bestopposing[i].length; k++) {
if( bestopposing[i][k][2] > bestresponse[i] ) {
bestresponse[i] = bestopposing[i][k][2];
}
}
}
let winscore = -100;
for(let i=0; i < num_cands; i++)
if( bestresponse[ i ] > winscore )
winscore = bestresponse[i];
sort_by( cand_list, tiebreak, true );
// elect first candidate with matching score
for(let i=0; i < num_cands; i++)
if( bestresponse[ cand_list[i] ] == winscore )
return cand_list[i];
}
}
// fpA-max(fpC)
class one_winner {
name = 'fpA-max(fpC)';
_solve_ {
if( cw > -1 ) return [ cw ];
let score = new Array( num_cands );
for(let i=0; i < num_cands; i++) {
let bestval = 0.0;
for(let j=0; j < num_cands; j++)
if( i!=j && mat[j][i] >= mat[i][j] )
if( first_prefs[j] > bestval )
bestval = first_prefs[j];
score[i] = first_prefs[i] - bestval;
}
sort_by( cand_list, tiebreak, true );
sort_by( cand_list, score, true );
return cand_list[0];
}
}
END OF GUIDE