UNPKG

ecclesia

Version:

Framework for political and electoral simulations

218 lines (212 loc) 6.59 kB
import { AttributionFailure } from "./chunk-ISZ4VX6L.js"; import { proportionalFromDivisorFunction, proportionalFromRankIndexFunction, rankIndexFunctionFromDivisorFunction, stationaryDivisorFunction } from "./chunk-PFWVE4EX.js"; // src/election/attribution/transform.ts import { Counter as Counter4 } from "@gouvernathor/python/collections"; // src/election/attribution/majorityFactory.ts import { max } from "@gouvernathor/python"; import { Counter } from "@gouvernathor/python/collections"; function plurality({ nSeats }) { const attrib = (votes, rest = {}) => { const win = max(votes.keys(), (p) => votes.get(p)); if (votes.get(win) > 0) { return new Counter([[win, nSeats]]); } throw new AttributionFailure("No party won any vote"); }; attrib.nSeats = nSeats; return attrib; } function superMajority({ nSeats, threshold, contingency = null }) { const attrib = (votes, rest = {}) => { const win = max(votes.keys(), (p) => votes.get(p)); if (votes.get(win) / votes.total > threshold) { return new Counter([[win, nSeats]]); } if (contingency === null) { throw new AttributionFailure("No party reached the threshold"); } return contingency(votes, rest); }; attrib.nSeats = nSeats; return attrib; } // src/election/attribution/orderingFactory.ts import { enumerate, max as max2, min } from "@gouvernathor/python"; import { Counter as Counter2, DefaultMap } from "@gouvernathor/python/collections"; function instantRunoff({ nSeats }) { const attrib = (votes, rest = {}) => { const blacklisted = /* @__PURE__ */ new Set(); const nParties = new Set(votes.flat()).size; for (let pn = 0; pn < nParties; pn++) { const firstPlaces = new Counter2(); for (const ballot of votes) { for (const party of ballot) { if (!blacklisted.has(party)) { firstPlaces.increment(party); break; } } } const total = firstPlaces.total; for (const [party, score] of firstPlaces) { if (score / total > 0.5) { return new Counter2([[party, nSeats]]); } } blacklisted.add(min(firstPlaces.keys(), (p) => firstPlaces.get(p))); } throw new Error("Should not happen"); }; attrib.nSeats = nSeats; return attrib; } function bordaCount({ nSeats }) { const attrib = (votes, rest = {}) => { const scores = new Counter2(); for (const ballot of votes) { for (const [i, party] of enumerate(ballot.slice().reverse(), 1)) { scores.increment(party, i); } } return new Counter2([[max2(scores.keys(), (p) => scores.get(p)), nSeats]]); }; attrib.nSeats = nSeats; return attrib; } function condorcet({ nSeats, contingency = null }) { const attrib = (votes, rest = {}) => { const counts = new DefaultMap(() => new Counter2()); const majority = votes.length / 2; for (const ballot of votes) { for (const [i, party1] of enumerate(ballot)) { for (const party2 of ballot.slice(i + 1)) { counts.get(party1).increment(party2); } } } const win = new Set(counts.keys()); for (const [party, partyCounter] of counts) { for (const value of partyCounter.pos.values()) { if (value > majority) { win.delete(party); break; } } } if (win.size !== 1) { if (win.size !== 0) { throw new Error("Bad attribution"); } if (contingency === null) { throw new condorcet.Standoff("No Condorcet winner"); } return contingency(votes, rest); } const [winner] = win; return new Counter2([[winner, nSeats]]); }; attrib.nSeats = nSeats; return attrib; } condorcet.Standoff = class CondorcetStandoff extends AttributionFailure { }; // src/election/attribution/proportionalFactory.ts import { divmod } from "@gouvernathor/python"; import { Counter as Counter3 } from "@gouvernathor/python/collections"; var divisor1 = stationaryDivisorFunction(1); function jefferson({ nSeats }) { return proportionalFromDivisorFunction({ nSeats, divisorFunction: divisor1 }); } var dHondt = jefferson; var divisorPoint5 = (k) => 2 * k + 1; function webster({ nSeats }) { return proportionalFromDivisorFunction({ nSeats, divisorFunction: divisorPoint5 }); } var sainteLague = webster; function hamilton({ nSeats }) { const attrib = (votes, rest = {}) => { const seats = new Counter3(); const remainders = /* @__PURE__ */ new Map(); const sumVotes = votes.total; for (const [party, scores] of votes) { const [i, r] = divmod(scores * nSeats, sumVotes); seats.set(party, i); remainders.set(party, r); } seats.update([...remainders.keys()].sort((a, b) => remainders.get(b) - remainders.get(a)).slice(0, nSeats - seats.total)); return seats; }; attrib.nSeats = nSeats; return attrib; } var hareLargestRemainders = hamilton; var huntingtonHillBaseRankIndexFunction = rankIndexFunctionFromDivisorFunction((k) => Math.sqrt(k * (k + 1))); var huntingtonHillRankIndexFunction = (t, a) => { if (a <= 0) { return Infinity; } return huntingtonHillBaseRankIndexFunction(t, a); }; function huntingtonHill({ nSeats, threshold, contingency = null }) { const attrib = addThresholdToSimpleAttribution({ threshold, contingency, attribution: proportionalFromRankIndexFunction({ nSeats, rankIndexFunction: huntingtonHillRankIndexFunction }) }); attrib.nSeats = nSeats; return attrib; } var highestAverages = webster; var largestRemainders = hamilton; // src/election/attribution/transform.ts function addThresholdToSimpleAttribution({ threshold, attribution, contingency = attribution }) { const attrib = (votes, rest = {}) => { if (threshold > 0) { const original_votes = votes; const votes_threshold = threshold * votes.total; votes = new Counter4([...votes.entries()].filter(([_, v]) => v >= votes_threshold)); if (votes.size === 0) { if (contingency === null) { throw new AttributionFailure("No party reached the threshold"); } return contingency(original_votes, rest); } } return attribution(votes, rest); }; return attrib; } export { addThresholdToSimpleAttribution, plurality, superMajority, instantRunoff, bordaCount, condorcet, jefferson, dHondt, webster, sainteLague, hamilton, hareLargestRemainders, huntingtonHill, highestAverages, largestRemainders }; //# sourceMappingURL=chunk-ZSNRG47E.js.map