Adjust PRSA 1977 implementation
Count in votes to 3 decimal places Round transfer values correctly
This commit is contained in:
parent
6f5cf70abb
commit
6b5c188668
@ -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.
|
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.
|
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 enabled, the transfer value is calculated separately and rounded to the specified precision, before being multiplied by the applicable multiplicand.
|
|
||||||
|
* 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)
|
## Surplus order (-s/--surplus-order)
|
||||||
|
|
||||||
|
@ -112,12 +112,13 @@ function changePreset() {
|
|||||||
document.getElementById('selNumbers').value = 'fixed';
|
document.getElementById('selNumbers').value = 'fixed';
|
||||||
document.getElementById('txtDP').value = '5';
|
document.getElementById('txtDP').value = '5';
|
||||||
document.getElementById('chkRoundQuota').checked = true;
|
document.getElementById('chkRoundQuota').checked = true;
|
||||||
document.getElementById('txtRoundQuota').value = '0';
|
document.getElementById('txtRoundQuota').value = '3';
|
||||||
document.getElementById('chkRoundVotes').checked = true;
|
document.getElementById('chkRoundVotes').checked = true;
|
||||||
document.getElementById('txtRoundVotes').value = '0';
|
document.getElementById('txtRoundVotes').value = '3';
|
||||||
document.getElementById('chkRoundTVs').checked = true;
|
document.getElementById('chkRoundTVs').checked = true;
|
||||||
document.getElementById('txtRoundTVs').value = '0';
|
document.getElementById('txtRoundTVs').value = '3';
|
||||||
document.getElementById('chkRoundWeights').checked = false;
|
document.getElementById('chkRoundWeights').checked = true;
|
||||||
|
document.getElementById('txtRoundWeights').value = '3';
|
||||||
document.getElementById('selSurplus').value = 'order';
|
document.getElementById('selSurplus').value = 'order';
|
||||||
document.getElementById('selTransfers').value = 'eg';
|
document.getElementById('selTransfers').value = 'eg';
|
||||||
document.getElementById('selPapers').value = 'transferable';
|
document.getElementById('selPapers').value = 'transferable';
|
||||||
|
@ -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('--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('--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('--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):
|
def print_step(args, stage, result):
|
||||||
print('{}. {}'.format(stage, result.comment))
|
print('{}. {}'.format(stage, result.comment))
|
||||||
@ -65,23 +66,23 @@ def print_step(args, stage, result):
|
|||||||
elif count_card.state == pyRCV2.model.CandidateState.WITHDRAWN:
|
elif count_card.state == pyRCV2.model.CandidateState.WITHDRAWN:
|
||||||
state = 'Withdrawn'
|
state = 'Withdrawn'
|
||||||
|
|
||||||
ppVotes = count_card.votes.pp(2)
|
ppVotes = count_card.votes.pp(args.pp_decimals)
|
||||||
ppTransfers = count_card.transfers.pp(2)
|
ppTransfers = count_card.transfers.pp(args.pp_decimals)
|
||||||
|
|
||||||
if state is None:
|
if state is None:
|
||||||
print('- {}: {} ({})'.format(candidate.name, ppVotes, ppTransfers))
|
print('- {}: {} ({})'.format(candidate.name, ppVotes, ppTransfers))
|
||||||
else:
|
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('- {}: {} ({}) - {}'.format(candidate.name, ppVotes, ppTransfers, state))
|
||||||
|
|
||||||
print('Exhausted: {} ({})'.format(result.exhausted.votes.pp(2), result.exhausted.transfers.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(2), result.loss_fraction.transfers.pp(2)))
|
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(2)))
|
print('Total votes: {}'.format(result.total.pp(args.pp_decimals)))
|
||||||
|
|
||||||
if args.quota_mode == 'ers97' and result.vote_required_election < result.quota:
|
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:
|
else:
|
||||||
print('Quota: {}'.format(result.quota.pp(2)))
|
print('Quota: {}'.format(result.quota.pp(args.pp_decimals)))
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
@ -1,69 +1,69 @@
|
|||||||
7 4
|
7 4
|
||||||
1000 1 3 0
|
1 1 3 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 2 6 1 7 0
|
1 2 6 1 7 0
|
||||||
1000 2 6 5 3 0
|
1 2 6 5 3 0
|
||||||
1000 3 0
|
1 3 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 3 0
|
1 3 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 3 0
|
1 3 0
|
||||||
1000 5 7 0
|
1 5 7 0
|
||||||
1000 4 1 0
|
1 4 1 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 2 3 0
|
1 2 3 0
|
||||||
1000 3 0
|
1 3 0
|
||||||
1000 7 0
|
1 7 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 2 3 0
|
1 2 3 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 7 0
|
1 7 0
|
||||||
1000 2 6 0
|
1 2 6 0
|
||||||
1000 2 1 0
|
1 2 1 0
|
||||||
1000 7 0
|
1 7 0
|
||||||
1000 2 4 1 3 0
|
1 2 4 1 3 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 2 4 3 7 0
|
1 2 4 3 7 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 2 7 0
|
1 2 7 0
|
||||||
1000 2 7 0
|
1 2 7 0
|
||||||
1000 2 6 1 7 0
|
1 2 6 1 7 0
|
||||||
1000 3 0
|
1 3 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 2 6 0
|
1 2 6 0
|
||||||
1000 2 4 3 7 0
|
1 2 4 3 7 0
|
||||||
1000 2 6 0
|
1 2 6 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 4 0
|
1 4 0
|
||||||
1000 2 6 7 0
|
1 2 6 7 0
|
||||||
1000 2 6 1 0
|
1 2 6 1 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 2 3 0
|
1 2 3 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 2 6 5 3 0
|
1 2 6 5 3 0
|
||||||
1000 2 4 1 7 0
|
1 2 4 1 7 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 2 1 0
|
1 2 1 0
|
||||||
1000 2 4 7 0
|
1 2 4 7 0
|
||||||
1000 2 6 5 1 7 0
|
1 2 6 5 1 7 0
|
||||||
1000 2 7 0
|
1 2 7 0
|
||||||
1000 2 4 3 7 0
|
1 2 4 3 7 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 2 3 0
|
1 2 3 0
|
||||||
1000 2 5 7 0
|
1 2 5 7 0
|
||||||
1000 2 4 5 1 3 0
|
1 2 4 5 1 3 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
1000 6 0
|
1 6 0
|
||||||
0
|
0
|
||||||
"Evans"
|
"Evans"
|
||||||
"Grey"
|
"Grey"
|
||||||
|
@ -20,8 +20,8 @@ from pyRCV2.method.base_stv import EGSTVCounter
|
|||||||
from pyRCV2.model import CandidateState, CountCompleted
|
from pyRCV2.model import CandidateState, CountCompleted
|
||||||
|
|
||||||
def isclose(x, y):
|
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
|
# Misnomer: Require exact match
|
||||||
return abs(int(x.impl) - y) < 100
|
return x == pyRCV2.numbers.Num(y) / pyRCV2.numbers.Num(1000)
|
||||||
|
|
||||||
def test_prsa1():
|
def test_prsa1():
|
||||||
"""Compare count of prsa1.blt with model result at http://www.prsa.org.au/example1.pdf"""
|
"""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',
|
'surplus_order': 'order',
|
||||||
'papers': 'transferable',
|
'papers': 'transferable',
|
||||||
'exclusion': 'parcels_by_order',
|
'exclusion': 'parcels_by_order',
|
||||||
'round_quota': 0,
|
'round_quota': 3,
|
||||||
|
'round_votes': 3,
|
||||||
|
'round_tvs': 3,
|
||||||
|
'round_weights': 3,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Stage 1
|
# Stage 1
|
||||||
result = counter.reset()
|
result = counter.reset()
|
||||||
assert len(result.candidates.impl) == 7
|
assert len(result.candidates.impl) == 7
|
||||||
assert int(result.total.impl) == 65000
|
assert result.total == pyRCV2.numbers.Num('65')
|
||||||
assert int(result.quota.impl) == 13001
|
assert result.quota == pyRCV2.numbers.Num('13.001')
|
||||||
assert result.comment == 'First preferences'
|
assert result.comment == 'First preferences'
|
||||||
assert isclose(result.candidates[c_evans].votes, 1000)
|
assert isclose(result.candidates[c_evans].votes, 1000)
|
||||||
assert isclose(result.candidates[c_grey].votes, 34000)
|
assert isclose(result.candidates[c_grey].votes, 34000)
|
||||||
|
Reference in New Issue
Block a user