This repository has been archived on 2021-05-25. You can view files and clone it, but cannot push or open issues or pull requests.
pyRCV2/html/worker.js

163 lines
5.2 KiB
JavaScript

/*
pyRCV2: Preferential vote counting
Copyright © 2020 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
importScripts('vendor/BigInt_BigRat-a5f89e2.min.js', 'vendor/big-6.0.0.min.js', 'vendor/sjcl-1.0.8.min.js', 'bundle.js');
let stage, result, counter, ppDPs, tiesPrompt;
onmessage = function(evt) {
if (evt.data.type === 'init') {
// Set settings
if (evt.data.data.numbers === 'native') {
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Native);
}
if (evt.data.data.numbers === 'rational') {
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Rational);
}
if (evt.data.data.numbers === 'fixed') {
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Fixed);
py.pyRCV2.numbers.set_dps(evt.data.data.fixedDPs);
}
if (evt.data.data.numbers === 'gfixed') {
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.FixedGuarded);
py.pyRCV2.numbers.set_dps(evt.data.data.fixedDPs);
}
ppDPs = evt.data.data.ppDPs;
let election = py.pyRCV2.blt.readBLT(evt.data.data.data);
let total_ballots = py.pyRCV2.numbers.Num(0);
for (let b of election.ballots) {
total_ballots.__iadd__(b.value);
}
// Create counter
if (evt.data.data.transfers === 'uig') {
counter = py.pyRCV2.method.gregory.UIGSTVCounter(election, evt.data.data.options);
} else if (evt.data.data.transfers === 'eg') {
counter = py.pyRCV2.method.gregory.EGSTVCounter(election, evt.data.data.options);
} else if (evt.data.data.transfers === 'meek') {
counter = py.pyRCV2.method.meek.MeekSTVCounter(election, evt.data.data.options);
} else {
counter = py.pyRCV2.method.gregory.WIGSTVCounter(election, evt.data.data.options);
}
if (evt.data.data.options['ties'] === 'backwards_random') {
counter.options['ties'] = [py.pyRCV2.ties.TiesBackwards(counter), py.pyRCV2.ties.TiesRandom(evt.data.data.seed)];
} else if (evt.data.data.options['ties'] === 'forwards_random') {
counter.options['ties'] = [py.pyRCV2.ties.TiesForwards(counter), py.pyRCV2.ties.TiesRandom(evt.data.data.seed)];
} else if (evt.data.data.options['ties'] === 'random') {
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.data.seed)];
} else if (evt.data.data.options['ties'] === 'prompt') {
tiesPrompt = py.pyRCV2.ties.TiesPrompt();
counter.options['ties'] = [tiesPrompt];
}
postMessage({
'type': 'init',
'election': {
'name': election.py_name,
'candidates': election.candidates.map(c => c.py_name),
'seats': election.seats,
},
'total_ballots': total_ballots.pp(0),
'options': counter.describe_options(),
});
// Reset
stage = 1;
try {
result = counter.reset();
} catch (ex) {
handleException(ex);
return;
}
if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
postMessage({'type': 'done', 'result': resultToJS(result)});
return;
} else {
postMessage({'type': 'result', 'result': resultToJS(result)});
}
stepElection();
} else if (evt.data.type === 'require_input') {
// Data received from the user
tiesPrompt.buffer = evt.data.input;
stepElection();
}
}
function stepElection() {
while (true) {
try {
result = counter.step();
} catch (ex) {
handleException(ex);
return;
}
stage += 1;
if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
postMessage({'type': 'done', 'result': resultToJS(result)});
break;
} else {
postMessage({'type': 'result', 'result': resultToJS(result)});
}
}
}
function handleException(ex) {
if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) {
// Signals we require input to break a tie
postMessage({'type': 'require_input', 'message': ex.message});
} else if (py.isinstance(ex, py.pyRCV2.method.base_stv.STVException)) {
console.error(ex);
postMessage({'type': 'stv_exception', 'message': ex.message});
} else {
console.error(ex);
throw ex;
}
}
function resultToJS(result) {
return {
'stage': stage,
'comment': result.comment,
'logs': result.logs,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
'transfers': cc.transfers.pp(ppDPs),
'votes': cc.votes.pp(ppDPs),
'state': cc.state,
'order_elected': cc.order_elected,
}]),
'exhausted': {
'transfers': result.exhausted.transfers.pp(ppDPs),
'votes': result.exhausted.votes.pp(ppDPs)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(ppDPs),
'votes': result.loss_fraction.votes.pp(ppDPs)
},
'total': result.total.pp(ppDPs),
'quota': result.quota.pp(ppDPs),
'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDPs),
};
}