I posted an essay (No Abacus or Pillow) detailing how the manual counting of ballots can rival the speed of the automated counting of ballots … provided those ballots are sorted. In the essay’s appendix is a listing of a short Julia script demonstration. I appreciate the speed and clarity of Julia and, perhaps even more important, Julia has an excellent reputation among tinkerers. Tinkering seems to be the proper mindset to implement much needed innovations in vote counting software.
For example, to counter false claims about fraudulent votes getting entered in “big dumps” in the middle of the night or false claims about thousands of unregistered voters (some dead) voting, perhaps there can be a formal approach to clearly document the expected/actual timing of vote counting events and the expected/actual turnout of registered voters … something like double entry bookkeeping but for voting.
# in response to a cheeky question by Rachel Maddow in
# a news story about a locality's new voting regulation
# requiring all their voting ballots be counted manually:
# if they're not allowed to use automated ballot counting
# machines, what will they use? An abacus and a My Pillow?
#
# This nap.jl code is just a prototype to demonstrate
# that the sorting of ballots and the counting of duplicate
# ballots is faster than counting each ballot individually.
# Some tinkering with this prototype code would be needed
# to implement voting options such as
# 1. rank order voting,
# 2. random ordering of choices,
# 3. write-in ballots,
# 4. a double-entry accounting system for ballots,
# 5. ballot images taken with smartphone camera instead of
# flatbed scanner.
#
# A ballot configuration text file specifies the largest
# possible value for each ballot item. For example, the
# following ballot.txt configuration file specifies that
# item 1's max value is 3, item 2's max value is 2, and
# item 3's max value is 4.
#=
3
2
4
=#
#
using DelimitedFiles
using Printf
# index VS to index D conversion
# arg iVS: row Index into Vote Sorted matrix
# arg VS: Vote Sorted matrix
# arg BVI: Ballot configuration Vector Incremented
function ivs2id(
iVS::Int64,
VS::Matrix{Int8},
BVI::Vector{Int8})
nItem = length(BVI)
iD = 1
nStep = 1
for iItem=nItem:-1:1
iD += VS[iVS,iItem]*nStep
nStep *= BVI[iItem]
end
return iD
end # ivs2id()
# index D to count matrix conversion
# arg iD: index into Duplicate count matrix
# arg D: Duplicate count matrix
# arg BVI: Ballot configuration Vector Incremented
# arg C: ballot Count matrix
function id2c(
iD::Int64,
D::Matrix{Int64},
BVI::Vector{Int8},
C::Matrix{Int64})
nItem = length(BVI)
nDup = D[iD]
iD -= 1
for iItem = nItem:-1:1
nStep = BVI[iItem]
C[mod(iD,nStep)+1,iItem] += nDup
iD = div(iD,nStep)
end
end # id2c()
# prototype of No Abacus or Pillow
# arg nVote: # of vote ballots generated and counted
# arg verbosity: level of debugging verbosity
# 0: minimal number of printouts
# 1: maximal number of printouts
# arg fn: filename of ballot.txt configuration file
function nap(
nVote::Int64=20,
verbosity::Int64=1,
fn::String="ballot.txt")
# read ballot configuration text file
B = readdlm(fn, Int8) # Ballot matrix
print("B is "); display(B)
# generate random votes
nItem = size(B,1)
V = rand(0:B[1],nVote) # Vote matrix
for iItem = 2:nItem
V = hcat(V, rand(0:B[iItem],nVote))
end
V = Int8.(V)
if verbosity > 0
print("V is "); display(V)
else
println("nVote: $nVote")
end
# sort votes
VS = sortslices(V,dims=1) # Vote matrix Sorted
if verbosity > 0
print("VS is "); display(VS)
end
# count duplicates
BVI = B[:,1] .+ Int8(1) # Ballot Vector Incremented
nD = prod(BVI)
D = zeros(Int64,1,nD) # Dup matrix
iVSU = 1 # index of first (i.e., Unique) vote
# in set of duplicate votes
iD = ivs2id(iVSU,VS,BVI)
D[iD] += 1
for iVS = 2:nVote
if VS[iVSU,:] == VS[iVS,:]
D[iD] += 1
else
iVSU = iVS
iD = ivs2id(iVSU,VS,BVI)
D[iD] += 1
end
end
print("D is "); display(D)
# check for ballot counting mistakes
sumD = sum(D)
if sumD != nVote
str = @sprintf(
"ERROR: sum of duplicate vector (%d) should equal vote count (%d)",
sumD, nVote)
println(str)
end
C = zeros(Int64, maximum(BVI), nItem)
for iVote = 1:nVote
for iItem = 1:nItem
C[V[iVote,iItem]+1,iItem] += 1
end
end
print("C is "); display(C)
CS = sum(C, dims=1)
for iItem = 1:nItem
if CS[iItem] != nVote
str = @sprintf(
"ERROR: C col %d does not sum to nVote",
iItem)
println(str)
end
end
C2 = zeros(Int64, maximum(BVI), nItem)
for iD = 1:nD
if D[iD] > 0
id2c(iD,D,BVI,C2)
end
end
if C != C2
print("ERROR: count matrices should match; C2 is "); display(C2)
end
end # nap()
julia> include("nap.jl")
nap (generic function with 4 methods)
julia> @time nap(50000,0)
B is 3×1 Matrix{Int8}:
3
2
4
nVote: 50000
D is 1×60 Matrix{Int64}:
805 852 833 804 858 857 831 844 851 866 863 852 … 807 835 809 801 817 806 829 807 834 850 815
C is 5×3 Matrix{Int64}:
12684 16723 9956
12501 16580 9867
12475 16697 10000
12340 0 10056
0 0 10121
0.136220 seconds (1.14 M allocations: 32.175 MiB, 21.53% gc time, 4.84% compilation time)
julia>