Skip to content

Exercise 14 — The transfer machine

A nine-member book club buys two novels, by ranked ballot under STV — the proportional cousin of RCV-IRV. Five members adore Austen; one champions Brontë; three want Camus. Your job is to be the counting machine: compute the quota, elect, transfer the surplus, eliminate, transfer again — and follow one single ballot through the whole journey to see where its vote finally lands.

▶ Live on BetterVoting: vote (election tk776t, Test ID BV2201) — but don't expect numbers there: this election found a live BV bug — now diagnosed. BetterVoting's STV tabulator crashes on any count that ends with a sole remaining hopeful reaching quota (this one does), while its other STV races compute fine — see A live bug, found — and diagnosed below. The seats on this page are the LH engine's.

You practice: STV's two moving parts — the Droop quota and the transfer (surplus and elimination) — the mechanics behind every "no vote is wasted" claim, done by hand at whiteboard scale. (Method home: STV; the score-ballot counterpart is exercise 12.)

Work each part on paper before opening its solution. The YAML is runnable; the _tabulated mirror is the full report.

The ballots

Nine ranked ballots, four novelists, two seats:

count ranking
×5 Austen > Brontë > Camus > Dickens
×1 Brontë > Camus
×3 Camus > Dickens

Your task

  • (a) Compute the Droop quota for 9 voters and 2 seats. Why that formula and not "half"?
  • (b) Round 1: count first choices. Who is elected, what is the surplus, and — transferring fractionally — what does each standing candidate now hold?
  • (c) Nobody else reaches quota. Run the eliminations until the second seat fills. Narrate where every transferred vote goes.
  • (d) Follow ONE of the five Austen ballots from the moment it was cast to the final result. How much of it elected Austen? Where did the rest end up?
  • (e) The Austen bloc is 5 of 9 voters and got 1 of 2 seats; the Camus camp is 3 of 9 and got the other. Is that proportional? Check against the quota — and name what would have happened under a Bloc-style count.
  • (f) Now count it again with the exact Droop quota, 9 ÷ 3 = 3.00 — the one the engine applies. Redo the surplus, the transfer weight, and the eliminations. What changes? What doesn't?

Solutions

