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