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

144 lines
4.9 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, ppDP, 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);
}
ppDP = evt.data.data.fixedDPs > 2 ? 2 : evt.data.data.fixedDPs;
let election = py.pyRCV2.blt.readBLT(evt.data.data.data);
postMessage({'type': 'init', 'election': {
'candidates': election.candidates.map(c => c.py_name)
}});
// Create counter
if (evt.data.data.transfers === 'uig') {
counter = py.pyRCV2.method.base_stv.UIGSTVCounter(election, evt.data.data.options);
} else if (evt.data.data.transfers === 'eg') {
counter = py.pyRCV2.method.base_stv.EGSTVCounter(election, evt.data.data.options);
} else {
counter = py.pyRCV2.method.base_stv.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];
}
// Reset
stage = 1;
result = counter.reset();
postMessage({'type': 'result', 'result': {
'stage': stage,
'comment': result.comment,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
'transfers': cc.transfers.pp(ppDP),
'votes': cc.votes.pp(ppDP),
'state': cc.state
}]),
'exhausted': {
'transfers': result.exhausted.transfers.pp(ppDP),
'votes': result.exhausted.votes.pp(ppDP)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(ppDP),
'votes': result.loss_fraction.votes.pp(ppDP)
},
'total': result.total.pp(ppDP),
'quota': result.quota.pp(ppDP),
'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDP),
}});
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();
stage += 1;
} catch (ex) {
if (py.isinstance(ex, py.pyRCV2.ties.RequireInput)) {
// Signals we require input to break a tie
postMessage({'type': 'require_input', 'message': ex.message});
break;
} else if (py.isinstance(ex, py.pyRCV2.method.base_stv.STVException)) {
console.error(ex);
postMessage({'type': 'stv_exception', 'message': ex.message});
break;
} else {
console.error(ex);
throw ex;
}
}
if (py.isinstance(result, py.pyRCV2.model.CountCompleted)) {
postMessage({'type': 'done'});
break;
} else {
postMessage({'type': 'result', 'result': {
'stage': stage,
'comment': result.comment,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
'transfers': cc.transfers.pp(ppDP),
'votes': cc.votes.pp(ppDP),
'state': cc.state
}]),
'exhausted': {
'transfers': result.exhausted.transfers.pp(ppDP),
'votes': result.exhausted.votes.pp(ppDP)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(ppDP),
'votes': result.loss_fraction.votes.pp(ppDP)
},
'total': result.total.pp(ppDP),
'quota': result.quota.pp(ppDP),
'vote_required_election': result.vote_required_election === null ? null : result.vote_required_election.pp(ppDP),
}});
}
}
}