ecclesia
Version:
Framework for political and electoral simulations
218 lines (212 loc) • 6.59 kB
JavaScript
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