(a) Hand-count Droop quota = 4 Droop = ⌊9 ÷ (2+1)⌋ + 1 = **4**. The logic: at most *two* candidates can each hold 4 votes out of 9 (4+4=8 ≤ 9, but 4+4+4 > 9) — so reaching 4 makes a seat mathematically safe, and no third rival can match both winners. "Half" is the single-winner special case of the same formula: ⌊V/2⌋+1. **One thing to know before you count.** "The Droop quota" names *two* published formulas, one vote apart, and this exercise walks the hand-count one. The theory literature (Woodall) and most software — including the engine behind this page — use the **exact** form with no rounding, 9 ÷ 3 = **3.00**. Both are standard; they seat the same two novels here; they move different numbers to get there. Work parts (b)–(e) with **4**, then read [(f)](#f) for the same count at 3.00. The engine's header prints both so a hand-checker is never stranded:
Abridged for the lesson — the header only
 2 seats; quota = 3.00 (exact Droop, votes/(seats+1)) — 33.3% of 9.
 Elected at >= quota, and every surplus is measured from it.
 (Hand-count Droop, floor(9/3)+1 = 4, is a different but equally standard rule.)
(b) Round 1 — Austen elected; her surplus of 1 rides to Brontë First choices — and this table is the same whichever quota you use, because nothing has moved yet:
Abridged for the lesson — round 1 only
ROUND 1
Candidate      Votes  Status
-----------  -------  --------
Austen             5  Elected
Camus              3  Hopeful
Bronte             1  Hopeful
Dickens            0  Hopeful
Austen holds 5 ≥ 4: **elected**, with a surplus of 5 − 4 = **1**. Fractional (Gregory) transfer: all five Austen ballots move to their next choice at weight 1/5 each — five ballots × 0.2 = **1.0 vote to Brontë**. Standing after round 1: Brontë 1 + 1 = **2**, Camus **3**, Dickens **0**. The surplus is where the two quotas part company, and it is worth seeing why the arithmetic is forced. A winner keeps *exactly a quota* and passes on the rest, so the quota you chose in (a) sets both numbers: keep 4, pass 1, transfer at 0.2 per ballot. Take the exact quota instead and Austen keeps 3, passes **2**, and each ballot moves at 0.4 — [part (f)](#f) runs that count to the end. Neither is a rounding slip; they are two rulebooks.
(c) The eliminations — the surplus keeps moving until it lands on Camus No hopeful holds 4, so the machine eliminates from the bottom. **Dickens (0)** goes first — nothing to transfer (no ballot ranks him top). Then **Brontë (2)**: her pile transfers to each ballot's next standing choice — her own full ballot (Brontë > Camus) goes to **Camus**, and the five one-fifth fractions from Austen's surplus *continue down their rankings* to **Camus** too. Camus: 3 + 1 + 1 = **5 ≥ 4 — elected. Seats: Austen and Camus.** That is the drill, and it is a correct STV count. It is *not* the count the engine ran: under the exact quota, Brontë reaches 3.00 on the bigger surplus and the second seat fills without a single elimination — see [(f)](#f). Both roads end at Austen and Camus.
(d) One ballot's journey Take any one of the five `Austen > Brontë > Camus > Dickens` ballots. **4/5 of it** was spent electing Austen (five ballots jointly paying the quota of 4). The remaining **1/5** transferred to Brontë; when Brontë was eliminated, that same fifth continued to **Camus** — and became part of the vote that elected him. Final ledger for this single ballot: 0.8 votes → Austen (seated), 0.2 votes → Camus (seated), 0.0 wasted. That is the "transferable" in Single Transferable Vote: a ballot is a *ranked to-do list with a budget*, spent top-down until it's used up — the honest version of the no-wasted-vote slogan. (The dishonest version ignores that rankings can run out mid-journey: a truncated ballot's remainder simply [exhausts](../../06_Other/RCV_IRV/concepts/RCV_IRV_exhausted_ballots.md) — here every transfer found a next name, but that's a property of these ballots, not of STV.) **That tidy ledger is the hand count's — our engine's is less tidy.** Under the exact quota it applies, the ballot pays 0.6 to Austen and sends 0.4 to Brontë, who is then never eliminated because the seats fill first; that 0.4 comes to rest on a candidate who does not win. Nothing was stolen from the voter — Camus already had the second seat sewn up, so the transfer would have changed nothing — but "0.0 wasted" is a claim about a particular rulebook and this electorate, not a theorem about STV. (Careful with the blame: RCTab counts the same ballots at the same exact quota and *does* carry that 0.4 on to Camus. The stranded fraction is our engine's tie handling, not the quota. [(f)](#f) puts all three counts side by side.)
(e) The proportionality check With quota 4, the 5-voter Austen bloc funds exactly one seat (and its leftover 1 vote correctly *failed* to buy Brontë a second); the 3-voter Camus camp — just under quota on its own — reaches a seat with the transferred remainders. One seat per ~4 voters: **proportional to the room**, as designed. A Bloc-style count on these preferences would hand the Austen majority *both* seats ([exercise 12](ex12_bloc_vs_proportional.md)'s sweep, in ranked clothing). STV is the ranked-ballot route to the same proportional philosophy as Allocated Score — the side-by-side is worked in [STV vs STAR-PR](../../method_comparisons/stv_vs_star_pr/README.md), and the family map lives at [electing more than one](../../07_Concepts/topics/electing_more_than_one.md). The verdict survives the quota switch, and gets cleaner: at 3.00 the Camus camp's three voters are *exactly* one quota, so they buy their seat outright rather than on remainders. Five voters is 1.67 quotas — one seat, with change that cannot fund a second. Proportional under either rulebook.

(f) The same nine ballots at the exact quota

Not a spoiler — the reconciliation. Work (a)–(e) first, then read this.

Set the quota to 9 ÷ 3 = 3.00 and run it again. Round 1 is unchanged (nothing has moved yet), but everything after it moves: Austen keeps 3 instead of 4, so her surplus is 2, and the five ballots travel at 0.4 each instead of 0.2.

Three counts of the same nine ballots, then — the hand count, our engine, and RCTab, the certified tabulator US jurisdictions run on election night, configured to the same exact quota:

hand count, quota 4 our engine, exact 3.00 RCTab, exact 3.0001
Austen keeps / passes on 4 / 1 3 / 2 3.0001 / 1.9999
transfer weight per Austen ballot 0.2 0.4 0.3999
Brontë after the surplus 2 3.00 — level with Camus 2.9995 — just short
eliminations Dickens, then Brontë none Dickens, then Brontë
how the second seat fills Camus climbs to 5 ≥ 4 Camus is the last hopeful left Camus climbs to 5.9995
one Austen ballot's ledger 0.8 Austen + 0.2 Camus 0.6 Austen + 0.4 Brontë (unseated) 0.6 Austen + 0.4 → Brontë → Camus
seats Austen + Camus Austen + Camus Austen + Camus

Read the bottom row first, then the rest. Three rulebooks disagree about nearly every number in the count and agree about the only thing being decided.

And the middle column is the odd one out, which is the lesson. It is tempting to read the "none" in the eliminations row as something the exact quota causes — it isn't. RCTab uses the same exact Droop quota and still eliminates Dickens and Brontë exactly as the hand count does. The difference is that RCTab sets the bar a hair above the quota (V/(S+1) + 10⁻ᵈ = 3.0001, per its own config documentation), so Austen keeps 3.0001, passes on 1.9999, and Brontë arrives at 2.9995 — strictly short of Camus, and duly eliminated. Our pyrankvote uses the bare V/(S+1), so Brontë arrives at exactly 3.00, ties Camus, and gets set aside by a "cannot change the result" shortcut rather than eliminated. The quota fork is real; the vanishing elimination round is our engine's tie handling, not the quota. Only running a third engine makes that separable — which is what a cross-check is for.

So Brontë being rejected on the same 3.00 that seats Camus is an artifact of one implementation's rounding, not a fact about STV. A tie for the last seat is the one place where the rulebook, not the electorate, picks the winner — pyrankvote breaks it deterministically on second choices; real STV codes write that rung down in law. Worth noticing that they have to.

All three engines seat Austen and Camus, and all ten STV elections in this library agree with RCTab.

A live bug, found — and diagnosed

Making this exercise live turned it into a bug report. BetterVoting accepted the election and all nine ballots (tk776t, BV2201) — but its STV tabulator returns a server error when computing results ({"error":"Error (a5f1af00)"}-style, fresh ID per attempt). The bisection ran to ground the same day, with permanent public elections as the lab notebook — the full write-up, evidence table, and ready-to-file issue live at the sole-survivor STV crash. The short version:

  • Truncation acquitted. A fully-ranked twin — same nine voters, trailing rankings no transfer ever reaches — fails identically (bj8dfc, BV2202; repo home ex14_two_novels_fullranks.yaml, LH-verified to the same seats).
  • The enable_write_in flag acquitted. A probe with the key omitted from the race object — the one config difference from BV's older, working STV races — crashes identically (gvtg2h, BV2203).
  • The shape acquitted, the endgame convicted. A control with identical config (STV, 2 seats, 4 candidates) whose seats fill while two hopefuls still stand computes fine (39py93, BV2204) — and a minimal 1-seat, 6-voter election whose eliminations leave one candidate standing crashes (8xwx43, BV2205).
  • Root cause, in BV's own source. This exercise's count ends with Camus reaching quota as the sole remaining hopeful. BetterVoting's IRV.ts elect-branch then redistributes his surplus over an empty candidate list, and distributeVotes runs remainingCandidates.reduce(…) with no initial value — [].reduce(f) throws TypeError. A sole survivor below quota is rescued by the fill-remaining-seats shortcut; only the at-quota sole survivor crashes. (Their own STV.test.ts is this same 9-voter shape with a benign endgame — the gap in one test.)

One more symptom for the report: the BV UI export of both elections silently omits the Results section entirely (Election + Ballots only) — the frozen exports beside these YAMLs archive the ballots and await a re-export once the tabulator is fixed. Until BV fixes it, the seats come from the LH engine (or any STV engine you point at these nine ballots), and the live elections stand as the reproduction set. It is also this set's best accidental lesson: methods are math, implementations are software — both need testing, which is what this repo's triple-check habit is for. The tidy design — every STV moving part firing exactly once — is precisely what walked the count into an endgame that big real-world fields almost never reach. The exercise wasn't unlucky; it was thorough.

Reading this fairly

A tidy electorate: full rankings (nothing exhausts), one surplus, one meaningful elimination — chosen so every moving part fires exactly once. Real STV counts inherit RCV-IRV's operational caveats (central tabulation, exhaustion under rank limits) alongside the genuine proportionality shown here; the balanced comparison with the score-ballot PR family is STV vs STAR-PR.

The case file

The YAML the engine actually runs, embedded at build time — so the ballots on this page can never drift from the file:

cases/ex14_two_novels.yaml
election_title: "Exercise 14  The transfer machine: a book club buys two novels (STV)"
bv_test_id: BV2201
bv_election_id: tk776t
bv_results_url: https://bettervoting.com/tk776t/results

scenario_description: |-
  A nine-member book club buys TWO novels by ranked ballot, counted with
  STV (the proportional cousin of RCV-IRV). Round 1: Austen has 5 first
  choices — over quota — and is elected; her surplus transfers, as
  fractions of the five ballots that elected her, to their next choice,
  Bronte. From there STV fills the second seat the way it always does —
  moving surpluses, and eliminating from the bottom if nobody is over
  the line — and Camus takes it. Seats: Austen + Camus. The drill
  follows one ballot through the whole journey — how much of it elected
  Austen, and where the remainder came to rest — to see how STV keeps a
  vote moving until it lands. How large that surplus is, and whether any
  elimination is needed, depends on which of the two published Droop
  quotas the count applies; the report below names the one it uses, and
  part (f) of the exercise works these same ballots both ways. Exercise:
  ex14_transfer_machine.md. Ballots and cast (novelists as candidates)
  are this repo's own.
  Live on BetterVoting (Test ID BV2201): https://bettervoting.com/tk776t
  — the vote page works, but BV's STV tabulator currently returns a
  server error on /results for this election, and for its fully-ranked
  probe twin BV2202 (bj8dfc, ex14_two_novels_fullranks.yaml) — a live BV
  bug, since DIAGNOSED: the count ends with Camus reaching quota as the
  sole remaining hopeful, and BV's IRV.ts redistributes his surplus over
  an empty candidate list ([].reduce with no initial value throws). Full
  bisection (BV2203-2205) and ready-to-file issue:
  06_Other/STV/bv_stv_sole_survivor_crash/. The seats above are the LH
  engine's; BV STV races whose endgame leaves a hopeful standing
  (ywckmg, kcf8vf, 39py93) compute fine.
voting_method: STV
num_winners: 2
ballots: |-
  5:Austen>Bronte>Camus>Dickens
  1:Bronte>Camus
  3:Camus>Dickens
expected_winners:
  - Austen
  - Camus

# file: ex14_two_novels.yaml

Run it yourself

python STARVote_LH_tabulation_engine/starvote_larry_hastings.py 01_STAR/05_Practice/cases/ex14_two_novels.yaml

Every fence on this page is abridged for the lesson. Want the whole count — the header, both rounds and the Smith set? See the full LH report → ex14_two_novels.md. Source: ex14_two_novels.yaml; raw mirror.


Where this comes from. Original to this repo (ballots and cast — novelists as candidates, so the ranked ballot reads like a bookshelf). Concept homes: STV, STV vs STAR-PR.

Back to the exercises set · curriculum home: Voting 301

file: ex14_transfer_machine.md