Expose advanced rounding options in count software
This commit is contained in:
parent
a3d79e993a
commit
e219969711
@ -29,12 +29,17 @@
|
||||
tr.info td {
|
||||
background-color: #edededff;
|
||||
}
|
||||
i.sep::after {
|
||||
content: "•";
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<input type="file" id="bltFile">
|
||||
<button onclick="clickCount()">Count</button>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
Preset:
|
||||
<select id="selPreset" onchange="changePreset()">
|
||||
@ -66,10 +71,12 @@
|
||||
<option value="hare_exact">Hare (exact)</option>
|
||||
</select>
|
||||
</label>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
<input type="checkbox" id="chkProgQuota">
|
||||
Progressive quota
|
||||
</label>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
<input type="checkbox" id="chkBulkElection" checked>
|
||||
Bulk election
|
||||
@ -87,10 +94,29 @@
|
||||
<option value="fixed" selected>Fixed</option>
|
||||
</select>
|
||||
</label>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
Decimal places (if Numbers = Fixed):
|
||||
<input type="number" id="txtDP" value="5" style="width: 3em;">
|
||||
</label>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
<input type="checkbox" id="chkRoundVotes">
|
||||
Round votes to
|
||||
</label>
|
||||
<label>
|
||||
<input type="number" id="txtRoundVotes" value="0" style="width: 3em;">
|
||||
d.p.
|
||||
</label>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
<input type="checkbox" id="chkRoundWeights">
|
||||
Round ballot weights to
|
||||
</label>
|
||||
<label>
|
||||
<input type="number" id="txtRoundWeights" value="0" style="width: 3em;">
|
||||
d.p.
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
Surplus order:
|
||||
@ -99,6 +125,7 @@
|
||||
<option value="order">By order</option>
|
||||
</select>
|
||||
</label>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
Method:
|
||||
<select id="selTransfers">
|
||||
@ -131,6 +158,7 @@
|
||||
<option value="prompt">Prompt</option>
|
||||
</select>
|
||||
</label>
|
||||
<i class="sep"></i>
|
||||
<label>
|
||||
Random seed:
|
||||
<input type="text" id="txtSeed" value="">
|
||||
|
@ -35,6 +35,8 @@ function changePreset() {
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('chkRoundVotes').checked = false;
|
||||
document.getElementById('chkRoundWeights').checked = false;
|
||||
document.getElementById('selSurplus').value = 'size';
|
||||
document.getElementById('selTransfers').value = 'wig';
|
||||
document.getElementById('selPapers').value = 'both';
|
||||
@ -47,6 +49,8 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = true;
|
||||
document.getElementById('selNumbers').value = 'rational';
|
||||
document.getElementById('chkRoundVotes').checked = false;
|
||||
document.getElementById('chkRoundWeights').checked = false;
|
||||
document.getElementById('selSurplus').value = 'size';
|
||||
document.getElementById('selTransfers').value = 'wig';
|
||||
document.getElementById('selPapers').value = 'both';
|
||||
@ -59,7 +63,10 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = true;
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '0';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('chkRoundVotes').checked = true;
|
||||
document.getElementById('txtRoundVotes').value = '0';
|
||||
document.getElementById('chkRoundWeights').checked = false;
|
||||
document.getElementById('selSurplus').value = 'order';
|
||||
document.getElementById('selTransfers').value = 'uig';
|
||||
document.getElementById('selPapers').value = 'both';
|
||||
@ -73,6 +80,8 @@ function changePreset() {
|
||||
document.getElementById('chkBulkExclusion').checked = true;
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('chkRoundVotes').checked = false;
|
||||
document.getElementById('chkRoundWeights').checked = false;
|
||||
document.getElementById('selSurplus').value = 'size';
|
||||
document.getElementById('selTransfers').value = 'wright';
|
||||
document.getElementById('selPapers').value = 'both';
|
||||
@ -85,7 +94,10 @@ function changePreset() {
|
||||
document.getElementById('chkBulkElection').checked = true;
|
||||
document.getElementById('chkBulkExclusion').checked = false;
|
||||
document.getElementById('selNumbers').value = 'fixed';
|
||||
document.getElementById('txtDP').value = '0';
|
||||
document.getElementById('txtDP').value = '5';
|
||||
document.getElementById('chkRoundVotes').checked = true;
|
||||
document.getElementById('txtRoundVotes').value = '0';
|
||||
document.getElementById('chkRoundWeights').checked = false;
|
||||
document.getElementById('selSurplus').value = 'order';
|
||||
document.getElementById('selTransfers').value = 'eg';
|
||||
document.getElementById('selPapers').value = 'transferable';
|
||||
@ -318,7 +330,9 @@ async function clickCount() {
|
||||
'surplus_order': document.getElementById('selSurplus').value,
|
||||
'papers': document.getElementById('selPapers').value,
|
||||
'exclusion': document.getElementById('selExclusion').value,
|
||||
'ties': document.getElementById('selTies').value
|
||||
'ties': document.getElementById('selTies').value,
|
||||
'round_votes': document.getElementById('chkRoundVotes').checked ? parseInt(document.getElementById('txtRoundVotes').value) : null,
|
||||
'round_weights': document.getElementById('chkRoundWeights').checked ? parseInt(document.getElementById('txtRoundWeights').value) : null,
|
||||
},
|
||||
'seed': document.getElementById('txtSeed').value,
|
||||
'data': text
|
||||
|
@ -17,7 +17,7 @@
|
||||
__pragma__ = lambda x: None
|
||||
|
||||
from pyRCV2.model import CandidateState, CountCard, CountCompleted, CountStepResult
|
||||
from pyRCV2.numbers import Num, Rational
|
||||
from pyRCV2.numbers import Num
|
||||
from pyRCV2.safedict import SafeDict
|
||||
|
||||
import itertools
|
||||
@ -33,8 +33,6 @@ class BaseSTVCounter:
|
||||
def __init__(self, election, options=None):
|
||||
self.election = election
|
||||
|
||||
self.cls_ballot_value = Num # Need to use Rational in unweighted inclusive Gregory
|
||||
|
||||
# Default options
|
||||
self.options = {
|
||||
'prog_quota': False, # Progressively reducing quota?
|
||||
@ -44,7 +42,9 @@ class BaseSTVCounter:
|
||||
'surplus_order': 'size', # 'size' or 'order'
|
||||
'papers': 'both', # 'both' or 'transferable'
|
||||
'exclusion': 'one_round', # 'one_round', 'parcels_by_order' or 'by_value'
|
||||
'ties': []
|
||||
'ties': [], # List of tie strategies (e.g. TiesRandom)
|
||||
'round_votes': None, # Number of decimal places or None
|
||||
'round_weights': None, # Number of decimal places or None
|
||||
}
|
||||
|
||||
if options is not None:
|
||||
@ -103,12 +103,12 @@ class BaseSTVCounter:
|
||||
if candidate is not None:
|
||||
self.candidates[candidate].transfers += ballot.value
|
||||
if len(self.candidates[candidate].parcels) == 0:
|
||||
self.candidates[candidate].parcels.append([(ballot, self.cls_ballot_value(ballot.value))])
|
||||
self.candidates[candidate].parcels.append([(ballot, Num(ballot.value))])
|
||||
else:
|
||||
self.candidates[candidate].parcels[0].append((ballot, self.cls_ballot_value(ballot.value)))
|
||||
self.candidates[candidate].parcels[0].append((ballot, Num(ballot.value)))
|
||||
else:
|
||||
self.exhausted.transfers += ballot.value
|
||||
#self.exhausted.parcels[0].append((ballot, self.cls_ballot_value(ballot.value)))
|
||||
#self.exhausted.parcels[0].append((ballot, Num(ballot.value)))
|
||||
__pragma__('noopov')
|
||||
|
||||
def step(self):
|
||||
@ -250,39 +250,6 @@ class BaseSTVCounter:
|
||||
"""
|
||||
raise NotImplementedError('Method not implemented')
|
||||
|
||||
def next_preferences(self, parcels):
|
||||
"""
|
||||
Examine the specified parcels and group ballot papers by next available preference
|
||||
"""
|
||||
# SafeDict: Candidate -> [List[Ballot], ballots, votes]
|
||||
next_preferences = SafeDict([(c, [[], Num('0'), Rational('0')]) for c, cc in self.candidates.items()])
|
||||
total_ballots = Num('0')
|
||||
total_votes = Rational('0')
|
||||
|
||||
next_exhausted = []
|
||||
exhausted_ballots = Num('0')
|
||||
exhausted_votes = Rational('0')
|
||||
|
||||
for parcel in parcels:
|
||||
for ballot, ballot_value in parcel:
|
||||
__pragma__('opov')
|
||||
total_ballots += ballot.value
|
||||
total_votes += ballot_value.to_rational()
|
||||
candidate = next((c for c in ballot.preferences if self.candidates[c].state == CandidateState.HOPEFUL), None)
|
||||
|
||||
if candidate is not None:
|
||||
next_preferences[candidate][0].append((ballot, ballot_value))
|
||||
next_preferences[candidate][1] += ballot.value
|
||||
next_preferences[candidate][2] += ballot_value.to_rational()
|
||||
else:
|
||||
next_exhausted.append((ballot, ballot_value))
|
||||
exhausted_ballots += ballot.value
|
||||
exhausted_votes += ballot_value.to_rational()
|
||||
|
||||
__pragma__('noopov')
|
||||
|
||||
return next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes
|
||||
|
||||
def before_exclusion(self):
|
||||
"""
|
||||
Check before excluding a candidate
|
||||
@ -428,6 +395,43 @@ class BaseSTVCounter:
|
||||
|
||||
meets_quota.remove(x)
|
||||
|
||||
# -----------------
|
||||
# UTILITY FUNCTIONS
|
||||
# -----------------
|
||||
|
||||
def next_preferences(self, parcels):
|
||||
"""
|
||||
Examine the specified parcels and group ballot papers by next available preference
|
||||
"""
|
||||
# SafeDict: Candidate -> [List[Ballot], ballots, votes]
|
||||
next_preferences = SafeDict([(c, [[], Num('0'), Num('0')]) for c, cc in self.candidates.items()])
|
||||
total_ballots = Num('0')
|
||||
total_votes = Num('0')
|
||||
|
||||
next_exhausted = []
|
||||
exhausted_ballots = Num('0')
|
||||
exhausted_votes = Num('0')
|
||||
|
||||
for parcel in parcels:
|
||||
for ballot, ballot_value in parcel:
|
||||
__pragma__('opov')
|
||||
total_ballots += ballot.value
|
||||
total_votes += ballot_value
|
||||
candidate = next((c for c in ballot.preferences if self.candidates[c].state == CandidateState.HOPEFUL), None)
|
||||
|
||||
if candidate is not None:
|
||||
next_preferences[candidate][0].append((ballot, ballot_value))
|
||||
next_preferences[candidate][1] += ballot.value
|
||||
next_preferences[candidate][2] += ballot_value
|
||||
else:
|
||||
next_exhausted.append((ballot, ballot_value))
|
||||
exhausted_ballots += ballot.value
|
||||
exhausted_votes += ballot_value
|
||||
|
||||
__pragma__('noopov')
|
||||
|
||||
return next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes
|
||||
|
||||
def choose_lowest(self, l):
|
||||
"""
|
||||
Provided a list of tuples (Candidate, CountCard), sorted in ASCENDING order of votes, choose the tuple with the fewest votes, breaking ties appropriately
|
||||
@ -475,6 +479,16 @@ class BaseSTVCounter:
|
||||
return result
|
||||
|
||||
raise Exception('Unable to resolve tie')
|
||||
|
||||
def round_votes(self, num):
|
||||
if self.options['round_votes'] is None:
|
||||
return num
|
||||
return num.round(self.options['round_votes'], num.ROUND_DOWN)
|
||||
|
||||
def round_weight(self, num):
|
||||
if self.options['round_weights'] is None:
|
||||
return num
|
||||
return num.round(self.options['round_weights'], num.ROUND_DOWN)
|
||||
|
||||
class WIGSTVCounter(BaseSTVCounter):
|
||||
"""
|
||||
@ -486,7 +500,7 @@ class WIGSTVCounter(BaseSTVCounter):
|
||||
|
||||
if self.options['papers'] == 'transferable':
|
||||
__pragma__('opov')
|
||||
transferable_votes = total_votes.to_num() - exhausted_votes.to_num()
|
||||
transferable_votes = total_votes - exhausted_votes
|
||||
__pragma__('noopov')
|
||||
|
||||
for candidate, x in next_preferences.items():
|
||||
@ -503,11 +517,11 @@ class WIGSTVCounter(BaseSTVCounter):
|
||||
__pragma__('opov')
|
||||
if self.options['papers'] == 'transferable':
|
||||
if transferable_votes > surplus:
|
||||
self.candidates[candidate].transfers += (num_votes.to_num() * surplus) / transferable_votes
|
||||
self.candidates[candidate].transfers += self.round_votes((num_votes * surplus) / transferable_votes)
|
||||
else:
|
||||
self.candidates[candidate].transfers += num_votes.to_num() # Do not allow weight to increase
|
||||
self.candidates[candidate].transfers += self.round_votes(num_votes) # Do not allow weight to increase
|
||||
else:
|
||||
self.candidates[candidate].transfers += (num_votes.to_num() * surplus) / total_votes.to_num()
|
||||
self.candidates[candidate].transfers += self.round_votes((num_votes * surplus) / total_votes)
|
||||
__pragma__('noopov')
|
||||
|
||||
for ballot, ballot_value in cand_ballots:
|
||||
@ -518,9 +532,9 @@ class WIGSTVCounter(BaseSTVCounter):
|
||||
else:
|
||||
new_value = ballot_value
|
||||
else:
|
||||
new_value = (ballot_value * surplus) / total_votes.to_num()
|
||||
new_value = (ballot_value * surplus) / total_votes
|
||||
|
||||
new_parcel.append((ballot, new_value))
|
||||
new_parcel.append((ballot, self.round_weight(new_value)))
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
@ -528,9 +542,9 @@ class WIGSTVCounter(BaseSTVCounter):
|
||||
if transferable_votes > surplus:
|
||||
pass # No ballots exhaust
|
||||
else:
|
||||
self.exhausted.transfers += (surplus - transferable_votes)
|
||||
self.exhausted.transfers += self.round_votes((surplus - transferable_votes))
|
||||
else:
|
||||
self.exhausted.transfers += (exhausted_votes.to_num() * surplus) / total_votes.to_num()
|
||||
self.exhausted.transfers += self.round_votes((exhausted_votes * surplus) / total_votes)
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
@ -552,8 +566,10 @@ class WIGSTVCounter(BaseSTVCounter):
|
||||
# Sort the ballots by value
|
||||
ballots = [(b, bv) for p in count_card.parcels for b, bv in p]
|
||||
__pragma__('opov')
|
||||
ballots.sort(key=lambda x: x[1] / x[0].value.to_rational(), reverse=True)
|
||||
count_card.parcels = [list(g) for k, g in itertools.groupby(ballots, lambda x: x[1] / x[0].value.to_rational())]
|
||||
ballots.sort(key=lambda x: x[1] / x[0].value, reverse=True)
|
||||
# Round to 8 decimal places to consider equality
|
||||
# FIXME: Work out a better way of doing this
|
||||
count_card.parcels = [list(g) for k, g in itertools.groupby(ballots, lambda x: (x[1] / x[0].value).round(8, x[1].ROUND_DOWN))]
|
||||
__pragma__('noopov')
|
||||
|
||||
if len(count_card.parcels) > 0:
|
||||
@ -579,7 +595,7 @@ class WIGSTVCounter(BaseSTVCounter):
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
self.candidates[candidate].transfers += num_votes.to_num()
|
||||
self.candidates[candidate].transfers += self.round_votes(num_votes)
|
||||
__pragma__('noopov')
|
||||
|
||||
for ballot, ballot_value in cand_ballots:
|
||||
@ -588,11 +604,11 @@ class WIGSTVCounter(BaseSTVCounter):
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
self.exhausted.transfers += exhausted_votes.to_num()
|
||||
self.exhausted.transfers += self.round_votes(exhausted_votes)
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
count_card.transfers -= total_votes.to_num()
|
||||
count_card.transfers -= total_votes
|
||||
__pragma__('noopov')
|
||||
|
||||
if len(count_card.parcels) == 0:
|
||||
@ -608,22 +624,20 @@ class UIGSTVCounter(WIGSTVCounter):
|
||||
|
||||
def __init__(self, *args):
|
||||
WIGSTVCounter.__init__(self, *args)
|
||||
# Need to use Rational for ballot value internally, as Num may be set to integers only
|
||||
self.cls_ballot_value = lambda x: x.to_rational()
|
||||
|
||||
def do_surplus(self, candidate_surplus, count_card, surplus):
|
||||
next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences(count_card.parcels)
|
||||
|
||||
if self.options['papers'] == 'transferable':
|
||||
__pragma__('opov')
|
||||
transferable_ballots = total_ballots - exhausted_ballots # Num
|
||||
transferable_votes = total_votes - exhausted_votes # Rational
|
||||
transferable_ballots = total_ballots - exhausted_ballots
|
||||
transferable_votes = total_votes - exhausted_votes
|
||||
__pragma__('noopov')
|
||||
|
||||
for candidate, x in next_preferences.items():
|
||||
cand_ballots = x[0]
|
||||
num_ballots = x[1] # Num
|
||||
num_votes = x[2] # Rational
|
||||
num_ballots = x[1]
|
||||
num_votes = x[2]
|
||||
|
||||
new_parcel = []
|
||||
if len(cand_ballots) > 0:
|
||||
@ -633,35 +647,35 @@ class UIGSTVCounter(WIGSTVCounter):
|
||||
|
||||
__pragma__('opov')
|
||||
if self.options['papers'] == 'transferable':
|
||||
if transferable_votes.to_num() > surplus:
|
||||
self.candidates[candidate].transfers += (num_ballots * surplus) / transferable_ballots
|
||||
if transferable_votes > surplus:
|
||||
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / transferable_ballots)
|
||||
else:
|
||||
self.candidates[candidate].transfers += num_votes.to_num()
|
||||
self.candidates[candidate].transfers += self.round_votes(num_votes)
|
||||
else:
|
||||
self.candidates[candidate].transfers += (num_ballots * surplus) / total_ballots
|
||||
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / total_ballots)
|
||||
__pragma__('noopov')
|
||||
|
||||
for ballot, ballot_value in cand_ballots:
|
||||
__pragma__('opov')
|
||||
if self.options['papers'] == 'transferable':
|
||||
if transferable_votes.to_num() > surplus:
|
||||
new_value = (ballot.value * surplus).to_rational() / transferable_ballots.to_rational()
|
||||
if transferable_votes > surplus:
|
||||
new_value = (ballot.value * surplus) / transferable_ballots
|
||||
else:
|
||||
new_value = ballot_value
|
||||
else:
|
||||
new_value = (ballot.value * surplus).to_rational() / total_ballots.to_rational()
|
||||
new_value = (ballot.value * surplus) / total_ballots
|
||||
|
||||
new_parcel.append((ballot, new_value))
|
||||
new_parcel.append((ballot, self.round_weight(new_value)))
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
if self.options['papers'] == 'transferable':
|
||||
if transferable_votes.to_num() > surplus:
|
||||
if transferable_votes > surplus:
|
||||
pass # No ballots exhaust
|
||||
else:
|
||||
self.exhausted.transfers += surplus - transferable_votes.to_num()
|
||||
self.exhausted.transfers += self.round_votes(surplus - transferable_votes)
|
||||
else:
|
||||
self.exhausted.transfers += (exhausted_ballots * surplus) / total_ballots
|
||||
self.exhausted.transfers += self.round_votes((exhausted_ballots * surplus) / total_ballots)
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
@ -684,7 +698,7 @@ class EGSTVCounter(UIGSTVCounter):
|
||||
if self.options['papers'] == 'transferable':
|
||||
__pragma__('opov')
|
||||
transferable_ballots = total_ballots - exhausted_ballots
|
||||
transferable_votes = total_votes.to_num() - exhausted_votes.to_num()
|
||||
transferable_votes = total_votes - exhausted_votes
|
||||
__pragma__('noopov')
|
||||
|
||||
for candidate, x in next_preferences.items():
|
||||
@ -701,11 +715,11 @@ class EGSTVCounter(UIGSTVCounter):
|
||||
__pragma__('opov')
|
||||
if self.options['papers'] == 'transferable':
|
||||
if transferable_votes > surplus:
|
||||
self.candidates[candidate].transfers += (num_ballots * surplus) / transferable_ballots
|
||||
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / transferable_ballots)
|
||||
else:
|
||||
self.candidates[candidate].transfers += num_votes.to_num()
|
||||
self.candidates[candidate].transfers += self.round_votes(num_votes)
|
||||
else:
|
||||
self.candidates[candidate].transfers += (num_ballots * surplus) / total_ballots
|
||||
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / total_ballots)
|
||||
__pragma__('noopov')
|
||||
|
||||
for ballot, ballot_value in cand_ballots:
|
||||
@ -718,7 +732,7 @@ class EGSTVCounter(UIGSTVCounter):
|
||||
else:
|
||||
new_value = (ballot.value * surplus) / total_ballots
|
||||
|
||||
new_parcel.append((ballot, new_value))
|
||||
new_parcel.append((ballot, self.round_weight(new_value)))
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
@ -726,9 +740,9 @@ class EGSTVCounter(UIGSTVCounter):
|
||||
if transferable_votes > surplus:
|
||||
pass # No ballots exhaust
|
||||
else:
|
||||
self.exhausted.transfers += (surplus - transferable_votes)
|
||||
self.exhausted.transfers += self.round_votes((surplus - transferable_votes))
|
||||
else:
|
||||
self.exhausted.transfers += (exhausted_ballots * surplus) / total_ballots
|
||||
self.exhausted.transfers += self.round_votes((exhausted_ballots * surplus) / total_ballots)
|
||||
__pragma__('noopov')
|
||||
|
||||
__pragma__('opov')
|
||||
|
@ -14,6 +14,7 @@
|
||||
# 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/>.
|
||||
|
||||
from pytest import approx
|
||||
from pytest_steps import test_steps
|
||||
|
||||
import pyRCV2.blt
|
||||
@ -36,7 +37,7 @@ candidates = [data[i][0] for i in range(2, len(data) - 2)]
|
||||
def test_aec_tas19():
|
||||
"""Compare count of aec-senate-formalpreferences-24310-TAS.blt.gz with model result at https://results.aec.gov.au/24310/Website/External/SenateStateDop-24310-TAS.pdf"""
|
||||
|
||||
pyRCV2.numbers.set_numclass(pyRCV2.numbers.NativeInt)
|
||||
pyRCV2.numbers.set_numclass(pyRCV2.numbers.Native)
|
||||
|
||||
with gzip.open('tests/data/aec-senate-formalpreferences-24310-TAS.blt.gz', 'rt') as f:
|
||||
election = pyRCV2.blt.readBLT(f.read())
|
||||
@ -45,7 +46,8 @@ def test_aec_tas19():
|
||||
|
||||
counter = UIGSTVCounter(election, {
|
||||
'surplus_order': 'order',
|
||||
'exclusion': 'by_value'
|
||||
'exclusion': 'by_value',
|
||||
'round_votes': 0,
|
||||
})
|
||||
result = counter.reset()
|
||||
|
||||
@ -56,21 +58,21 @@ def test_aec_tas19():
|
||||
result = counter.step()
|
||||
|
||||
comment = data[1][i]
|
||||
assert result.comment == comment, 'Failed to verify stage {} comment'.format(stage)
|
||||
assert result.comment == comment, 'Failed to verify comment'
|
||||
|
||||
for j, cand in enumerate(candidates):
|
||||
votes = pyRCV2.numbers.Num(data[j + 2][i])
|
||||
cc = next(cc for c, cc in result.candidates.items() if c.name == cand)
|
||||
assert cc.votes == votes, 'Failed to verify stage {} candidate "{}" votes, got {} expected {}'.format(stage, cand, cc.votes.pp(0), votes.pp(0))
|
||||
assert cc.votes.impl == approx(votes.impl), 'Failed to verify candidate "{}" votes, got {} expected {}'.format(cand, cc.votes.pp(0), votes.pp(0))
|
||||
|
||||
state = data[j + 2][i + 1] if len(data[j + 2]) > (i + 1) else ''
|
||||
accept = {'': CandidateState.HOPEFUL, 'PEL': CandidateState.PROVISIONALLY_ELECTED, 'EL': CandidateState.ELECTED, 'EX': CandidateState.EXCLUDED, 'EXCLUDING': CandidateState.EXCLUDING}
|
||||
assert cc.state == accept[state], 'Failed to verify stage {} candidate "{}" state'.format(stage, cand)
|
||||
assert cc.state == accept[state], 'Failed to verify candidate "{}" state'.format(cand)
|
||||
|
||||
exhausted = pyRCV2.numbers.Num(data[len(candidates) + 2][i])
|
||||
assert result.exhausted.votes == exhausted, 'Failed to verify stage {} exhausted votes, got {} expected {}'.format(stage, result.exhausted.votes.pp(0), exhausted.pp(0))
|
||||
assert result.exhausted.votes.impl == approx(exhausted.impl), 'Failed to verify exhausted votes, got {} expected {}'.format(result.exhausted.votes.pp(0), exhausted.pp(0))
|
||||
|
||||
loss_fraction = pyRCV2.numbers.Num(data[len(candidates) + 3][i])
|
||||
assert result.loss_fraction.votes == loss_fraction, 'Failed to verify stage {} loss to fraction, got {} expected {}'.format(stage, result.loss_fraction.votes.pp(0), loss_fraction.pp(0))
|
||||
assert result.loss_fraction.votes.impl == approx(loss_fraction.impl), 'Failed to verify loss to fraction, got {} expected {}'.format(result.loss_fraction.votes.pp(0), loss_fraction.pp(0))
|
||||
|
||||
yield 'Stage {}'.format(stage)
|
||||
|
Reference in New Issue
Block a user