Implement integer number method

This commit is contained in:
RunasSudo 2020-10-18 03:48:00 +11:00
parent 41251e11f6
commit 907c296337
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
8 changed files with 153 additions and 13 deletions

View File

@ -29,6 +29,9 @@ class STVCCounter:
def reset(self):
self.candidates = SafeDict([(c, CountCard()) for c in self.election.candidates])
self.exhausted = CountCard()
self.loss_fraction = CountCard()
self.total_orig = sum((b.value for b in self.election.ballots), Num('0'))
# Withdraw candidates
for candidate in self.election.withdrawn:
@ -57,7 +60,8 @@ class STVCCounter:
'First preferences',
self.candidates,
self.exhausted,
self.total + self.exhausted.votes,
self.loss_fraction,
self.total + self.exhausted.votes + self.loss_fraction.votes,
self.quota
)
__pragma__('noopov')
@ -67,6 +71,7 @@ class STVCCounter:
for candidate, count_card in self.candidates.items():
count_card.step()
self.exhausted.step()
self.loss_fraction.step()
# Have sufficient candidates been elected?
if sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.PROVISIONALLY_ELECTED or cc.state == CandidateState.ELECTED) >= self.election.seats:
@ -84,7 +89,8 @@ class STVCCounter:
'Bulk election',
self.candidates,
self.exhausted,
self.total + self.exhausted.votes,
self.loss_fraction,
self.total + self.exhausted.votes + self.loss_fraction.votes,
self.quota
)
__pragma__('noopov')
@ -132,7 +138,8 @@ class STVCCounter:
'Surplus of ' + candidate_surplus.name,
self.candidates,
self.exhausted,
self.total + self.exhausted.votes,
self.loss_fraction,
self.total + self.exhausted.votes + self.loss_fraction.votes,
self.quota
)
__pragma__('noopov')
@ -171,7 +178,8 @@ class STVCCounter:
'Exclusion of ' + candidate_excluded.name,
self.candidates,
self.exhausted,
self.total + self.exhausted.votes,
self.loss_fraction,
self.total + self.exhausted.votes + self.loss_fraction.votes,
self.quota
)
__pragma__('noopov')
@ -180,6 +188,7 @@ class STVCCounter:
# Compute quota
__pragma__('opov')
self.total = sum((cc.votes for c, cc in self.candidates.items()), Num('0'))
self.loss_fraction.transfers += (self.total_orig - self.total - self.exhausted.votes) - self.loss_fraction.votes
self.quota = self.total / Num(self.election.seats + 1)
__pragma__('noopov')

View File

@ -77,11 +77,12 @@ class CountCompleted:
pass
class CountStepResult:
def __init__(self, comment, candidates, exhausted, total, quota):
def __init__(self, comment, candidates, exhausted, loss_fraction, total, quota):
self.comment = comment
self.candidates = candidates # SafeDict: Candidate -> CountCard
self.exhausted = exhausted # CountCard
self.loss_fraction = loss_fraction # CountCard
self.total = total
self.quota = quota

View File

@ -23,11 +23,13 @@ __pragma__('noskip')
if is_py:
__pragma__('skip')
from pyRCV2.numbers.fixed_py import Fixed, set_dps
from pyRCV2.numbers.int_py import NativeInt
from pyRCV2.numbers.native_py import Native
from pyRCV2.numbers.rational_py import Rational
__pragma__('noskip')
else:
from pyRCV2.numbers.fixed_js import Fixed, set_dps
from pyRCV2.numbers.int_js import NativeInt
from pyRCV2.numbers.native_js import Native
from pyRCV2.numbers.rational_js import Rational

View File

@ -35,13 +35,13 @@ class Fixed:
return format(self.impl, '.{}f'.format(dp))
def __add__(self, other):
return Fixed((self.impl + other.impl).quantize(_quantize_exp))
return Fixed(self.impl + other.impl)
def __sub__(self, other):
return Fixed((self.impl - other.impl).quantize(_quantize_exp))
return Fixed(self.impl - other.impl)
def __mul__(self, other):
return Fixed((self.impl * other.impl).quantize(_quantize_exp))
return Fixed(self.impl * other.impl)
def __div__(self, other):
return Fixed((self.impl / other.impl).quantize(_quantize_exp))
return Fixed(self.impl / other.impl)
def __gt__(self, other):
return self.impl > other.impl

45
pyRCV2/numbers/int_js.py Normal file
View File

