fork download
  1. require "rational"
  2.  
  3. # Set up the classes required to swallow the raw data for presentation to the
  4. # rest of the program.
  5.  
  6. class ExtendedObject
  7. # Put any methods here that I want to use just about all over the place.
  8. def initialize
  9. super
  10. yield self if block_given?
  11. end
  12. end
  13. class Person < ExtendedObject
  14. attr_accessor :name
  15. def inspect
  16. if name
  17. "{#{class_word} #{name}}"
  18. else
  19. "{a #{class_word}}"
  20. end
  21. end
  22. end
  23. class Voter < Person
  24. attr_accessor :ranks_by_candidate, :scores_by_candidate, :weight
  25. class Initializer
  26. attr_accessor :target
  27. class CandidateInitializer
  28. attr_accessor :voter, :candidate
  29. def rank a_rank
  30. voter.ranks_by_candidate[candidate] = a_rank
  31. end
  32. def score a_score
  33. voter.scores_by_candidate[candidate] = a_score
  34. end
  35. end
  36. def weight shall_be
  37. target.weight = shall_be
  38. end
  39. def candidate name, &y
  40. # Accept data initialization concerning the voter's attitude to the named candidate.
  41. target.scores_by_candidate ||= Hash.new
  42. target.ranks_by_candidate ||= Hash.new
  43. ci = CandidateInitializer.new
  44. ci.voter = target
  45. ci.candidate = $example.candidate name
  46. y.call ci
  47. true
  48. end
  49. end # Initilizer
  50. def initializer
  51. # Answer with an object that can initialize me from data declarations.
  52. iobj = Initializer.new
  53. iobj.target = self
  54. self.weight = 1 # can be changed
  55. iobj
  56. end
  57. def class_word
  58. "voter"
  59. end
  60. end # Voter
  61. class Candidate < Person
  62. def class_word
  63. "candidate"
  64. end
  65. end
  66.  
  67. $example ||= Object.new
  68. class << $example
  69. attr_accessor :data_initializer_thingy, :voters_by_name, :candidates_by_name
  70. def inspect
  71. "$example"
  72. end
  73. def data
  74. # Accept a data declaration specified by the block.
  75. yield data_initializer_thingy
  76. true
  77. end
  78. def voter name
  79. # Return an object to represent the voter whose name is given.
  80. voters_by_name[name] ||= Voter.new {|v| v.name = name}
  81. end
  82. def candidate name
  83. # Return an object to represent the candidate whose name is given.
  84. candidates_by_name[name] ||= Candidate.new {|c| c.name = name}
  85. end
  86. def voters
  87. voters_by_name.values
  88. end
  89. def candidates
  90. candidates_by_name.values
  91. end
  92. end # class << $example
  93. $example.candidates_by_name ||= Hash.new
  94. $example.voters_by_name ||= Hash.new
  95. $example.data_initializer_thingy = Object.new
  96. class << $example.data_initializer_thingy
  97. def inspect
  98. "{the data initializer thingy}"
  99. end
  100. def voter voter_name, &y
  101. # Accept a data declaration concerning the voter whose name is given by
  102. # voter_name. Consult the block given for further details about the data to be associated
  103. # to the voter.
  104. y.call(($example.voter voter_name).initializer)
  105. end
  106. end
  107.  
  108.  
  109.  
  110. ################################################################################
  111. # #
  112. # D A T A #
  113. # #
  114. ################################################################################
  115.  
  116. # OK, the raw data are here. You can clone this code and stubstitute your own
  117. # example here.
  118.  
  119. $example.data do |d|
  120. d.voter "Ossipoff" do |v|
  121. v.weight 1
  122. v.candidate "fine Score" do |c|
  123. c.score 1
  124. end
  125. v.candidate "coarse Score" do |c|
  126. c.score Rational(75, 100)
  127. end
  128. v.candidate "Approval" do |c|
  129. c.score Rational(74, 100)
  130. end
  131. v.candidate "ER-Bucklin (delay)" do |c|
  132. c.score Rational(74, 100)
  133. end
  134. v.candidate "ER-Bucklin (no delay)" do |c|
  135. c.score Rational(73, 100)
  136. end
  137. v.candidate "Majority Judgment" do |c|
  138. c.score Rational(1, 100)
  139. end
  140. end
  141. d.voter "Jennings" do |v|
  142. v.candidate("coarse Score") {|c|c.score 1}
  143. v.candidate("fine Score") {|c|c.score 1}
  144. v.candidate("Majority Judgment") {|c|c.score 1}
  145. v.candidate("Approval") {|c|c.score 1}
  146. v.candidate("Condorcet") {|c|c.score Rational(75, 100)}
  147. v.candidate("Instant Runoff") {|c|c.score Rational(35, 100)}
  148. end
  149. d.voter "Quinn" do |v|
  150. v.candidate "ER-Bucklin (delay)" do |c|
  151. c.score Rational(93, 100)
  152. end
  153. v.candidate "ER-Bucklin (no delay)" do |c|
  154. c.score Rational(93, 100)
  155. end
  156. v.candidate("Ranked Pairs") {|c|c.score Rational(91, 100)} # Condorcet.
  157. v.candidate("Improved Condorcet Top") {|c|c.score Rational(91, 100)} # Condorcet.
  158. v.candidate("ERABW") {|c|c.score Rational(91, 100)} # Condorcet?
  159. v.candidate("other Condorcet") {|c|c.score Rational(90, 100)}
  160. v.candidate("coarse Score") {|c|c.score Rational(8, 10)}
  161. v.candidate("fine Score") {|c|c.score Rational(94, 100)}
  162. v.candidate("Runoff") {|c|c.score Rational(4, 10)}
  163. v.candidate("Majority Judgment") {|c|c.score 1}
  164. v.candidate("Continuous Majority Judgment") {|c|c.score 1}
  165. v.candidate("SODA") {|c|c.score 1}
  166. v.candidate("Approval") {|c|c.score Rational(94, 100)}
  167. v.candidate("Condorcet") {|c|c.score Rational(90, 100)}
  168. v.candidate("Instant Runoff") {|c|c.score Rational(20, 100)}
  169. v.candidate("Score (selected alternative values)") {|c|c.score Rational(99, 100)}
  170. end
  171. d.voter "Gilson" do |v|
  172. v.weight 1
  173. v.candidate "coarse Score" do |c|
  174. c.score 1
  175. end
  176. v.candidate "fine Score" do |c|
  177. c.score Rational(75, 100)
  178. end
  179. v.candidate "Condorcet" do |c|
  180. c.score Rational(75, 100)
  181. end
  182. v.candidate "Majority Judgment" do |c|
  183. # c.rank 2 Not trying to rank these.
  184. c.score Rational(90, 100)
  185. end
  186. v.candidate "Bucklin" do |c|
  187. # c.rank 2 Not trying to rank these.
  188. c.score Rational(90, 100)
  189. end
  190. v.candidate "Plurality" do |c|
  191. # c.rank 2 Not trying to rank these.
  192. c.score Rational(10, 100)
  193. end
  194. end
  195. d.voter "Shentrup" do |v|
  196. v.weight 1
  197. v.candidate "coarse Score" do |c|
  198. c.score 1
  199. end
  200. v.candidate "fine Score" do |c|
  201. c.score Rational(100, 100)
  202. end
  203. v.candidate "Approval" do |c|
  204. c.score Rational(90, 100)
  205. end
  206. v.candidate "Bucklin" do |c|
  207. c.score Rational(75, 100)
  208. end
  209. v.candidate "Majority Judgment" do |c|
  210. # c.rank 2 Not trying to rank these.
  211. c.score Rational(70, 100)
  212. end
  213. v.candidate "Condorcet" do |c|
  214. # c.rank 2 Not trying to rank these.
  215. c.score Rational(67, 100)
  216. end
  217. v.candidate "Instant Runoff" do |c|
  218. # c.rank 2 Not trying to rank these.
  219. c.score Rational(30, 100)
  220. end
  221. end
  222. d.voter "Le'vesque" do |v|
  223. v.weight 1
  224. v.candidate "coarse Score" do |c|
  225. c.score 1
  226. end
  227. v.candidate "fine Score" do |c|
  228. c.score Rational(100, 100)
  229. end
  230. v.candidate "Approval" do |c|
  231. c.score Rational(85, 100)
  232. end
  233. v.candidate "Bucklin" do |c|
  234. c.score Rational(70, 100)
  235. end
  236. v.candidate "Majority Judgment" do |c|
  237. # c.rank 2 Not trying to rank these.
  238. c.score Rational(75, 100)
  239. end
  240. v.candidate "Instant Runoff" do |c|
  241. # c.rank 2 Not trying to rank these.
  242. c.score Rational(30, 100)
  243. end
  244. end
  245. end
  246.  
  247.  
  248.  
  249. ################################################################################
  250. # #
  251. # R A N G E #
  252. # #
  253. ################################################################################
  254.  
  255.  
  256. # Score or range voting.
  257.  
  258. class ScoreElection < ExtendedObject
  259. attr_accessor :max_score # What is the maximum score that a voter is allowed to assign to a
  260. # candidate in this election?
  261. # Code all over the place assumes the minimum score is zero.
  262. def initialize
  263. @max_score = Rational(1, 1)
  264. super
  265. end
  266. def voters
  267. $example.voters
  268. end
  269. def candidates
  270. $example.candidates
  271. end
  272. def ballots
  273. @ballots ||= voters.map do | a_voter |
  274. ScoreBallot.new do |it|
  275. it.election = self
  276. it.voter = a_voter
  277. end
  278. end
  279. end
  280. def first_round
  281. # Answer with my first round.
  282. @first_round ||= ScoreRound.new do |it|
  283. it.real_ballots = ballots
  284. it.candidates = candidates
  285. it.ordinal = 1 # for display
  286. end
  287. end
  288. end
  289.  
  290.  
  291. class << $example
  292. # This is a re-open.
  293.  
  294. attr_accessor :score_election
  295.  
  296. def voters
  297. voters_by_name.values
  298. end
  299. def candidates
  300. candidates_by_name.values
  301. end
  302. end
  303.  
  304. $example.score_election = ScoreElection.new
  305.  
  306.  
  307. class ScoreBallot < ExtendedObject
  308. attr_writer :weight
  309. attr_accessor :voter, :election
  310. attr_writer :scores_by_candidate
  311.  
  312. def weight
  313. @weight ||= voter.weight.to_r
  314. end
  315. def inspect
  316. if voter
  317. "{ballot of #{voter.inspect} wt:#{weight} #{scores_by_candidate.size} scores}"
  318. else
  319. "{a score ballot}"
  320. end
  321. end
  322.  
  323. def max_score
  324. election.max_score
  325. end
  326.  
  327. def scores_by_candidate
  328. @scores_by_candidate ||= lambda do
  329. r = Hash.new
  330. voter.scores_by_candidate.each do | cand, score |
  331. r[cand] = score.to_r
  332. end
  333. r
  334. end.call
  335. end
  336.  
  337. def weighted_score_for_candidate a_candidate
  338. hit = scores_by_candidate[a_candidate]
  339. if hit
  340. hit * weight
  341. else
  342. nil
  343. end
  344. end
  345.  
  346. def []=(k, v)
  347. scores_by_candidate[k] = v
  348. end
  349.  
  350. def deweighted_with_winners some_winners
  351. # Answer with a ballot similar to myself but with weight determined by
  352. # some_winners as canddidates who have already won in prior rounds of the
  353. # multi-winner election.
  354. sum = some_winners.inject(0.to_r) do | acc, a_winner |
  355. acc + ((weighted_score_for_candidate a_winner) || 0.to_r)
  356. end
  357. new_weight = Rational(1, 2) * voter.weight / (Rational(1, 2) + sum.to_r / max_score)
  358. if new_weight == weight
  359. self
  360. else
  361. n = self.class.allocate
  362. n.scores_by_candidate = scores_by_candidate
  363. n.voter = voter
  364. n.election = election
  365. n.weight = new_weight
  366. n
  367. end
  368. end
  369. end
  370.  
  371. class FakeBallot < ExtendedObject
  372. # There exist versions of score election code that do not use this class.
  373. attr_accessor :weight
  374. def weighted_score_for_candidate any_candidate
  375. 0.to_r
  376. end
  377. def deweighted_with_winners some_winners
  378. self
  379. end
  380. end
  381.  
  382. class RoundCandidateTally
  383. # The purpose of a round candidate tally is to calculate the total score that
  384. # a candidate receives in a round.
  385.  
  386. attr_accessor :candidate, :round
  387.  
  388. def score
  389. @score ||= lambda do
  390. acc = Rational(0)
  391. base = Rational(0)
  392. round.ballots.each do | a_ballot |
  393. hit = a_ballot.weighted_score_for_candidate candidate
  394. if hit
  395. acc += hit
  396. # base += a_ballot.weight
  397. end
  398. base += a_ballot.weight # even if candidate not mentioned on ballot.
  399. end
  400. acc / base
  401. end.call
  402. end
  403.  
  404. def inspect
  405. if @candidate
  406. "{#{score.to_f} #{candidate.name}}"
  407. else
  408. "{an uninitialized tally object}"
  409. end
  410. end
  411. end
  412.  
  413. class ScoreRound < ExtendedObject
  414. # An instance represents a round of tallying for a reweighted range (score) multi-winner
  415. # election.
  416.  
  417. attr_accessor :ballots, :candidates, :ordinal
  418. attr_writer :prior_winners
  419.  
  420. attr_writer :winners
  421. # Can be set from outside to express decision between ties for first place.
  422. # Expressed as tallies, not candidates (historical reasons).
  423. # Probably should have just one element.
  424.  
  425. def real_ballots=(them)
  426. # Receive them as the real ballots on which to base my work.
  427. # Internally, our ballots could also include artificial ballots for
  428. # the "better quorum" scheme http://r...content-available-to-author-only...g.org/BetterQuorum.html
  429. # but that feature is commented out.
  430. # fake = FakeBallot.new {|it| it.weight = [them.size, 1000].min.to_r}
  431. self.ballots = them # + [fake]
  432. end
  433.  
  434. def prior_winners
  435. # What candidates already won on prior rounds?
  436. @prior_winners ||= []
  437. end
  438.  
  439. def follow prior_round
  440. # Be the round that follows prior_round.
  441. self.ordinal = prior_round.ordinal + 1
  442. self.prior_winners = prior_round.prior_winners + prior_round.winners.map(&:candidate)
  443. self.ballots = prior_round.ballots.map {|e|e.deweighted_with_winners prior_winners}
  444. self.candidates = prior_round.candidates - prior_winners
  445. true
  446. end
  447.  
  448. def inspect
  449. if ordinal
  450. "{round #{ordinal}}"
  451. else
  452. "{a round}"
  453. end
  454. end
  455.  
  456. def tallies
  457. @tallies ||= lambda do
  458. candidates.map do | a_candidate |
  459. n = RoundCandidateTally.new
  460. n.round = self
  461. n.candidate = a_candidate
  462. n
  463. end
  464. end.call
  465. end
  466.  
  467. def ordered_tallies
  468. tallies.sort_by {|t| - t.score}
  469. end
  470.  
  471. def winners
  472. @winners ||= leaders # by default; can be set otherwise.
  473. end
  474.  
  475. def leaders
  476. # Answer with the tallies of the candidates in first place by score.
  477.  
  478. @leaders ||= lambda do
  479. ordered_tallies = self.ordered_tallies
  480. acc = []
  481. unless ordered_tallies.empty?
  482. cur = 1
  483. lim = ordered_tallies.size
  484. top_score = ordered_tallies.first.score
  485. acc = [ordered_tallies.first]
  486. while cur < lim && (hit = ordered_tallies[cur]).score == top_score
  487. acc += [hit]
  488. cur += 1
  489. end # while
  490. end # unless
  491. acc
  492. end.call
  493. end # def
  494.  
  495. def successor
  496. r = self.class.new
  497. r.follow self
  498. r
  499. end
  500. def next_round
  501. successor
  502. end
  503. def next # Can do this with a keyword? Yes, but be careful with implied "self".
  504. successor
  505. end
  506. end # class
  507.  
  508.  
  509.  
  510. ################################################################################
  511. # #
  512. # O G L E #
  513. # #
  514. ################################################################################
  515.  
  516. # Rank election by my best understanding of the method of James Ogle despite being
  517. # somewhat confused by his communications about it.
  518.  
  519. # An Ogle election only has one round. However, to make the code parallel the
  520. # code for a score election, I will objectify the round.
  521.  
  522. class OgleElection < ExtendedObject
  523. def voters
  524. $example.voters
  525. end
  526. def candidates
  527. $example.candidates
  528. end
  529. def ballots
  530. @ballots ||= voters.map do | a_voter |
  531. OgleBallot.new do |it|
  532. it.election = self
  533. it.voter = a_voter
  534. end
  535. end
  536. end
  537. def first_round
  538. # Answer with my first round.
  539. @first_round ||= OgleRound.new do |it|
  540. it.real_ballots = ballots
  541. it.candidates = candidates
  542. it.ordinal = 1 # for display
  543. end
  544. end
  545. end
  546.  
  547. class << $example
  548. attr_accessor :rank_election
  549. end
  550. $example.rank_election = OgleElection.new
  551.  
  552. class OgleTally
  553. # An instance denotes the tally of a candidate across the ballots.
  554.  
  555. attr_accessor :candidate, :round
  556.  
  557. def high_order_part
  558. do_tally unless @high_order_part
  559. @high_order_part
  560. end
  561. def low_order_part
  562. do_tally unless @low_order_part
  563. @low_order_part
  564. end
  565.  
  566. def inspect
  567. if @candidate && @round
  568. "{(#{high_order_part}, #{low_order_part}) #{candidate.name}}"
  569. else
  570. "{a tally}"
  571. end
  572. end
  573.  
  574. def <=> another
  575. if high_order_part < another.high_order_part
  576. 1
  577. elsif high_order_part > another.high_order_part
  578. -1
  579. elsif low_order_part < another.low_order_part
  580. 1
  581. elsif low_order_part > another.low_order_part
  582. -1
  583. else
  584. 0
  585. end
  586. end
  587.  
  588. def do_tally
  589. # Private.
  590. high = 0
  591. low = 0
  592. round.ballots.each do | a_ballot |
  593. hit = a_ballot.rank_for_candidate candidate
  594. if hit
  595. high += a_ballot.weight # tic count.
  596. low -= hit * a_ballot.weight # sum rank numbers negated.
  597. end
  598. end
  599. @high_order_part = high
  600. @low_order_part = low
  601. true
  602. end
  603. end
  604.  
  605. class OgleRound < ExtendedObject
  606. attr_accessor :ballots, :candidates, :ordinal
  607. attr_writer :prior_winners, :winners
  608.  
  609. def real_ballots=(them)
  610. # Receive them as the real ballots on which to base my work.
  611. self.ballots = them
  612. end
  613.  
  614. def prior_winners
  615. # What candidates already won on prior rounds?
  616. @prior_winners ||= []
  617. end
  618.  
  619. def follow prior_round
  620. # Be the round that follows prior_round.
  621. self.ordinal = prior_round.ordinal + 1
  622. self.prior_winners = prior_round.prior_winners + prior_round.winners.map(&:candidate)
  623. self.ballots = prior_round.ballots.map {|e|e.deweighted_with_winners prior_winners}
  624. self.candidates = prior_round.candidates - prior_winners
  625. true
  626. end
  627.  
  628. def inspect
  629. if ordinal
  630. "{round #{ordinal}}"
  631. else
  632. "{a round}"
  633. end
  634. end
  635.  
  636. def tallies
  637. @tallies ||= lambda do
  638. candidates.map do | a_candidate |
  639. n = OgleTally.new
  640. n.round = self
  641. n.candidate = a_candidate
  642. n
  643. end
  644. end.call
  645. end
  646.  
  647. def ordered_tallies
  648. tallies.sort
  649. end
  650.  
  651. def winners
  652. @winners ||= leaders
  653. end
  654.  
  655. def leaders
  656. # Answer with the tallies of the candidates in first place by Ogle pair.
  657. throw "Don't ask."
  658. end # def
  659.  
  660. def successor
  661. r = self.class.new
  662. r.follow self
  663. r
  664. end
  665. def next_round
  666. successor
  667. end
  668. def next # Can do this with a keyword? Yes, but be careful with implied "self".
  669. successor
  670. end
  671. end
  672.  
  673. class OgleBallot < ExtendedObject
  674. attr_accessor :voter, :election
  675. attr_writer :ranks_by_candidate
  676. attr_writer :weight # for made-up examples where a ballot represents many.
  677.  
  678. def inspect
  679. if voter
  680. "{ballot of #{voter.inspect} #{ranks_by_candidate.size} ranks}"
  681. else
  682. "{a score ballot}"
  683. end
  684. end
  685.  
  686. def rank_for_candidate a_candidate
  687. ranks_by_candidate[a_candidate]
  688. end
  689. def weight
  690. @weight ||= voter.weight || 1
  691. end
  692.  
  693. def ranks_by_candidate
  694. @ranks_by_candidate ||= lambda do
  695. r = voter.ranks_by_candidate
  696. # Just do a sanity check.
  697. candidates_by_rank = Array.new r.size
  698. r.each {|k, v| candidates_by_rank[v - 1] = k}
  699. candidates_by_rank.each do |e|
  700. e || (throw "Bad rank ballot #{voter.inspect}.")
  701. end
  702. r
  703. end.call
  704. end
  705. end
  706.  
  707.  
  708.  
  709.  
  710. ################################################################################
  711. # #
  712. # B U C K L I N #
  713. # #
  714. ################################################################################
  715.  
  716.  
  717. # What's it take to run a Bucklin election?
  718.  
  719. class BucklinElection < ExtendedObject
  720. def electorate_size
  721. @electorate_size ||= ballot_models.inject(0){| a, e | a + e.instance_count}
  722. end
  723. def half_electorate_size
  724. @half_electorate_size ||= electorate_size * Rational(1, 2)
  725. end
  726. def finds_majority_in_score a_score
  727. a_score > half_electorate_size
  728. end
  729. def candidates
  730. $example.candidates
  731. end
  732. def assess_ballot_models
  733. # Initialize myself according to what I find in the example ballot_models.
  734. self.approval_grade_count = [ $example.voters.map do |b|
  735. b.ranks_by_candidate.values.max
  736. end.max, 3 ].max
  737. true
  738. end
  739. def approval_grade_count= the_count
  740. # Initialize myself to tally the election based on the given count of
  741. # Bucklin grades or ranks indicating approval.
  742. @approval_grade_count = the_count
  743. end
  744. def approval_grade_count
  745. @approval_grade_count or assess_ballot_models
  746. @approval_grade_count
  747. end
  748. def grade_from_one_based_rank_index an_index
  749. # Given a one-based index to a grade viewed as a rank, where rank 1 is the
  750. # most preferred rank and higher numbers correspond to the less-preferred
  751. # ranks, answer with the abstrct grade object representing the corresponding
  752. # grade in the Bucklin election.
  753. grades_by_rank_index[an_index - 1]
  754. end
  755. def most_preferred_grade
  756. @most_preferred_grade ||= grades_by_rank_index[0]
  757. end
  758. def grades_by_rank_index
  759. @grades_by_rank_index ||= lambda do # called immediately.
  760. they = Array.new approval_grade_count
  761. (0..(approval_grade_count - 1)).each do|i|
  762. they[i] = BucklinGrade.new {|n| n.rank_index = i}
  763. end
  764. (1..(approval_grade_count - 1)).each do|i|
  765. they[i - 1].next_less_preferred = they[i]
  766. end
  767. they
  768. end.call
  769. end
  770. def grades
  771. grades_by_rank_index.values
  772. end
  773. def ballot_models
  774. @ballot_models ||= $example.voters.map do | a_voter |
  775. BucklinBallotModel.new do |b|
  776. b.instance_count = a_voter.weight
  777. a_dict = Hash.new
  778. a_voter.ranks_by_candidate.each do | candidate, one_based_rank |
  779. a_dict[candidate] = grade_from_one_based_rank_index one_based_rank
  780. end
  781. b.grades_by_candidate = a_dict
  782. end
  783. end
  784. end
  785. def first_round
  786. BucklinRound.new {|n| n.election = self}
  787. end
  788. end
  789. class BucklinBallotModel < ExtendedObject
  790. # An instance represents a group of ballots all marked the same way.
  791. attr_accessor :grades_by_candidate, :instance_count
  792. end
  793. class BucklinGrade < ExtendedObject
  794. # An instance represents a grade or rank.
  795. attr_accessor :rank_index # zero-based index where most preferred gets lowest.
  796. attr_accessor :next_less_preferred
  797. end
  798. class BucklinCandidateTally < ExtendedObject
  799. attr_accessor :candidate
  800. attr_accessor :approval_accumulators_by_rank_index
  801. end
  802.  
  803. class BucklinRound < ExtendedObject
  804. # An instance represents a round in the election.
  805. # It might be the only round.
  806. attr_accessor :election
  807. def finds_majority_in_score a_score
  808. election.finds_majority_in_score a_score
  809. end
  810. def approval_grade_count
  811. election.approval_grade_count
  812. end
  813. def ballot_models
  814. election.ballot_models
  815. end
  816. def candidates
  817. election.candidates
  818. end
  819. def most_preferred_grade
  820. election.most_preferred_grade
  821. end
  822. def working_tallies
  823. # Answer with data structures allocated for the candidates'
  824. # tallies but not necessarily filled in with the totals yet.
  825. @working_tallies ||= candidates.map do | c |
  826. BucklinCandidateTally.new do |n|
  827. n.candidate = c
  828. n.approval_accumulators_by_rank_index = [0] * approval_grade_count
  829. end
  830. end
  831. end
  832. def working_tallies_by_candidate
  833. @working_tallies_by_candidate ||= working_tallies.inject({}) do | a, e |
  834. a.update({e.candidate => e})
  835. end
  836. end
  837. def working_tally_for_candidate a_candidate
  838. working_tallies_by_candidate[a_candidate]
  839. end
  840. def tallies
  841. # Answer with the candidate tallies for this round.
  842. @tallies or ballot_models.each do | ab |
  843. ab.grades_by_candidate.each do | candidate, grade |
  844. ( working_tally_for_candidate candidate
  845. ).approval_accumulators_by_rank_index[grade.rank_index] +=
  846. ab.instance_count
  847. end
  848. end
  849. @tallies = @working_tallies
  850. end
  851. def first_miniround
  852. BucklinMiniround.new do |n|
  853. n.round = self
  854. n.through_grade = self.most_preferred_grade
  855. end
  856. end
  857. end
  858.  
  859. class BucklinMiniround < ExtendedObject
  860. # A mini-round deals with the grades from the most preferred down through
  861. # some given grade, and looks at the candidates with regard to their scores
  862. # taken through that range of grades.
  863. attr_accessor :round, :through_grade
  864. def finds_majority_in_score a_score
  865. round.finds_majority_in_score a_score
  866. end
  867. def tallies
  868. @tallies ||= round.tallies.map do |e| BucklinTallyThroughGrade.new do |n|
  869. n.tally = e
  870. n.through_grade = through_grade
  871. end end
  872. end
  873. def majority_tallies
  874. tallies.select{|e| finds_majority_in_score e.score}
  875. end
  876. def next
  877. ng = through_grade.next_less_preferred
  878. ng and self.class.new do |n|
  879. n.round = round
  880. n.through_grade = ng
  881. end
  882. end
  883. def inspect
  884. "{a miniround}"
  885. end
  886. end
  887. class BucklinTallyThroughGrade < ExtendedObject
  888. # An instance holds a candidate's score summed down through a certain grade
  889. # in a round of a Bucklin election.
  890. attr_accessor :tally, :through_grade
  891. def score
  892. @score ||= (0..(through_grade.rank_index)).inject(0) do | a, i |
  893. a + approval_accumulators_by_rank_index[i]
  894. end
  895. end
  896. def approval_accumulators_by_rank_index
  897. tally.approval_accumulators_by_rank_index
  898. end
  899. def inspect
  900. if @tally && @through_grade
  901. "{#{score} #{candidate.name}}"
  902. else
  903. super
  904. end
  905. end
  906. def candidate
  907. tally.candidate
  908. end
  909. end
  910.  
  911.  
  912.  
  913. ################################################################################
  914. # #
  915. # Symmetric Improved Condorcet Top #
  916. # #
  917. ################################################################################
  918.  
  919. class SymmetricImprovedCondercetTopElection < ExtendedObject
  920. # Symmetric Improved-Condorcet-Top
  921. # http://w...content-available-to-author-only...a.com/wiki/Symmetrical_ICT
  922.  
  923. class BallotModel < ExtendedObject
  924. # An instance represents a group of ballots that are marked the same way.
  925.  
  926. attr_accessor :underlying # My basis in the raw data.
  927.  
  928. def ranks__over a, b
  929. # Answer whether I rank candidate a over candidate b.
  930. if ! score_for_candidate(a)
  931. false
  932. elsif ! score_for_candidate(b)
  933. true
  934. else
  935. score_for_candidate(a) > score_for_candidate(b)
  936. end # if
  937. end # ranks__over
  938. def score_for_candidate a_candidate
  939. # Answer with the score that I assign to a_candidate.
  940. # If none, answer nil.
  941. underlying.scores_by_candidate[a_candidate.underlying]
  942. end
  943. def instance_count
  944. # How many ballots are in me?
  945. underlying.weight
  946. end
  947. def ranks_at_bottom a_candidate
  948. # Answer whether I rank a_candidate at the bottom.
  949. i = score_for a_candidate
  950. ! i || 0 == i
  951. end
  952. def ranks_at_top a_candidate
  953. # Answer whether I rank a_candidate at the top.
  954. underlying.scores_by_candidate.values.max == (score_for a_candidate)
  955. end
  956. def score_for a_candidate
  957. score_for_candidate a_candidate
  958. end
  959. end # BallotModel
  960.  
  961. class Candidate < ExtendedObject
  962. attr_accessor :underlying # the rawer data structure that underlies me.
  963. attr_accessor :election # the election to which I belong.
  964.  
  965. def inspect
  966. if @underlying
  967. "{#{@underlying.name}}"
  968. else
  969. "{an unitialized Candidate structure for Symmetric ICT}"
  970. end
  971. end
  972. def beats another
  973. # Answer whether I beat the indicated other candidate.
  974. (self % another).beats
  975. end
  976. def % another
  977. # Answer with the pair of candidates consisting of myself and another
  978. # in that order. Another should not be me.
  979. another.inverse_percent self
  980. end # %
  981. def inverse_percent another
  982. # Indicate the pair of which another is the left member and I am the
  983. # right member. another should not be me.
  984. inward_edges_by_candidate[another]
  985. end
  986. def is_unbeaten
  987. # Answer whether I am unbeaten.
  988. if nil == @is_unbeaten
  989. inward_edges.each do | a_pair |
  990. if a_pair.beats
  991. return @is_unbeaten = false # Someone beat me.
  992. end
  993. end
  994. # The above loop contains an early return.
  995. @is_unbeaten = true
  996. end
  997. # This method contains an early return.
  998. @is_unbeaten
  999. end
  1000. def inward_edges
  1001. # Answer with the candidate pairs of which I am the right-hand candidate.
  1002. @inward_edges ||= others.map do | another |
  1003. CandidatePair.new{|n| n.left = another; n.right = self}
  1004. end
  1005. end
  1006. def inward_edges_by_candidate
  1007. @inward_edges_by_candidate ||= inward_edges.inject({}) do | a, e |
  1008. a[e.left] = e
  1009. a
  1010. end
  1011. end
  1012. def others
  1013. # Indicate the candidates who are not me.
  1014. all.select{|e| self != e}
  1015. end
  1016. def all
  1017. # Indicate the candidates in the election.
  1018. election.candidates
  1019. end
  1020. def top_count
  1021. # The count of ballots that rank me at the top.
  1022. @top_count_prim ||= ballot_models.inject(0) do
  1023. | a, e|
  1024. if e.ranks_at_top self
  1025. a + e.instance_count
  1026. else
  1027. a
  1028. end # if
  1029. end # do
  1030. end # top_count
  1031. def ballot_models; election.ballot_models; end
  1032. end # Candidate
  1033.  
  1034. class CandidatePair < ExtendedObject
  1035. # An instance represents an ordered pair of different candidates.
  1036. attr_accessor :left, :right
  1037. attr_reader :bottom_count_prim, :top_count_prim # for use with mates.
  1038. def bottom_count
  1039. # Return the count of ballots that rank both my members at the bottom.
  1040. @bottom_count_prim ||= transpose.bottom_count_prim || ballot_models.inject(0) do
  1041. | a, e|
  1042. if e.ranks_at_bottom left and e.ranks_at_bottom right
  1043. a + e.instance_count
  1044. else
  1045. a
  1046. end
  1047. end
  1048. end
  1049. def top_count
  1050. # Return the count of ballots that rank both my members at the top.
  1051. @top_count_prim ||= transpose.top_count_prim || ballot_models.inject(0) do
  1052. | a, e|
  1053. if e.ranks_at_top left and e.ranks_at_top right
  1054. a + e.instance_count
  1055. else
  1056. a
  1057. end
  1058. end
  1059. end
  1060. def election
  1061. right.election
  1062. end
  1063. def ballot_models
  1064. election.ballot_models
  1065. end
  1066. def beats
  1067. if approximately_beats
  1068. if transpose.approximately_beats
  1069. over_count > transpose.over_count
  1070. else
  1071. true
  1072. end
  1073. else
  1074. false
  1075. end
  1076. end # beats
  1077. def approximately_beats
  1078. # Answer whether by the first-cut definition of "beats", left would be
  1079. # said to beat right.
  1080. over_count + bottom_count > transpose.over_count + top_count
  1081. end
  1082. def over_count
  1083. # Return the count of ballots that rate left over right.
  1084. @over_count ||= ballot_models.inject(0) do | a, m |
  1085. if m.ranks__over(left, right)
  1086. a + m.instance_count
  1087. else
  1088. a
  1089. end
  1090. end
  1091. end # over_count
  1092. def transpose
  1093. # Answer with the candidate pair that has the same candidates I have, but
  1094. # in the opposite order.
  1095. @transpose ||= right % left
  1096. end
  1097. end # CandidatePair
  1098. def unbeaten_candidates_count
  1099. @unbeaten_candidates_count ||= unbeaten_candidates.size
  1100. end
  1101. def unbeaten_candidates
  1102. @unbeaten_candidates ||= candidates.select{|c|c.is_unbeaten}
  1103. end
  1104. def candidates_count
  1105. @candidates_count ||= candidates.size
  1106. end
  1107. def candidates
  1108. @candidates ||= underlying.candidates_by_name.values.map do | uc |
  1109. Candidate.new{|n| n.underlying = uc; n.election = self}
  1110. end
  1111. end
  1112. def leaders
  1113. # Answer with the candidates tied for the win.
  1114. if unbeaten_candidates_count == 1
  1115. unbeaten_candidates
  1116. elsif unbeaten_candidates_count == 0 or
  1117. unbeaten_candidates_count == candidates_count
  1118. # then
  1119. leaders_by_top_count_among candidates
  1120. else
  1121. leaders_by_top_count_among unbeaten_candidates
  1122. end
  1123. end
  1124. def print_leaders_with_explanation
  1125. # Print the names of the candidates tied for the win, with a top-level
  1126. # explanation of by what rule we arrive at that result.
  1127. if unbeaten_candidates_count == 1
  1128. puts "There is only one unbeaten candidate, #{unbeaten_candidates.first}."
  1129. elsif unbeaten_candidates_count == 0 or
  1130. unbeaten_candidates_count == candidates_count
  1131. # then
  1132. puts unbeaten_candidates_count == 0 ?
  1133. "There are no unbeaten candidates." :
  1134. "All #{candidates_count} candidates are unbeaten."
  1135. puts "The candidates who received top billing from the most ballots are" +
  1136. " " + (leaders_by_top_count_among candidates).inspect + "."
  1137. else
  1138. puts "Some, but not all, of the candidates are unbeaten."
  1139. puts "Among the unbeaten ones, these are the ones who received top"
  1140. puts "rankings from the highest count of ballots:"
  1141. puts leaders_by_top_count_among(unbeaten_candidates).inspect
  1142. end
  1143. end
  1144. def leaders_by_top_count_among some_candidates
  1145. # Answer with the candidates from among some_candidates who received top
  1146. # ranking from the most ballots.
  1147. leaders_among(some_candidates) {|e| e.top_count}
  1148. end
  1149. def leaders_among(some_objects, &y)
  1150. # Answer with those among some_objects to which the result of calling the
  1151. # given block on them is the highest.
  1152. max_r = some_objects.map(&y).max
  1153. some_objects.select{|e| max_r == y.call(e)}
  1154. end
  1155. def underlying
  1156. # Indicate the rawer data structure that underlies me.
  1157. $example
  1158. end
  1159. def ballot_models
  1160. @ballot_models ||= underlying.voters.map do |v| BallotModel.new do |n|
  1161. n.underlying = v
  1162. end end
  1163. end
  1164. end # SymmetricImprovedCondercetTopElection
  1165.  
  1166.  
  1167. ################################################################################
  1168. # #
  1169. # Top-level Queries #
  1170. # #
  1171. ################################################################################
  1172.  
  1173.  
  1174. # Who wins the first round of the score election? Let r1 be the first round.
  1175.  
  1176. r1 = $example.score_election.first_round
  1177. puts "Score election."
  1178. puts
  1179. puts "Round 1, ordered tallies:"
  1180. r1.ordered_tallies.each {|e| puts " " + e.inspect}
  1181. puts
  1182. puts "Round 1 leaders #{r1.leaders}"
  1183. # puts "Manual resolution of tie: fine Score used as the winner."
  1184. # r1.winners = [r1.leaders[0]]
  1185. # puts "r1.winners #{r1.winners}"
  1186. r2 = r1.next
  1187. puts "Round 2 leaders #{r2.leaders}."
  1188. r3 = r2.next
  1189. puts "Round 3 leaders #{r3.leaders}."
  1190. r4 = r3.next
  1191. puts "Round 4 leaders #{r4.leaders}."
  1192.  
  1193. puts
  1194. puts
  1195. puts "Symmetric Improved Condorcet Top election:"
  1196. puts
  1197. sicte = SymmetricImprovedCondercetTopElection.new
  1198. sicte.print_leaders_with_explanation
  1199.  
