Adjust PRSA 1977 implementation

Count in votes to 3 decimal places
Round transfer values correctly
This commit is contained in:
RunasSudo 2021-01-04 18:10:07 +11:00
parent 6f5cf70abb
commit 6b5c188668
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
5 changed files with 92 additions and 85 deletions

View File

@ -73,10 +73,12 @@ When deferred surpluses is enabled, the transfer of all surpluses is deferred if
When rounding is enabled, the specified values are rounded to the specified number of decimal places. This enables, for example, votes to be counted only in integers, while ballot weights and transfer values are calculated to higher precision (according to the *Numbers*) option.
In relation to *Round transfer values to [n] d.p.* specifically:
The quota is incremented or rounded up (according to the *Quota* option), whereas votes, transfer values and weights are always rounded down.
* When this option is disabled (default), the numerator of the transfer value is multiplied by the applicable multiplicand before being divided by the denominator of the transfer value. For example, in the weighted inclusive Gregory method with non-transferable papers included, to determine the number of votes to transfer to a candidate, the surplus is multiplied by the number of votes for that candidate, before being divided by the total votes of the transferring candidate. This minimises rounding errors.
* When this option is enabled, the transfer value is calculated separately and rounded to the specified precision, before being multiplied by the applicable multiplicand.
In relation to *Round transfer values to [n] d.p.* – note that transfer values are used in STV in calculations of the form *A* × (*B*/*C*), where (*B*/*C*) is the transfer value. The order of operations depends on this setting:
* When this option is disabled (default), (*A* × *B*) is calculated first, then divided by *C*. This minimises rounding errors.
* When this option is enabled, (*B*/*C*) is calculated separately first and rounded to the specified precision, before being multiplied by *A*.
## Surplus order (-s/--surplus-order)

View File

@ -112,12 +112,13 @@ function changePreset() {
document.getElementById('selNumbers').value = 'fixed';
document.getElementById('txtDP').value = '5';
document.getElementById('chkRoundQuota').checked = true;
document.getElementById('txtRoundQuota').value = '0';
document.getElementById('txtRoundQuota').value = '3';
document.getElementById('chkRoundVotes').checked = true;
document.getElementById('txtRoundVotes').value = '0';
document.getElementById('txtRoundVotes').value = '3';
document.getElementById('chkRoundTVs').checked = true;
document.getElementById('txtRoundTVs').value = '0';
document.getElementById('chkRoundWeights').checked = false;
document.getElementById('txtRoundTVs').value = '3';
document.getElementById('chkRoundWeights').checked = true;
document.getElementById('txtRoundWeights').value = '3';
document.getElementById('selSurplus').value = 'order';
document.getElementById('selTransfers').value = 'eg';
document.getElementById('selPapers').value = 'transferable';

View File

@ -48,6 +48,7 @@ def add_parser(subparsers):
parser.add_argument('--random-seed', default=None, help='arbitrary string used to seed the RNG for random tie breaking')
parser.add_argument('--hide-excluded', action='store_true', help='hide excluded candidates from results report')
parser.add_argument('--sort-votes', action='store_true', help='sort candidates by votes in results report')
parser.add_argument('--pp-decimals', type=int, default=2, help='print votes to specified decimal places in results report (default: 2)')
def print_step(args, stage, result):
print('{}. {}'.format(stage, result.comment))
@ -65,23 +66,23 @@ def print_step(args, stage, result):
elif count_card.state == pyRCV2.model.CandidateState.WITHDRAWN:
state = 'Withdrawn'
ppVotes = count_card.votes.pp(2)
ppTransfers = count_card.transfers.pp(2)
ppVotes = count_card.votes.pp(args.pp_decimals)
ppTransfers = count_card.transfers.pp(args.pp_decimals)
if state is None:
print('- {}: {} ({})'.format(candidate.name, ppVotes, ppTransfers))
else:
if not (args.hide_excluded and state == 'Excluded' and (ppVotes == '0.00' or ppVotes == '-0.00') and (ppTransfers == '0.00' or ppTransfers == '-0.00')):
if not (args.hide_excluded and state == 'Excluded' and float(ppVotes) == 0 and float(ppTransfers) == 0):
print('- {}: {} ({}) - {}'.format(candidate.name, ppVotes, ppTransfers, state))
print('Exhausted: {} ({})'.format(result.exhausted.votes.pp(2), result.exhausted.transfers.pp(2)))
print('Loss to fraction: {} ({})'.format(result.loss_fraction.votes.pp(2), result.loss_fraction.transfers.pp(2)))
print('Total votes: {}'.format(result.total.pp(2)))
print('Exhausted: {} ({})'.format(result.exhausted.votes.pp(args.pp_decimals), result.exhausted.transfers.pp(args.pp_decimals)))
print('Loss to fraction: {} ({})'.format(result.loss_fraction.votes.pp(args.pp_decimals), result.loss_fraction.transfers.pp(args.pp_decimals)))
print('Total votes: {}'.format(result.total.pp(args.pp_decimals)))
if args.quota_mode == 'ers97' and result.vote_required_election < result.quota:
print('Vote required for election: {}'.format(result.vote_required_election.pp(2)))
print('Vote required for election: {}'.format(result.vote_required_election.pp(args.pp_decimals)))
else:
print('Quota: {}'.format(result.quota.pp(2)))
print('Quota: {}'.format(result.quota.pp(args.pp_decimals)))
print()

View File

@ -1,69 +1,69 @@
7 4
1000 1 3 0
1000 2 4 7 0
1000 2 6 1 7 0
1000 2 6 5 3 0
1000 3 0
1000 4 0
1000 3 0
1000 4 0
1000 4 0
1000 3 0
1000 5 7 0
1000 4 1 0
1000 6 0
1000 4 0
1000 2 3 0
1000 3 0
1000 7 0
1000 4 0
1000 2 4 7 0
1000 2 4 7 0
1000 4 0
1000 2 3 0
1000 4 0
1000 7 0
1000 2 6 0
1000 2 1 0
1000 7 0
1000 2 4 1 3 0
1000 4 0
1000 6 0
1000 6 0
1000 2 4 3 7 0
1000 6 0
1000 2 7 0
1000 2 7 0
1000 2 6 1 7 0
1000 3 0
1000 2 4 7 0
1000 2 6 0
1000 2 4 3 7 0
1000 2 6 0
1000 2 4 7 0
1000 4 0
1000 2 6 7 0
1000 2 6 1 0
1000 2 4 7 0
1000 6 0
1000 2 4 7 0
1000 2 3 0
1000 6 0
1000 2 6 5 3 0
1000 2 4 1 7 0
1000 6 0
1000 6 0
1000 2 1 0
1000 2 4 7 0
1000 2 6 5 1 7 0
1000 2 7 0
1000 2 4 3 7 0
1000 6 0
1000 2 3 0
1000 2 5 7 0
1000 2 4 5 1 3 0
1000 6 0
1000 6 0
1 1 3 0
1 2 4 7 0
1 2 6 1 7 0
1 2 6 5 3 0
1 3 0
1 4 0
1 3 0
1 4 0
1 4 0
1 3 0
1 5 7 0
1 4 1 0
1 6 0
1 4 0
1 2 3 0
1 3 0
1 7 0
1 4 0
1 2 4 7 0
1 2 4 7 0
1 4 0
1 2 3 0
1 4 0
1 7 0
1 2 6 0
1 2 1 0
1 7 0
1 2 4 1 3 0
1 4 0
1 6 0
1 6 0
1 2 4 3 7 0
1 6 0
1 2 7 0
1 2 7 0
1 2 6 1 7 0
1 3 0
1 2 4 7 0
1 2 6 0
1 2 4 3 7 0
1 2 6 0
1 2 4 7 0
1 4 0
1 2 6 7 0
1 2 6 1 0
1 2 4 7 0
1 6 0
1 2 4 7 0
1 2 3 0
1 6 0
1 2 6 5 3 0
1 2 4 1 7 0
1 6 0
1 6 0
1 2 1 0
1 2 4 7 0
1 2 6 5 1 7 0
1 2 7 0
1 2 4 3 7 0
1 6 0
1 2 3 0
1 2 5 7 0
1 2 4 5 1 3 0
1 6 0
1 6 0
0
"Evans"
"Grey"

View File

@ -20,8 +20,8 @@ from pyRCV2.method.base_stv import EGSTVCounter
from pyRCV2.model import CandidateState, CountCompleted
def isclose(x, y):
# There will be some discrepancy due to how the PRSA count rounds transfer values, so accept up to 0.1 votes difference
return abs(int(x.impl) - y) < 100
# Misnomer: Require exact match
return x == pyRCV2.numbers.Num(y) / pyRCV2.numbers.Num(1000)
def test_prsa1():
"""Compare count of prsa1.blt with model result at http://www.prsa.org.au/example1.pdf"""
@ -46,14 +46,17 @@ def test_prsa1():
'surplus_order': 'order',
'papers': 'transferable',
'exclusion': 'parcels_by_order',
'round_quota': 0,
'round_quota': 3,
'round_votes': 3,
'round_tvs': 3,
'round_weights': 3,
})
# Stage 1
result = counter.reset()
assert len(result.candidates.impl) == 7
assert int(result.total.impl) == 65000
assert int(result.quota.impl) == 13001
assert result.total == pyRCV2.numbers.Num('65')
assert result.quota == pyRCV2.numbers.Num('13.001')
assert result.comment == 'First preferences'
assert isclose(result.candidates[c_evans].votes, 1000)
assert isclose(result.candidates[c_grey].votes, 34000)