Skip to content
This repository has been archived by the owner on Nov 11, 2024. It is now read-only.

Commit

Permalink
Add scripts to help with determining results
Browse files Browse the repository at this point in the history
The random generate script is useful to test OpaVote/CIVS in advance

Please forgive me for using bash ':)
  • Loading branch information
infinisil committed Nov 3, 2024
1 parent 75796ae commit c9b64d5
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 0 deletions.
76 changes: 76 additions & 0 deletions nix/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash

# Generates a randomised OpaVote ballot file (https://opavote.com/help/overview#blt-file-format) to stdout

export BC_ENV_ARGS="-lq"

# Every voter has a normal-distributed preference X

# 25 voters and 10 candidates is the maximum for the free tier
voterCount=450
# 9 so that we have candidates -2, -1.5, ..., 0, ..., 1.5, 2, instead of messy numbers
candidateCount=23
seatCount=7
# With 40%, a minority group should get a representative
minorityPercentage=15


# Voters are represented with a single number. They vote for the candidate nearest to their number
voterValues=()
pi=$(echo "scale=10; 4*a(1)" | bc -l)
normal() {
bc <<< "$1 * sqrt(-2 * l($(( RANDOM )) / 32767 )) * c(2 * $pi * $(( RANDOM )) / 32767) + $2"
}

for i in $(seq "$voterCount"); do
voterValues+=( "$(normal 2 0)" )
# Check proportional representation works
#if (( 100 * i / voterCount <= minorityPercentage )); then
# # The minority group votes with a wide standard deviation around candidate (-3)
# # such that no single candidate gets more 1st ranks than the majority group.
# # We want to test that this group still get a representative seat regardless
# voterValues+=( "$(normal 2 -3)" )
#else
# # The majority group all votes effectively for the same candidate (3) with only minor differences,
# # which ensures that a number of candidates that could in theory fill all seats get most 1st and 2nd ranks,
# # but we don't want that to happen because of proportional representation
# voterValues+=( "$(normal 0.5 3)" )
#fi
done

# Sort the voters by their value number and therefore also ballots
readarray -t voterValues < <(printf "%s\n" "${voterValues[@]}" | sort -n)
declare -p voterValues >&2

# Candidates are uniformly distributed from -4 to +4, somewhat matching what would happen in reality,
# where people want to have a representative to vote for, and if they don't, they'd nominate themselves.
candidates=()
for (( i=1; i <= candidateCount; i++ )); do
candidates[i]=$(bc <<< "scale=1; ($i - 1) * 8 / ($candidateCount - 1) - 4")
done
declare -p candidates >&2

# Start generating the file

echo "$candidateCount $seatCount"

for value in "${voterValues[@]}"; do
#echo "New voter: $value"
readarray -t rankedCandidates < <(
for candidate in "${!candidates[@]}"; do
# Calculate the distance between the voters value and the candidates
echo "$candidate $(bc <<< "($value - ${candidates[$candidate]}) ^ 2")"
done | sort -n -k2 | cut -d' ' -f1)

#declare -p rankedCandidates
weight=1
end=0
echo "$weight" "${rankedCandidates[@]}" "$end"
done
echo 0

for candidate in "${candidates[@]}"; do
echo "\"$candidate\""
done

echo "\"Test Election\""
62 changes: 62 additions & 0 deletions nix/process.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash

# Converts an OpaVote BLT file (https://opavote.com/help/overview#blt-file-format) from stdin
# to a CIVS ballot file (https://github.com/andrewcmyers/civs/blob/867d2c68b0259cac6e0269515b56edb1c49a8003/cgi-bin/languages/base_language.pm#L326-L344) to stdout

# Summary of the CIVS format:
# Each line contains the ranks of the N choices. Ranks are numbers from 1 to N
# 2 4 3 1 5 A simple ballot ranking five choices. The top-ranked choice is the fourth one.

# However, entirely undocumented right now, for the best-candidate (the opposite of combined weights/ratings) voter criteria, which we're using, the rankings are inverted!
# https://github.com/andrewcmyers/civs/blob/867d2c68b0259cac6e0269515b56edb1c49a8003/cgi-bin/vote#L176-L178
# https://github.com/andrewcmyers/civs/blob/867d2c68b0259cac6e0269515b56edb1c49a8003/cgi-bin/results#L468-L475

set -euo pipefail

read -r candidateCount _seatCount;
echo "Candidate count: $candidateCount" >&2
while read -ra ballot; do
if [[ "${#ballot[@]}" == 1 && "${ballot[0]}" == "0" ]]; then
echo "Read all ballots" >&2
break
else
weight=${ballot[0]}
unset 'ballot[0]'
end=${ballot[-1]}
unset 'ballot[-1]'
if [[ "$weight" != 1 || "$end" != 0 ]]; then
echo "Problematic ballot: First number (weight) is $weight (should be 1), last number (end marker) is $end (should be 0)" >&2
exit 1
fi

declare -a candidateRanks=()
for candidate in $(seq "${candidateCount}"); do
candidateRanks[candidate]=$candidateCount
done

# Inverts the ranking, see top of file!
rank=$candidateCount
for candidate in "${ballot[@]}"; do
candidateRanks[candidate]=$(( rank-- ))
done

echo "${ballot[@]}" "->" "${candidateRanks[@]}" >&2
echo "${candidateRanks[@]}"
fi
done

echo "Candidates (fill this into the candidate field):" >&2
for candidate in $(seq "${candidateCount}"); do
read -r name
name=${name%\"}
name=${name#\"}
if [[ "$name" == "0" ]]; then
# For some reason CIVS doesn't store candidates named "0"
echo "0.0" >&2
else
echo "$name" >&2
fi
done

read -r title
echo "Title: $title" >&2

0 comments on commit c9b64d5

Please sign in to comment.