Success #stdin #stdout 0.03s 7488KB
stdin
Input starts at about line 110 of the code.
Look for "$example.data do" and modify what comes after.
stdout
Score election.

Round 1, ordered tallies:
  {0.9483333333333334 fine Score}
  {0.925 coarse Score}
  {0.7383333333333333 Approval}
  {0.7266666666666667 Majority Judgment}
  {0.5116666666666667 Condorcet}
  {0.39166666666666666 Bucklin}
  {0.2783333333333333 ER-Bucklin (delay)}
  {0.27666666666666667 ER-Bucklin (no delay)}
  {0.19166666666666668 Instant Runoff}
  {0.16666666666666666 SODA}
  {0.16666666666666666 Continuous Majority Judgment}
  {0.165 Score (selected alternative values)}
  {0.15166666666666667 ERABW}
  {0.15166666666666667 Ranked Pairs}
  {0.15166666666666667 Improved Condorcet Top}
  {0.15 other Condorcet}
  {0.06666666666666667 Runoff}
  {0.016666666666666666 Plurality}

Round 1 leaders [{0.9483333333333334 fine Score}]
Round 2 leaders [{0.9265687583444593 coarse Score}].
Round 3 leaders [{0.7435829035900348 Approval}].
Round 4 leaders [{0.731319867628637 Majority Judgment}].


Symmetric Improved Condorcet Top election:

Some, but not all, of the candidates are unbeaten.
Among the unbeaten ones, these are the ones who received top
rankings from the highest count of ballots:
[{fine Score}, {coarse Score}]