@ -0,0 +1,45 @@
# 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/>.
class NativeInt:
"""
Wrapper for JS integers
"""
def __init__(self, val):
self.impl = Math.floor(parseFloat(val))
def pp(self, dp):
"""Pretty print to specified number of decimal places"""
return self.impl.toFixed(0)
def __add__(self, other):
return NativeInt(self.impl + other.impl)
def __sub__(self, other):
return NativeInt(self.impl - other.impl)
def __mul__(self, other):
return NativeInt(self.impl * other.impl)
def __div__(self, other):
return NativeInt(self.impl / other.impl)
def __gt__(self, other):
return self.impl > other.impl
def __ge__(self, other):
return self.impl >= other.impl
def __lt__(self, other):
return self.impl < other.impl
def __le__(self, other):
return self.impl <= other.impl

45
pyRCV2/numbers/int_py.py Normal file
View File

@ -0,0 +1,45 @@
# 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/>.
class NativeInt:
"""
Wrapper for Python int
"""
def __init__(self, val):
self.impl = int(val)
def pp(self, dp):
"""Pretty print to specified number of decimal places"""
return str(self.impl)
def __add__(self, other):
return NativeInt(self.impl + other.impl)
def __sub__(self, other):
return NativeInt(self.impl - other.impl)
def __mul__(self, other):
return NativeInt(self.impl * other.impl)
def __div__(self, other):
return NativeInt(self.impl / other.impl)
def __gt__(self, other):
return self.impl > other.impl
def __ge__(self, other):
return self.impl >= other.impl
def __lt__(self, other):
return self.impl < other.impl
def __le__(self, other):
return self.impl <= other.impl

View File

@ -33,7 +33,7 @@
<body>
<input type="file" id="bltFile">
<button onclick="clickBtn();">OK</button>
<select id="numbers"><option value="native" selected>Native</option><option value="rational">Rational</option><option value="fixed">Fixed (6 d.p.)</option></select>
<select id="numbers"><option value="native" selected>Native Float</option><option value="int">Native Integer</option><option value="rational">Rational</option><option value="fixed">Fixed (6 d.p.)</option></select>
<table id="result"></table>
@ -54,7 +54,7 @@
// Step election
let worker = new Worker('worker.js');
let election, elComment, elExhausted1, elExhausted2, elQuota;
let election, elComment, elExhausted1, elExhausted2, elLTF1, elLTF2, elTotal, elQuota;
worker.onmessage = function(evt) {
if (evt.data.type == 'init') {
@ -98,12 +98,26 @@
tblResults.appendChild(elExhausted1);
tblResults.appendChild(elExhausted2);
// Loss to fraction row
elLTF1 = document.createElement('tr');
elLTF1.classList.add('info');
elLTF2 = document.createElement('tr');
elLTF2.classList.add('info');
elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2');
elTd.style.borderTop = '1px solid black';
elTd.innerText = 'Loss to fraction';
elLTF1.appendChild(elTd);
tblResults.appendChild(elLTF1);
tblResults.appendChild(elLTF2);
// Total row
elTotal = document.createElement('tr');
elTotal.classList.add('info');
elTd = document.createElement('td');
elTd.style.borderTop = '1px solid black';
elTd.style.borderBottom = '1px solid black';
elTd.innerText = 'Total';
elTotal.appendChild(elTd);
tblResults.appendChild(elTotal);
@ -177,11 +191,24 @@
elTd.innerText = result.exhausted.votes;
elExhausted2.appendChild(elTd);
// Display loss to fraction
elTd = document.createElement('td');
elTd.classList.add('count');
elTd.style.borderTop = '1px solid black';
if (result.loss_fraction.transfers != '0.00') {
elTd.innerText = result.loss_fraction.transfers;
}
elLTF1.appendChild(elTd);
elTd = document.createElement('td');
elTd.classList.add('count');
elTd.innerText = result.loss_fraction.votes;
elLTF2.appendChild(elTd);
// Display total
elTd = document.createElement('td');
elTd.classList.add('count');
elTd.style.borderTop = '1px solid black';
elTd.style.borderBottom = '1px solid black';
elTd.innerText = result.total;
elTotal.appendChild(elTd);

View File

@ -8,6 +8,9 @@ onmessage = async function(evt) {
if (evt.data.numbers == 'native') {
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Native);
}
if (evt.data.numbers == 'int') {
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.NativeInt);
}
if (evt.data.numbers == 'rational') {
py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.Rational);
}
@ -37,6 +40,10 @@ onmessage = async function(evt) {
'transfers': result.exhausted.transfers.pp(2),
'votes': result.exhausted.votes.pp(2)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(2),
'votes': result.loss_fraction.votes.pp(2)
},
'total': result.total.pp(2),
'quota': result.quota.pp(2)
}});
@ -60,6 +67,10 @@ onmessage = async function(evt) {
'transfers': result.exhausted.transfers.pp(2),
'votes': result.exhausted.votes.pp(2)
},
'loss_fraction': {
'transfers': result.loss_fraction.transfers.pp(2),
'votes': result.loss_fraction.votes.pp(2)
},
'total': result.total.pp(2),
'quota': result.quota.pp(2)
}});