ecclesia
Version:
Framework for political and electoral simulations
1 lines • 11.3 kB
Source Map (JSON)
{"version":3,"sources":["../../../src/election/attribution/proportionalBase.ts","../../../src/election/attribution/metrics.ts"],"sourcesContent":["import { Counter } from \"@gouvernathor/python/collections\";\nimport { type Simple } from \"../ballots\";\nimport { type Attribution, type HasNSeats } from \"../attribution\";\nimport { defaultMetric, type DisproportionMetric } from \"./metrics\";\n\n/**\n * An attribution method that allocates seats proportionally\n * to the number of votes received by each party.\n */\nexport interface Proportional<Party> extends Attribution<Party, Simple<Party>> {};\n\n// TODO: doesn't it always implement HasNSeats ?\n/**\n * An specific kind of proportional attribution method,\n * based on a rank-index function, and which\n */\nexport interface RankIndexMethod<Party> extends Proportional<Party> {};\n\n/**\n * A function that should be pure : it should not take into account\n * any value other than the arguments passed to it.\n *\n * @param t The fraction of votes received by a candidate\n * @param a The number of seats already allocated to that candidate\n * @returns A value that should be increasing as t rises, and decreasing as a rises.\n * The higher the return value, the more likely the candidate is to receive another seat.\n */\nexport interface RankIndexFunction {\n (t: number, a: number): number;\n};\n\n/**\n * A function creating a fixed-seats, proportional, rank-index attribution method\n * from a rank-index function.\n *\n * The implementation is optimized so as to call rankIndexFunction as few times as possible.\n *\n * Replaces the RankIndexMethod class implementation.\n */\nexport function proportionalFromRankIndexFunction<Party>(\n { nSeats, rankIndexFunction }: {\n nSeats: number,\n rankIndexFunction: RankIndexFunction,\n }\n): RankIndexMethod<Party> & HasNSeats {\n const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n const allVotes = votes.total;\n const fractions = new Map([...votes.entries()].map(([party, v]) => [party, v / allVotes]));\n\n const rankIndexValues = new Map([...fractions.entries()].map(([party, f]) => [party, rankIndexFunction(f, 0)]));\n\n // the parties, sorted by increasing rankIndex value\n const parties = [...votes.keys()].sort((a, b) => rankIndexValues.get(a)! - rankIndexValues.get(b)!);\n\n const seats = new Counter<Party>();\n\n s: for (let sn = 0; sn < nSeats; sn++) {\n // take the most deserving party\n const winner = parties.pop()!;\n // give it a seat\n seats.increment(winner);\n // update the rankIndex value of the party\n rankIndexValues.set(winner, rankIndexFunction(fractions.get(winner)!, seats.get(winner)!));\n\n // insert it in its (new) place in the sorted list of parties\n for (let pn = 0; pn < parties.length; pn++) {\n if (rankIndexValues.get(parties[pn])! >= rankIndexValues.get(winner)!) {\n parties.splice(pn, 0, winner);\n continue s;\n }\n }\n parties.push(winner);\n }\n\n return seats;\n };\n attrib.nSeats = nSeats;\n return attrib;\n}\n\n/**\n * Creates a rank-index (proportional) attribution method in which\n * the total number of seats is NOT fixed.\n * Instead, it takes a metric of disproportionality\n * between the entitlements (the votes) and the numbers of seats,\n * and a range of valid number of seats,\n * and the attribution method will return the allocation of seats\n * that minimizes the metric, while respecting the range of valid number of seats.\n *\n * The attribution will only return a 0-seats attribution when the maxNSeats is 0,\n * otherwise, a minNSeats value of 0 will be treated as 1.\n *\n * The metric has a reasonable default.\n *\n * The implementation is still optimized so as to call rankIndexFunction as few times as possible.\n *\n * @param minNSeats The minimum number of seats to be allocated, inclusive.\n * @param maxNSeats The maximum number of seats to be allocated, inclusive.\n */\nexport function boundedRankIndexMethod<Party>(\n { minNSeats, maxNSeats, rankIndexFunction, metric = defaultMetric }: {\n minNSeats: number,\n maxNSeats: number,\n rankIndexFunction: RankIndexFunction,\n metric?: DisproportionMetric<Party>,\n }\n): RankIndexMethod<Party> {\n const attrib = (votes: Simple<Party>, rest = {}): Counter<Party> => {\n const allVotes = votes.total;\n const fractions = new Map([...votes.entries()].map(([party, v]) => [party, v / allVotes]));\n\n const rankIndexValues = new Map([...fractions.entries()].map(([party, f]) => [party, rankIndexFunction(f, 0)]));\n\n // the parties, sorted by increasing rankIndex value\n const parties = [...votes.keys()].sort((a, b) => rankIndexValues.get(a)! - rankIndexValues.get(b)!);\n\n const seats = new Counter<Party>();\n\n let bestSeats = seats.pos;\n // technically, most metrics give 0 for a 0-seats attribution\n // but we have to patch that out otherwise the 0-seats attribution will always be returned\n let bestSeatsMetric = Infinity;\n\n s: for (let sn = 1; sn <= maxNSeats; sn++) {\n // take the most deserving party\n const winner = parties.pop()!;\n // give it a seat\n seats.increment(winner);\n\n if (sn >= minNSeats) {\n // compute the metric of the new attribution\n const newMetric = metric({ votes, seats });\n if (newMetric < bestSeatsMetric) { // when above the min, favor fewer seats\n bestSeats = seats.pos;\n bestSeatsMetric = newMetric;\n }\n }\n\n // update the rankIndex value of the party\n rankIndexValues.set(winner, rankIndexFunction(fractions.get(winner)!, seats.get(winner)!));\n\n // insert it in its (new) place in the sorted list of parties\n for (let pn = 0; pn < parties.length; pn++) {\n if (rankIndexValues.get(parties[pn])! >= rankIndexValues.get(winner)!) {\n parties.splice(pn, 0, winner);\n continue s;\n }\n }\n parties.push(winner);\n }\n\n return bestSeats;\n }\n attrib.minNSeats = minNSeats;\n attrib.maxNSeats = maxNSeats;\n return attrib;\n}\n\nexport interface DivisorMethod<Party> extends RankIndexMethod<Party> {}\n\n/**\n * A function that should be pure.\n * @param k The number of seats already allocated to a party\n * @returns A value that should be increasing as k rises\n */\nexport interface DivisorFunction {\n (k: number): number;\n}\n\nexport function stationaryDivisorFunction(r: number): DivisorFunction {\n return (k: number) => k + r;\n}\n\nexport function rankIndexFunctionFromDivisorFunction(\n divisorFunction: DivisorFunction\n): RankIndexFunction {\n return (t, a) => t / divisorFunction(a);\n}\n\n/**\n * A function creating a divisor method -\n * one kind of rank-index attribution, itself a kind of proportional attribution.\n */\nexport function proportionalFromDivisorFunction<Party>(\n { nSeats, divisorFunction }: {\n nSeats: number,\n divisorFunction: DivisorFunction,\n }\n): DivisorMethod<Party> & HasNSeats {\n return proportionalFromRankIndexFunction({\n nSeats,\n rankIndexFunction: rankIndexFunctionFromDivisorFunction(divisorFunction),\n });\n}\n","import { type Counter } from \"@gouvernathor/python/collections\";\nimport { type Simple } from \"../ballots\";\n\nexport interface DisproportionMetric<Party> {\n (p0: {votes: Simple<Party>, seats: Counter<Party>}): number;\n}\n\n/**\n * Returns the mean value (across all candidates) of the absolute difference\n * between the theoretical, (f)ra(c)tional number of seats and the allocated number of seats.\n * The bigger, the less proportional.\n *\n * Compared to a sum or mean of absolute differences of percentage,\n * this\n */\nexport function defaultMetric<Party>(\n { votes, seats }: {\n votes: Simple<Party>,\n seats: Counter<Party>,\n }\n): number {\n const allVotes = votes.total;\n const allSeats = seats.total;\n let suum = 0;\n for (const party of seats.keys()) {\n const partyVotes = votes.get(party)!;\n const partySeats = seats.get(party)!;\n suum += Math.abs(allSeats * partyVotes / allVotes - partySeats);\n }\n return suum / seats.size;\n}\n"],"mappings":"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","names":[]}