Navigation

    Voting Theory Forum

    • Register
    • Login
    • Search
    • Recent
    • Categories
    • Tags
    • Popular
    • Users
    • Groups
    1. Home
    2. cfrank
    C
    • Profile
    • Following 1
    • Followers 3
    • Topics 61
    • Posts 460
    • Best 97
    • Groups 2

    cfrank

    @cfrank

    My name is Connor, I’m a moderator on this forum. I’m convinced that political corruption and issues of equity can’t be solved without an effective voting system, that our vote-for-one system is objectively flawed in irreconcilable ways, and that those flaws warrant a thoughtful replacement.

    My background is pure mathematics and nanotechnology. I’m a PhD student in biomedical engineering at OHSU, where I apply deep learning and statistical principles to uncover relationships between 3-dimensional chromatin conformation and transcription in oncogenesis.

    110
    Reputation
    36
    Profile views
    460
    Posts
    3
    Followers
    1
    Following
    Joined Last Online

    cfrank Unfollow Follow
    Forum Council administrators

    Best posts made by cfrank

    • Approval Voting as a Workable Compromise

      I think there are many of us here who prefer some voting system or another over approval voting. I also think there is room for improvement. However, approval voting has a huge advantage in its simplicity and potential for integration into existing infrastructure. This is totally besides the comparisons to make in terms of game theoretical stability with Condorcet methods and expressivity with Score or others.

      My thought is that, if we are really going to make progress by consolidating our support behind a single voting system, then realistically, Approval voting fits the bill. That isn’t to say that it should be the final destination for voting reform, but it would absolutely be a major step forward. While IRV is something of a tokenism, Approval would be an actual game changer.

      Any thoughts about this are welcome.

      posted in Election Policy and Reform
      C
      cfrank
    • Condorcet with Borda Runoff

      This is a minor attempt to modify Condorcet methods in a simple way to become more responsive to broader consensus and supermajority power. It’s sort of like the reverse of STAR and may already be a system that I don’t know the name of. In my opinion, the majority criterion is not necessarily a good thing in itself, since it enables tyrannical majorities to force highly divisive candidates to win elections, which is why I’ve been trying pretty actively to find some way to escape it.

      For the moment I will assume that a Condorcet winner exists in every relevant case, and otherwise defer the replacement to another system.

      First, find the Condorcet winner, which will be called the “primary” Condorcet winner. Next, find the “secondary” Condorcet winner, which is the Condorcet winner from the same ballots where the primary Condorcet winner is removed everywhere.

      Define the Borda difference from B to A on a ballot as the signed difference in their ranks. For example, the Borda difference from B to A on the ballot A>B>C>D is +1, and on C>B>D>A is -2.

      If A and B are the primary and secondary Condorcet winners, respectively, then we tally all of the Borda differences from B to A. If the difference is positive (or above some threshold), then A wins, and if it is negative or zero (or not above the threshold), then B wins.

      For example, consider the following election:

      A>B>C>D [30%]
      A>B>D>C [21%]
      C>B>D>A [40%]
      D>B>C>A [9%]

      In this case, A is a highly divisive majoritarian candidate and is the primary Condorcet winner. B is easily seen to be the secondary Condorcet winner. The net Borda difference from B to A is

      (0.3+0.21)-2(0.4+0.09)<0

      Therefore B would be chosen as the winner in this case.

      Some notes about this method:
      It certainly does not satisfy the Condorcet criterion, nor does it satisfy the majority criterion. These are both necessarily sacrificed in an attempt to prevent highly divisive candidates from winning the election. It does reduce to majority rule in the case of two candidates, and it does satisfy the Condorcet loser criterion, as well as monotonicity and is clearly polynomial time. It can also be modified to use some other metric in the runoff based on the ballot-wise Borda differences.


      Continuing with the above example, suppose that the divisive majority attempts to bury B, which is the top competitor to A.
      This will change the ballots to something like

      A>C>D>B [30%]
      A>D>C>B [21%]
      C>B>D>A [40%]
      D>B>C>A [9%]

      And if the described mechanism is used in this case, we will find instead that C is elected. So burial has backfired if B is "honestly" preferred over C by the divisive majority, and they would have been better off indicating their honest preference and electing B.


      And again, suppose that the divisive majority decides to bury the top two competitors to A, namely B and C, below D, keeping the order of honest preference between them. We will find

      A>D>B>C [30%]
      A>D>B>C [21%]
      C>B>D>A [40%]
      D>B>C>A [9%]

      In this case, the secondary Condorcet winner is D, and the mechanism will in fact elect D, again a worse outcome for the tactical voters.


      Finally, suppose that they swap the order of honest preference and vote as

      A>D>C>B [30%]
      A>D>C>B [21%]
      C>B>D>A [40%]
      D>B>C>A [9%]

      Still this elects D.

      As a general description, this method will elect the Condorcet winner unless they are too divisive, in which case it will elect the secondary Condorcet winner, which will necessarily be less divisive. I believe that choosing the runoff to be between the primary and secondary Condorcet winners should maintain much of the stability of Condorcet methods, while the Borda runoff punishes burial and simultaneously addresses highly divisive candidates.

      posted in Single-winner
      C
      cfrank
    • PR with ambassador quotas and "cake-cutting" incentives

      This is a concept I had in mind which may already have been described, although not all of the logistics are necessarily hashed out and there may be issues with it. The idea is described below, but first I want to make a connection to “cake-cutting.” The standard cake-cutting problem is when two greedy agents are going to try to share a cake fairly without an external arbiter. An elegant solution is a simple procedure where one agent is allowed to cut the cake into two pieces, and the other agent is allowed to choose which piece to take for themselves. The first agent will have incentive to cut the cake as evenly as discernible, since the second agent will try to take whichever piece is larger. In the end, neither agent should have any misgivings about their piece of cake.

      So this is my attempt to apply that kind of procedure to political parties and representatives. Forgive my lack of education regarding how political parties work:

      • There should be a government body that registers political parties and demands the compliance of all political parties to its procedures in order for them to acquire seats for representation;
      • (Eyebrow raising, but you might see why...) Every voter must register as a member of exactly one political party in order to cast a ballot (?);
      • Each political party A is initially reserved a number of seats in proportion to the number of voters with membership in A; the fraction of seats reserved for A is P(A). however
      • For each pair of political parties A and B (where possibly B=A), a fraction of seats totaling P(A~B):=P(A)P(B) will be reserved for candidates nominated by A, and elected by B; these seats will be called ambassador seats from A to B when B is different from A, and otherwise will be called the main platform seats for A;
      • Let there be a support quota Q(A~B) for the number of votes needed to elect ambassadors from A to B, and call P(A~B) the ambassador quota of party A for B. If E(A~B) is the fraction of filled A-to-B ambassador seats (as a fraction of all seats), I.e. nominees from A who are actually elected by members of B, then A will only be allowed to elect P(A~A)*min{min{E(A~B)/P(A~B), E(B~A)/P(B~A)}: B not equal to A} of its own nominees. That is, the proportion of reserved main-platform seats that A will be allowed to fill is the least fraction of reserved ambassador seats it fills in relation to every other party, including both the ambassadors from A to other parties, and the ambassadors from other parties to A.

      This procedure forces parties to also nominate candidates that compromise between different party platforms in order to obtain seats for any main-platform representatives. If a party fails to meet its quota for interparty compromises, it will lose representation. On the flip side, this set up will also establish high incentives for other parties to compromise with them in order to secure their own main-platform representation. In total, this system would give parties high incentives to compromise with each other and find candidates in the middle ground, which will serve as intermediaries between their main platforms.

      Basically, here the outlines indicate seats open to be filled by candidates who are nominated by the corresponding party, and the fill color indicates seats open for election by the corresponding party:

      Cake Cutting PR.png

      Seats with outlines and fills of non-matching color are ambassador seats, and seats with matching outline and color are main platform seats. In terms of party A, by failing to nominate sufficiently-many candidates who would meet the support quota Q(A~B) to become elected as ambassadors from A to B, or by failing to elect enough ambassadors from B to A, party A restricts its own main platform representation and that of B simultaneously. By symmetry the reciprocal relationship holds from B to A. Therefore all parties are entangled in a dilemma: to secure main-platform representation, parties must nominate a proportional number of candidates who are acceptable enough to other parties to be elected as ambassadors.

      To see that all needed seats are filled in the case of a stalemate, where parties refuse to nominate acceptable candidates to other parties and/or refuse to elect ambassadors, the election can be redone with the proportions being recalculated according to the party seats that were actually filled.

      The support quotas collectively serve as a non-compensatory threshold to indicate sufficient levels of inter-party compromise. Ordinary PR is identical to PR with ambassador quotas but with all support quotas set to zero, whereby there is no incentive to nominate compromise candidates.

      The purpose of this kind of procedure is twofold: firstly, it should significantly enhance the cognitive diversity of representatives, and secondly, it should significantly strengthen more moderate platforms (namely those of the ambassadors) that can serve as intermediaries for compromises between the main platforms of parties. Every party A has a natural “smooth route” from its main platform to the main platform of every other party: The main platform of A should naturally be in communication with ambassadors from A to B, who should naturally communicate with ambassadors from B to A, who should naturally communicate with the main platform of B.

      Also, this procedure gives small parties significant bargaining power in securing representation. Large parties will have much more representation to lose than the small parties that are able to secure seats if the small parties refuse to elect any ambassadors, so rationally speaking, large parties should naturally concede to nominating sufficiently many potential ambassadors whose platforms are closer to the main platforms of those small parties. The same rationale holds for the potential ambassadors nominated by small parties, who also should tend to have platforms closer to the main platform of the small party.

      Finally, this system creates significant incentives for voters to learn about the platforms of candidates from other parties who stand to reserve seats for representatives.

      posted in Proportional Representation
      C
      cfrank
    • RE: What are the strategic downsides of a state using a non-FPTP method for presidential elections?

      @rob especially if the state is a swing state, making it more difficult for the large parties to secure voters for their platform I think would be a significant influence forcing large parties and their candidates to more scrutinizingly determine the real interests of voters in those states. It may dilute the interests of less competitive states, but since the competitive states are crucial to obtaining the presidency, the large parties will still have to invest strongly in the interests of voters in those states in order to compete with alternatives (and obviously each other) for the crucial swing points. This may lead to something like an arms race of concessions, which happened in New Zealand in 1996 and led to the national adoption of a PR system, according to Arend Lijphart. Obviously that's quite a leap for the U.S., but maybe a less extreme analogue is not so far-fetched.

      Maine is one of the thirteen most competitive states for elections according to a 2016 analysis (Wikipedia: Swing state), so I’m not sure their recent establishment is actually strategically foolish, although it’s possible that it wasn’t fully thought through. I agree it isn't clear.

      I think it will definitely be interesting to observe how the current political apparatus responds to Maine--and apparently, more recently, and strangely, Alaska:

      https://news.yahoo.com/alaska-is-about-to-try-something-completely-new-in-the-fall-election-193615285.html

      Since Alaska is far from competitive, I do think this transition was in fact foolish for the reasoning you stated, but it remains to be seen. If we saw a state like Florida transition to a system like Maine's, it would be very interesting to study the relative differences between federal treatments of Florida, Maine, and Alaska as a case study for how "swingy-ness" might influence the effect of such voting system transitions. If Maine experiences an increase in federal power, it would be a good case for the remaining swing states to make a similar transition. If that occurred, the swing states would become a platform foothold for alternative parties to grow.

      posted in Voting Methods
      C
      cfrank
    • RE: Negative Score Voting

      @k98kurz I don’t think there should be any uncertainty in the default for a voter’s ballot.

      posted in Philosophy
      C
      cfrank
    • RE: Approval Voting as a Workable Compromise

      @lime the point of this post isn’t to argue that approval voting is superior to other methods or that modifications wouldn’t improve approval voting, it’s to point out that despite other methods being potentially superior, standard approval voting is probably the most realistic target for near future steps toward substantially reformed voting.

      Unfortunately, more choices does mean the system is more complicated. You can observe that the addition of even a very simple, marginal modification as you suggest already raises questions. Every question about a method is an opportunity for distrust to be exploited, even if the method is ultimately better. Plurality is terrible, but almost nobody had questions about it, and that’s why it’s stuck around for so long. Do you see what I mean? I may be a bit jaded, but I’m hoping to be realistic.

      I don’t mean to be a downer, but my point is a bit sad: in terms of what people would prefer, such as more choices or buttons, what we have to deal with is exactly the fact that people are having a hard time getting what they prefer. The political status quo is strongly opposed to voting reform, it will have to relinquish substantial power and accountability to the people under an effective voting system. There’s a reason only flawed tokenisms like IRV have passed through legislature in recent times. In fact, there is a history of voting reforms being enacted and then reversed.

      posted in Election Policy and Reform
      C
      cfrank
    • RE: What does STAR Voting do when 2nd place is tied?

      @democrates I meant a Condorcet winner only among the front runners (for example, the candidates with the top K scores, here we are taking K=2). If there is no Condorcet winner among them, then we can choose the top scoring candidate.

      In your case, if two candidates have the same second-greatest score, and the three front runners form a Condorcet cycle, then you can use the scores to break the tie. If this was used, then Jill Stein would have won the election.

      That isn’t “the correct” solution (there is no such thing), but it is somewhat less arbitrary than flipping a coin or operating by alphabetical order, neither of which has anything to do with relevant information that is readily available on the ballots.

      If we were being engineers about choosing a high quality candidate to win the election, we could even compute the distribution of scores, take the candidates whose scores exceed some elbow point, and find the Condorcet winner among those candidates with the top scoring candidate as the backup if no Condorcet winner exists. That’s basically a generalization of STAR with a dynamic front-runner selection method.

      There are other ways to proceed. For example, we could remove Condorcet losers, then try to find the Condorcet winner of all remaining candidates, iteratively eliminating the lowest scoring candidate until a Condorcet winner emerges.

      posted in Voting Method Discussion
      C
      cfrank
    • RE: Entropy-Statistic-Weighted Approval Voting

      @toby-pereira yes you’re right, it was just a thought that occurred to me when I was thinking about how to discourage bullet approvals, but it has irreconcilable flaws that are now apparent.

      posted in Voting Methods
      C
      cfrank
    • RE: Approval Voting as a Workable Compromise

      @k98kurz mirroring @Lime, I think any advantage conferred to one candidate over any other in an election should be granted on an opt in basis. A voter shouldn’t have to opt out from conferring an advantage to a candidate.

      posted in Election Policy and Reform
      C
      cfrank
    • RE: A tweak to IRV to make it a Condorcet method

      @wolftune this is a well-known Condorcet method due in spirit to Tideman and called “Bottom N Runoff” where N=2 (hence “Bottom Two Runoff,” I.e. BTR or B2R). Generally speaking, these methods use some kind of absolute criterion (like least number of first place votes, lowest score, lowest approval, etc.) to decide which “bottom” candidates to subject to an elimination round, eliminates a Condorcet loser among them, and iterates until the desired number of winners remain. You are right, they’re pretty good methods. I like them.

      posted in Voting Method Discussion
      C
      cfrank

    Latest posts made by cfrank

    • RE: Idea for truly proportional representation

      @toby-pereira I agree with this. Something in that spirit I am considering is that the power allocations can still be traced back to ballots. For example, if the seated representatives and powers were {A:45, B:35, C:20}, in principle, those single seats could be subdivided into multiple seats of roughly equal power, depending on the candidate pool (i.e. how many candidates are available).

      Possibly, a sub-election could be run to determine the representatives within the A:45 group, etc. Maybe they could be given 4 seats, the B:35 group 3 seats, and C:20 2 seats. That could refine representation, some candidates might pick up multiple seats. It’s probably getting messy and complicated, it essentially becomes a hierarchical partitioning of ballots. I’m not sure what to make of that prospect, it starts looking like a network/phylogenetic tree or forest architecture, and that can become arbitrary fast.

      I do see what you mean. An individual voter may actually prefer a particular coalition of candidates, rather than just want to get their top guy in. I wonder if non-strict rankings and distributed power would mitigate this issue, or for instance, if the A:45 group's sub-election guaranteed a seat for A, and ran the election on the remaining candidates, that might align with the spirit of preference for whole coalitions.

      You definitely are more familiar with this space than I am, so I wager some of my objections may be non-issues when one considers alternative PR methods. But it seems to me that in this case, pushing for coalitions that respect single individual preferences for whole coalitions can lean toward reduced diversity and reduced minority representation. Is that inaccurate? Or is that a common tradeoff issue in PR systems?

      “Would the weighting purely count towards their voting power in the elected body, or does it have other effects such as more time to speak?”

      Yeah, it does beg some questions.

      EDIT: After multiple adjustments made to guard against clone dependence and tactical voting, there is a non-monotonicity issue in my latest version (/branch, it is not my original concept so I don’t claim ownership in any way), where a minority faction can gain strictly preferred representation in the form of a seated candidate by merely withholding approval for that candidate. I have a concrete example of this, and may try to see how to address it. It may be due to something unnecessary.

      posted in Voting Theoretic Criteria
      C
      cfrank
    • RE: Idea for truly proportional representation

      @toby-pereira Awesome! I also reached out to the original author and linked them here.
      I hashed out more adjustments and a Python code chunk that runs elections with detailed audits. It's a bit sophisticated... and currently it actually removes 0-power seats rather than keeping them as a ceremonial role, probably they should still be seated in case subsequent power adjustments are computed. But the latest version and some examples are below.

      The description is not very straightforward either, but the results look pretty good to me.

      """
      Median-T Satiation with Dynamic Prefix Tightening + Candidate-wise RUS
      Exact implementation of the specified method with detailed auditing
      
      METHOD SPECIFICATION:
      ====================
      1. Ballots: RAC (Rank with Approval Cutoff) - each voter ranks all candidates and approves top a_i
      2. T = median(a_i) computed once at start (upper median if even)
      3. Fill seats K=1 to N iteratively
      4. DYNAMIC PREFIX TIGHTENING: Once satiated, a voter's active approvals are always 
         the prefix up to their CURRENT top-ranked seated winner. As better candidates 
         are seated, the prefix tightens upward. It never loosens.
      5. CANDIDATE-WISE RUS: Identify specific "consensus triggers" (candidates that would
         satiate ALL remaining voters). Mark only those candidates as non-satiating, but
         allow satiation based on other winners in the set.
      6. TIEBREAK PRIORITY: Prefer non-flagged candidates over RUS-flagged ones when breaking ties.
      """
      
      from dataclasses import dataclass, field
      from typing import List, Dict, Tuple, Set, Optional
      import math
      import pandas as pd
      from collections import defaultdict
      import copy
      
      class AuditLog:
          """Detailed logging of each step in the process"""
          def __init__(self, verbose: bool = True):
              self.entries = []
              self.verbose = verbose
          
          def log(self, phase: str, rule: str, details: str, data: dict = None):
              entry = {
                  "phase": phase,
                  "rule": rule,
                  "details": details,
                  "data": data or {}
              }
              self.entries.append(entry)
              if self.verbose:
                  print(f"[{phase}] {rule}")
                  print(f"  → {details}")
                  if data:
                      for k, v in data.items():
                          print(f"    {k}: {v}")
                  print()
      
      @dataclass
      class VoterGroup:
          """Represents a group of voters with identical preferences"""
          n: int                          # Number of voters in group
          rank: List[str]                 # Strict ranking of all candidates
          a: int                          # Approval cutoff (approve top a candidates)
          saturated: bool = False         # Whether group is saturated
          satiation_prefix_end: Optional[str] = None  # H: highest-ranked winner when satiated
          
          def get_prefix_candidates(self) -> Set[str]:
              """
              Get candidates in the satiation prefix (at or above H in ranking)
              """
              if not self.saturated or not self.satiation_prefix_end:
                  return set(self.rank)  # All candidates if not saturated
              
              prefix = set()
              for c in self.rank:
                  prefix.add(c)
                  if c == self.satiation_prefix_end:
                      break
              return prefix
          
          def active_approvals(self, current_winners: List[str] = None) -> Set[str]:
              """
              RULE: Active Approvals with Dynamic Prefix Tightening
              - Unsaturated: approve top a_i candidates
              - Saturated: approve only candidates in prefix up to CURRENT top-ranked winner
              """
              if not self.saturated:
                  return set(self.rank[:self.a])
              
              # Saturated: dynamically compute H based on current winners
              if current_winners:
                  current_H = self.top_in_set(current_winners)
                  if current_H:
                      # Build prefix up to current H
                      prefix = set()
                      for c in self.rank:
                          prefix.add(c)
                          if c == current_H:
                              break
                      original_approvals = set(self.rank[:self.a])
                      return original_approvals & prefix
              
              # Fallback to stored prefix
              prefix = self.get_prefix_candidates()
              original_approvals = set(self.rank[:self.a])
              return original_approvals & prefix
          
          def can_influence_h2h(self, cand_a: str, cand_b: str, current_winners: List[str] = None) -> bool:
              """
              RULE: Active Ranking Influence with Dynamic Prefix
              - Unsaturated: can influence all head-to-heads
              - Saturated: can only influence if BOTH candidates are in dynamically computed prefix
              """
              if not self.saturated:
                  return True
              
              # Compute current prefix based on current winners
              if current_winners:
                  current_H = self.top_in_set(current_winners)
                  if current_H:
                      # Build prefix up to current H
                      prefix = set()
                      for c in self.rank:
                          prefix.add(c)
                          if c == current_H:
                              break
                      return cand_a in prefix and cand_b in prefix
              
              # Fallback to stored prefix
              prefix = self.get_prefix_candidates()
              return cand_a in prefix and cand_b in prefix
          
          def prefers(self, a: str, b: str, current_winners: List[str] = None) -> int:
              """
              Return 1 if a>b, -1 if b>a, 0 if tie
              Only valid if can_influence_h2h returns True
              """
              if not self.can_influence_h2h(a, b, current_winners):
                  return 0  # No influence
              
              pos = {c: i for i, c in enumerate(self.rank)}
              ia, ib = pos.get(a, math.inf), pos.get(b, math.inf)
              if ia < ib: return 1
              if ib < ia: return -1
              return 0
          
          def top_in_set(self, winners: List[str]) -> Optional[str]:
              """
              Return highest-ranked candidate from winners (H for this voter)
              """
              for c in self.rank:
                  if c in winners:
                      return c
              return None
          
          def get_rank_position(self, candidate: str) -> int:
              """Get 0-based position of candidate in ranking"""
              try:
                  return self.rank.index(candidate)
              except ValueError:
                  return math.inf
          
          def would_satiate(self, winners: List[str], T: int) -> bool:
              """
              RULE: Satiation Test
              Voter satiates if ANY winner appears within top min(T, a_i) ranks
              """
              if self.saturated:
                  return False
              
              threshold = min(T, self.a)
              for w in winners:
                  pos = self.get_rank_position(w)
                  if pos < threshold:
                      return True
              return False
      
      @dataclass
      class Election:
          """Manages the election process with detailed auditing"""
          candidates: List[str]
          groups: List[VoterGroup]
          tie_order: List[str] = field(default_factory=list)
          audit: AuditLog = field(default_factory=AuditLog)
          
          def __post_init__(self):
              if not self.tie_order:
                  self.tie_order = list(self.candidates)
              self.T = None  # Will be computed once
          
          def compute_T(self) -> int:
              """
              RULE: T Calculation
              T = median of all approval cutoffs (upper median if even)
              Computed ONCE at the start, stays fixed
              """
              if self.T is not None:
                  return self.T
                  
              a_list = []
              for g in self.groups:
                  a_list.extend([g.a] * g.n)  # Expand by voter count
              a_list.sort()
              
              m = len(a_list)
              if m == 0:
                  self.T = 0
              elif m % 2 == 1:
                  self.T = a_list[m//2]
              else:
                  self.T = a_list[m//2]  # Upper median for even
              
              self.audit.log(
                  "INITIALIZATION", 
                  "T Calculation (Median of Approval Cutoffs)",
                  f"Median of {m} voters' approval cutoffs = {self.T}",
                  {"all_cutoffs": a_list, "T": self.T}
              )
              return self.T
          
          def tally_active_approvals(self, current_winners: List[str] = None) -> Dict[str, int]:
              """
              RULE: Approval Tallying with Dynamic Prefix Tightening
              Count active approvals from all groups (with dynamic H adjustment for satiated voters)
              """
              tallies = {c: 0 for c in self.candidates}
              
              for i, g in enumerate(self.groups):
                  approved = g.active_approvals(current_winners)
                  for c in approved:
                      if c in self.candidates:  # Only count if still in race
                          tallies[c] += g.n
              
              # Only return tallies for current candidates
              return {c: tallies[c] for c in self.candidates}
          
          def head_to_head(self, a: str, b: str, current_winners: List[str] = None) -> int:
              """
              RULE: Head-to-Head Tiebreaking with Dynamic Prefix
              Use rankings from voters who can influence this comparison
              """
              a_score = b_score = 0
              
              for g in self.groups:
                  pref = g.prefers(a, b, current_winners)
                  if pref > 0:
                      a_score += g.n
                  elif pref < 0:
                      b_score += g.n
              
              if a_score > b_score: return 1
              if b_score > a_score: return -1
              return 0
          
          def select_provisional_winners(self, K: int, iteration: int, previous_winners: List[str] = None, 
                                         non_satiating_candidates: Set[str] = None) -> List[str]:
              """
              RULE: Pick Provisional Winners
              1. Tally active approvals (based on previous winners for dynamic prefix)
              2. Take top K by approval count
              3. Tiebreak: prioritize non-flagged over RUS-flagged, then head-to-head, then fixed order
              """
              if non_satiating_candidates is None:
                  non_satiating_candidates = set()
                  
              tallies = self.tally_active_approvals(previous_winners)
              
              self.audit.log(
                  f"K={K} ITER-{iteration}",
                  "Active Approval Tally",
                  f"Current approval counts (with previous winners: {previous_winners})",
                  {"tallies": tallies, "non_satiating": list(non_satiating_candidates)}
              )
              
              # Group by approval count
              by_tally = defaultdict(list)
              for c, t in tallies.items():
                  by_tally[t].append(c)
              
              # Sort tallies descending
              sorted_candidates = []
              for tally in sorted(by_tally.keys(), reverse=True):
                  tied = by_tally[tally]
                  
                  if len(tied) == 1:
                      sorted_candidates.extend(tied)
                  else:
                      # Tiebreak needed
                      self.audit.log(
                          f"K={K} ITER-{iteration}",
                          "Tiebreaking",
                          f"{len(tied)} candidates tied with {tally} approvals",
                          {"tied_candidates": tied}
                      )
                      
                      # Sort by: (1) non-flagged before flagged, (2) head-to-head wins, (3) fixed order
                      def tiebreak_key(cand):
                          is_flagged = 1 if cand in non_satiating_candidates else 0
                          wins = sum(1 for other in tied if other != cand and self.head_to_head(cand, other, previous_winners) > 0)
                          return (is_flagged, -wins, self.tie_order.index(cand))
                      
                      tied_sorted = sorted(tied, key=tiebreak_key)
                      sorted_candidates.extend(tied_sorted)
              
              winners = sorted_candidates[:K]
              self.audit.log(
                  f"K={K} ITER-{iteration}",
                  "Provisional Winners Selected",
                  f"Top {K} candidates by approval with tiebreaking",
                  {"winners": winners}
              )
              
              return winners
          
          def apply_satiation(self, winners: List[str], T: int, non_satiating_candidates: Set[str]) -> Tuple[List[int], Set[str]]:
              """
              RULE: Median-T Satiation with Candidate-wise RUS
              Returns (list of newly satiated group indices, set of consensus triggers identified)
              """
              # First, identify consensus triggers (candidates that would satiate ALL unsaturated voters)
              # Skip candidates already flagged as non-satiating
              unsaturated_groups = [g for g in self.groups if not g.saturated]
              consensus_triggers = set()
              
              if unsaturated_groups:
                  # Check each winner to see if it's a consensus trigger
                  for w in winners:
                      # Skip already-flagged candidates
                      if w in non_satiating_candidates:
                          continue
                          
                      is_consensus = True
                      for g in unsaturated_groups:
                          pos = g.get_rank_position(w)
                          threshold = min(T, g.a)
                          if pos >= threshold:  # This candidate doesn't trigger this voter
                              is_consensus = False
                              break
                      if is_consensus:
                          consensus_triggers.add(w)
                  
                  if consensus_triggers:
                      self.audit.log(
                          f"K={len(winners)}",
                          "New Consensus Triggers Identified",
                          f"Candidates {consensus_triggers} would satiate ALL remaining voters",
                          {"consensus_triggers": list(consensus_triggers)}
                      )
              
              # Now apply satiation, but ignore consensus triggers as satiation causes
              newly_satiated = []
              
              for gi, g in enumerate(self.groups):
                  if g.saturated:
                      continue
                      
                  # Find highest-ranked winner that's NOT a consensus trigger or already non-satiating
                  ignore_for_satiation = consensus_triggers | non_satiating_candidates
                  
                  # Check if this voter would satiate based on non-ignored winners
                  threshold = min(T, g.a)
                  for w in winners:
                      if w in ignore_for_satiation:
                          continue
                      pos = g.get_rank_position(w)
                      if pos < threshold:
                          # This voter satiates based on winner w
                          H = g.top_in_set(winners)  # Still use actual top winner for prefix
                          g.saturated = True
                          g.satiation_prefix_end = H
                          newly_satiated.append(gi)
                          
                          prefix = g.get_prefix_candidates()
                          self.audit.log(
                              f"K={len(winners)}",
                              f"Group {gi+1} Satiated",
                              f"H={H}, satiated via {w}, retaining prefix of {len(prefix)} candidates",
                              {"group_size": g.n, "H": H, "trigger": w, "prefix": list(prefix)}
                          )
                          break
              
              return newly_satiated, consensus_triggers
          
          def assign_power(self, winners: List[str]) -> Dict[str, int]:
              """
              RULE: Power Assignment
              Each voter assigns power to their highest-ranked winner
              """
              power = {w: 0 for w in winners}
              
              for g in self.groups:
                  rep = g.top_in_set(winners)
                  if rep:
                      power[rep] += g.n
              
              return power
          
          def eliminate_zero_power(self, winners: List[str], power: Dict[str, int]) -> List[str]:
              """
              RULE: Zero-Power Elimination
              Remove winners with no voter support
              """
              eliminated = [w for w in winners if power[w] == 0]
              
              if eliminated:
                  self.audit.log(
                      f"K={len(winners)}",
                      "Zero-Power Elimination",
                      f"Removing {len(eliminated)} candidates with no support",
                      {"eliminated": eliminated}
                  )
                  
                  # Remove from candidate list
                  self.candidates = [c for c in self.candidates if c not in eliminated]
                  self.tie_order = [c for c in self.tie_order if c in self.candidates]
                  
                  # Update satiation prefixes if needed
                  for g in self.groups:
                      if g.satiation_prefix_end in eliminated:
                          # Find next candidate in prefix that's still valid
                          prefix_cands = []
                          for c in g.rank:
                              if c in self.candidates:
                                  prefix_cands.append(c)
                              if c == g.satiation_prefix_end:
                                  break
                          
                          # Update H to last valid candidate in prefix, or None
                          g.satiation_prefix_end = prefix_cands[-1] if prefix_cands else None
                          if g.satiation_prefix_end is None:
                              g.saturated = False  # No valid prefix anymore
              
              return eliminated
          
          def run_for_K(self, K: int) -> Tuple[List[str], Dict[str, int]]:
              """
              Main algorithm for selecting K winners with candidate-wise RUS and dynamic prefix tightening
              """
              self.audit.log(
                  f"K={K}",
                  "Starting K-Selection",
                  f"Selecting {K} winners from {len(self.candidates)} candidates",
                  {"candidates": self.candidates[:10] if len(self.candidates) > 10 else self.candidates}
              )
              
              T = self.compute_T()
              non_satiating_candidates = set()  # Specific candidates marked as non-satiating for this K
              
              iteration = 0
              last_winners = []  # Start with empty set
              last_power = None
              max_iterations = 100
              
              while iteration < max_iterations:
                  iteration += 1
                  
                  # Step 1: Pick provisional winners based on previous winners (for dynamic prefix)
                  # and considering non-satiating candidates for tiebreaking
                  winners = self.select_provisional_winners(K, iteration, 
                                                           last_winners if iteration > 1 else None,
                                                           non_satiating_candidates)
                  
                  # Step 2: Apply satiation with candidate-wise RUS
                  newly_satiated, found_consensus_triggers = self.apply_satiation(winners, T, non_satiating_candidates)
                  
                  # Compute newly added consensus triggers (not already known)
                  newly_added_triggers = found_consensus_triggers - non_satiating_candidates
                  
                  # Add newly identified consensus triggers to non-satiating set
                  if newly_added_triggers:
                      non_satiating_candidates.update(newly_added_triggers)
                      self.audit.log(
                          f"K={K} ITER-{iteration}",
                          "Non-satiating Candidates Updated",
                          f"Added {newly_added_triggers} to non-satiating set",
                          {"newly_added": list(newly_added_triggers), 
                           "total_non_satiating": list(non_satiating_candidates)}
                      )
                  
                  # Step 3: Assign power
                  power = self.assign_power(winners)
                  self.audit.log(
                      f"K={K} ITER-{iteration}",
                      "Power Assignment",
                      "Voter support distribution",
                      {"power": power}
                  )
                  
                  # Step 4: Eliminate zero-power winners
                  eliminated = self.eliminate_zero_power(winners, power)
                  
                  # Check for stabilization - only count NEWLY ADDED triggers as a change
                  changed = (winners != last_winners or 
                            power != last_power or 
                            newly_satiated or 
                            bool(newly_added_triggers) or  # Only newly added, not all found
                            eliminated)
                  
                  if not changed:
                      self.audit.log(
                          f"K={K}",
                          "STABILIZED",
                          f"No changes in iteration {iteration}",
                          {"final_winners": winners, "final_power": power}
                      )
                      break
                  
                  last_winners = winners
                  last_power = power
              
              return winners, power
      
      def run_election_with_audit(title: str, candidates: List[str], groups: List[VoterGroup], 
                                 max_K: int, verbose: bool = True):
          """
          Run complete election from K=1 to max_K with detailed auditing
          """
          print("="*80)
          print(f"ELECTION: {title}")
          print("="*80)
          
          if verbose:
              print("\nINITIAL CONFIGURATION:")
              print(f"Candidates: {candidates}")
              print("\nVoter Groups:")
              for i, g in enumerate(groups):
                  print(f"  Group {i+1}: {g.n} voters")
                  print(f"    Ranking: {' > '.join(g.rank)}")
                  print(f"    Approves top {g.a}: {list(g.rank[:g.a])}")
              print()
          
          # Create fresh election (groups will carry satiation forward between K values)
          el = Election(
              candidates=list(candidates),
              groups=groups,  # These will maintain state across K
              tie_order=list(candidates),
              audit=AuditLog(verbose=verbose)
          )
          
          results = []
          for K in range(1, max_K + 1):
              if verbose:
                  print(f"\n{'='*60}")
                  print(f"SOLVING FOR K={K}")
                  print(f"{'='*60}\n")
              
              winners, power = el.run_for_K(K)
              
              results.append({
                  "K": K,
                  "Winners": winners,
                  "Power": power,
                  "Power_str": ", ".join(f"{w}:{p}" for w, p in power.items())
              })
              
              if verbose:
                  print(f"\nRESULT FOR K={K}:")
                  print(f"  Winners: {winners}")
                  print(f"  Power: {power}")
          
          return results
      
      # Example scenarios
      def demo_scenario():
          """Run a demonstration scenario"""
          resuls = None
          if True:
              # Scenario: Three factions with compromise candidate U
              candidates = ["A", "B", "C", "U", "D"]
              groups = [
                  VoterGroup(34, ["A", "U", "B", "C", "D"], 2),
                  VoterGroup(33, ["B", "U", "A", "C", "D"], 2), 
                  VoterGroup(33, ["C", "U", "A", "B", "D"], 2),
              ]
              
              results = run_election_with_audit(
                  "Three Factions with Universal Compromise",
                  candidates,
                  groups,
                  max_K=3,
                  verbose=True
              )
              
              # Summary table
              print("\n" + "="*80)
              print("SUMMARY")
              print("="*80)
              df = pd.DataFrame([{
                  "K": r["K"],
                  "Winners": ", ".join(r["Winners"]),
                  "Power Distribution": r["Power_str"]
              } for r in results])
              print(df.to_string(index=False))
          elif True:
              pass
          
          return results
      
      if __name__ == "__main__":
          demo_scenario()
      
      # -------------------------------
      # More example elections
      # -------------------------------
      
      def scenario_majority_clones_vs_two_minorities(verbose=False):
          """
          Majority (52) spreads support across 3 near-clone heads (A1,A2,A3).
          Two cohesive minorities (28 for B-first, 20 for C-first).
          Stress: clone-packing + whether minorities still seat as K grows.
          Expectation: As K increases, A-bloc locks to one/two A* winners,
          while B and C each capture representation; U (none here) not present.
          """
          candidates = ["A1","A2","A3","B","C","D"]
          groups = [
              VoterGroup(18, ["A1","A2","A3","B","C","D"], 3),
              VoterGroup(17, ["A2","A3","A1","B","C","D"], 3),
              VoterGroup(17, ["A3","A1","A2","B","C","D"], 3),
              VoterGroup(28, ["B","C","A1","A2","A3","D"], 2),
              VoterGroup(20, ["C","B","A1","A2","A3","D"], 2),
          ]
          return run_election_with_audit(
              "Majority Clones vs Two Minorities",
              candidates, groups, max_K=4, verbose=verbose
          )
      
      def scenario_heterogeneous_T(verbose=False):
          """
          Heterogeneous approval cutoffs so median-T matters.
          30 voters approve only top-1; 30 approve top-2; 40 approve top-3.
          Expectation: T=2 (upper median). Compromise M is high but not always seated
          unless supported by multiple blocs; dynamic tightening should peel off approvals.
          """
          candidates = ["A","B","C","M","D","E"]
          groups = [
              VoterGroup(30, ["A","M","B","C","D","E"], 1),
              VoterGroup(30, ["B","M","A","C","D","E"], 2),
              VoterGroup(40, ["C","M","B","A","D","E"], 3),
          ]
          return run_election_with_audit(
              "Heterogeneous T (1/2/3) with Compromise M",
              candidates, groups, max_K=3, verbose=verbose
          )
      
      def scenario_big_majority_plus_consensus_U(verbose=False):
          """
          Big majority (60) vs minority (40), with widely approved compromise U.
          Approvals: Majority approves {A, U}; Minority approves {B, U}.
          Expectation: K=1 likely U; as K grows, blocs lock to A and B and U falls back.
          """
          candidates = ["A","B","U","C","D"]
          groups = [
              VoterGroup(60, ["A","U","B","C","D"], 2),
              VoterGroup(40, ["B","U","A","C","D"], 2),
          ]
          return run_election_with_audit(
              "Big Majority vs Minority with Consensus U",
              candidates, groups, max_K=3, verbose=verbose
          )
      
      def scenario_two_parties_plus_centrist(verbose=False):
          """
          Two parties (45/45) with distinct heads (A,B) and a centrist M broadly approved.
          Small 10-voter group prefers a reformer R (also approves M).
          Expectation: K=1 often M; K=2/3 should seat A and B; R may or may not make it at K=3/4.
          """
          candidates = ["A","B","M","R","D"]
          groups = [
              VoterGroup(45, ["A","M","B","R","D"], 2),
              VoterGroup(45, ["B","M","A","R","D"], 2),
              VoterGroup(10, ["R","M","A","B","D"], 2),
          ]
          return run_election_with_audit(
              "Two Parties + Centrist + Reformer",
              candidates, groups, max_K=4, verbose=verbose
          )
      
      def scenario_clone_sprinkling_attempt(verbose=False):
          """
          Simulate a 'decoy sprinkling' attempt: the 55-voter bloc splits into
          three sub-blocs each ranking a different decoy D* first, all approving their real champion X as well.
          Minority prefers Y and Z. Tests candidate-wise RUS + tightening against seat-packing.
          """
          candidates = ["X","Y","Z","D1","D2","D3","W"]
          groups = [
              VoterGroup(19, ["D1","X","D2","D3","Y","Z","W"], 2),
              VoterGroup(18, ["D2","X","D3","D1","Y","Z","W"], 2),
              VoterGroup(18, ["D3","X","D1","D2","Y","Z","W"], 2),
              VoterGroup(25, ["Y","Z","X","D1","D2","D3","W"], 2),
              VoterGroup(20, ["Z","Y","X","D1","D2","D3","W"], 2),
          ]
          return run_election_with_audit(
              "Clone Sprinkling Attempt (Decoys D1–D3)",
              candidates, groups, max_K=4, verbose=verbose
          )
      
      def scenario_many_seats_party_listish(verbose=False):
          """
          Party-list-ish: A:40, B:35, C:25 with some cross-approval for a shared governance U.
          Test proportionality as K increases (K up to 5).
          """
          candidates = ["A1","A2","B1","B2","C1","U","D"]
          groups = [
              VoterGroup(40, ["A1","U","A2","B1","C1","B2","D"], 2),
              VoterGroup(35, ["B1","U","B2","A1","C1","A2","D"], 2),
              VoterGroup(25, ["C1","U","A1","B1","A2","B2","D"], 2),
          ]
          return run_election_with_audit(
              "Many Seats, Party-list-ish with Shared U",
              candidates, groups, max_K=5, verbose=verbose
          )
      
      def scenario_tie_sensitivity(verbose=False):
          """
          Tight ties across factions; tie order matters.
          Use symmetric 33/33/34 with shared approvals to force frequent ties.
          Verify your tie-break ('unflagged before flagged', then H2H, then fixed).
          """
          candidates = ["A","B","C","M","N"]
          groups = [
              VoterGroup(34, ["A","M","B","C","N"], 2),
              VoterGroup(33, ["B","M","C","A","N"], 2),
              VoterGroup(33, ["C","M","A","B","N"], 2),
          ]
          return run_election_with_audit(
              "Tie Sensitivity (Symmetric 34/33/33)",
              candidates, groups, max_K=3, verbose=verbose
          )
      
      def scenario_median_T_equals_one(verbose=False):
          """
          Force T=1: simple-majority may try to set median approval to 1.
          Everyone approves exactly one (a_i=1), different heads.
          Expectation: behaves close to STV-ish seat allocation by top ranks; 
          dynamic tightening is trivial but RUS can still mute universal names.
          """
          candidates = ["A","B","C","U","D"]
          groups = [
              VoterGroup(40, ["A","U","B","C","D"], 1),
              VoterGroup(35, ["B","U","A","C","D"], 1),
              VoterGroup(25, ["C","U","A","B","D"], 1),
          ]
          return run_election_with_audit(
              "Median T = 1 (All a_i=1)",
              candidates, groups, max_K=3, verbose=verbose
          )
      
      def run_more_examples(verbose=False):
          """
          Run all the example scenarios above.
          Set verbose=True for full step-by-step audits.
          """
          all_results = []
      
          print("\n" + "="*80)
          print("SCENARIO 1: Majority Clones vs Two Minorities")
          print("="*80)
          all_results.append(scenario_majority_clones_vs_two_minorities(verbose=verbose))
      
          print("\n" + "="*80)
          print("SCENARIO 2: Heterogeneous T (1/2/3) with Compromise M")
          print("="*80)
          all_results.append(scenario_heterogeneous_T(verbose=verbose))
      
          print("\n" + "="*80)
          print("SCENARIO 3: Big Majority vs Minority with Consensus U")
          print("="*80)
          all_results.append(scenario_big_majority_plus_consensus_U(verbose=verbose))
      
          print("\n" + "="*80)
          print("SCENARIO 4: Two Parties + Centrist + Reformer")
          print("="*80)
          all_results.append(scenario_two_parties_plus_centrist(verbose=verbose))
      
          print("\n" + "="*80)
          print("SCENARIO 5: Clone Sprinkling Attempt (Decoys D1–D3)")
          print("="*80)
          all_results.append(scenario_clone_sprinkling_attempt(verbose=verbose))
      
          print("\n" + "="*80)
          print("SCENARIO 6: Many Seats, Party-list-ish with Shared U")
          print("="*80)
          all_results.append(scenario_many_seats_party_listish(verbose=verbose))
      
          print("\n" + "="*80)
          print("SCENARIO 7: Tie Sensitivity (Symmetric 34/33/33)")
          print("="*80)
          all_results.append(scenario_tie_sensitivity(verbose=verbose))
      
          print("\n" + "="*80)
          print("SCENARIO 8: Median T = 1 (All a_i=1)")
          print("="*80)
          all_results.append(scenario_median_T_equals_one(verbose=verbose))
      
          # Print compact summaries for each scenario’s last run
          print("\n" + "="*80)
          print("SUMMARY (compact)")
          print("="*80)
          for bundle in all_results:
              # bundle is the list returned by run_election_with_audit (one dict per K)
              df = pd.DataFrame([{
                  "K": r["K"],
                  "Winners": ", ".join(r["Winners"]),
                  "Power": r["Power_str"]
              } for r in bundle])
              print(df.to_string(index=False))
              print("-"*80)
      
          return all_results
      
      
      posted in Voting Theoretic Criteria
      C
      cfrank
    • RE: Idea for truly proportional representation

      @poppeacock here is another toy example, which shows that while seating may be pluralistic/consensus based, the ultimate power-based representation may be strongly majoritarian.

      Example: A “universally approved” compromise seat has ~0 power.

      N = 3 seats, 5 candidates, 100 voters
      • 40: A > M | B C D (approve {A,M})
      • 35: B > M | A C D (approve {B,M})
      • 25: C > B > M > A | D (approve {C,B,M,A})

      Approvals: M=100, A=65, B=60, C=25 → Seated: {M, A, B}.
      Strength:
      • 40 A-first → A
      • 35 B-first → B
      • 25 C-first → among seated {M,A,B} they rank B > M > A → B
      Final: A=40, B=60, M=0.

      The only universally approved candidate M is seated, but powerless, and the majority coalition has monopolized all legitimate majority-based bargaining power through representative B.

      Maybe that’s OK with so few seats? I’m just trying to think through the implications of the system. My guess is that this same kind of power-based majoritarianism may still cause issues even with more seats available. I could be wrong.

      However, it’s important to note that the proportionality/plurality/consensus aspect hinges strongly and primarily on the seating method. Using approval, for example, makes the seats susceptible to strategic nomination of clones, where a majority faction could dominate all seats by approving many clone candidates.

      So the method would need to rely on other more specialized PR-based seating algorithms. In that case, the majoritarian power allocation aspect becomes a questionable design choice, since it seems to potentially undermine the very pluralism that the PR-based seating is meant to achieve.

      The majoritarianism becomes less of an issue though if decisions require supermajorities of power (as it always does).

      SUGGESTED PRELIMINARY ADJUSTMENTS:

      This is out of my wheelhouse, but possibly this could be addressed by removing candidates with zero power, and enabling runners-up to take their seats. And in the approval case, perhaps also allocating approval to the top-seated candidate. This is a suggested direction for using approval:

      1. Select provisional open seat allocations by approval. If there are approval ties, resolve them by a head-to-head match if possible, otherwise by a predetermined sort order of the candidates.
      2. If any voter has their top-ranked candidate provisionally seated, remove all other approval support they give to other candidates, and discount any rankings from their ballot in subsequent head-to-head matches. Allocate provisional power.
      3. If there are any filled seats with zero power, completely remove the provisionally seated candidates with zero power from the election and vacate their seats.
      4. Repeat from step (1) using the updated approval and ranking profiles, until there is no change in the seating or power allocation.

      EDIT: There is a serious issue that remains with using approval even after the adjustments above: A majority could still crowd out seats by coordinating multiple “threads” of ballot with each clone as the head of the thread. It seems that the seating algorithm needs to be something different from straight approval.

      Here’s a thought though—what if the approval seating process was performed with an increasing seat number schedule? So for example, first, there is only one seat, then the election iterates until stability. Then, there are two seats, etc. This would disallow the vulnerability we just discovered, I believe, because the voters whose first choice got represented could no longer contribute to bloc approvals.

      I tested this version out, and it does pretty well.

      UPDATED SUGGESTIONS FOR ADJUSTMENT:

      I assume that we would want to use an approval-based mechanism to determine the seat allocations.

      First, if the total number of seats is N, then to avoid majoritarian seat-packing by bloc approval, rather than a single seat allocation of all seats at once, there should be a sequence of elections for K seats, where K is initialized at 1 and increases incrementally to N.

      The other rules would be as follows:

      1. Select provisional open seat allocations by approval. If there are approval ties, resolve them by a head-to-head match if possible, otherwise by a predetermined sort order of the candidates.
      2. If any voter has their top-ranked candidate provisionally seated, remove all other approval support they give to other candidates, and discount any rankings from their ballot in subsequent head-to-head matches. Allocate provisional power according to your rule (top-ranked seated candidate).
      3. If there are any filled seats with zero power, completely remove the provisionally seated candidates with zero power from the election and vacate their seats.
      4. Repeat from step (1) using the updated approval and ranking profiles, until there is no change in the seating or power allocation.

      This iteration will stabilize the seating for the value of K. Afterwards, proceed by increasing K by 1, maintaining the changes to the ballots that were incurred sequentially (ex: as in step (2), where some approvals and rankings are disregarded), and stopping after stabilization for K=N.

      The "top-rank satiation" principle of ("has their top-ranked candidate provisionally seated") is something that can be exploited by sprinkling in decoy candidates as top-ranks. It requires a pool of many distinct decoys and coordination, but is possible. An alternative is to satiate a voter when any of their approved candidates is seated, but that may "over-satiate" voters. Or, voters could indicate their own satiation thresholds. Or vote on one 😆 For instance, the satiation threshold could be set as the mean or median number of approved candidates. In fact, choosing the median will automatically eliminate the possibility of majoritarian decoy sprinkling.

      However, now the satiation can "stagnate." If all voters are satiated, probably the whole satiation state needs to refresh somehow. Food for thought!

      posted in Voting Theoretic Criteria
      C
      cfrank
    • RE: Idea for truly proportional representation

      EDIT: I was reasonably skeptical at first, but I think I’m buying into the idea more, as long as certain safeguards are in place. Specifically, if the seating method is well-designed, and perhaps if supermajorities are required for certain decisions, then it seems as though it could be a very effective method. It’s a simple and interesting idea that looks promising, to me. Some pathologies do exist, including several recognized by the post's author, but I think I was able to address several of them with adjustments to the seating process and some additional rules. @Toby-Pereira I wonder what you think about this, since you have deeper knowledge of PR systems.

      My initial critique is as follows: @poppeacock I feel as though aspects are unclear. The manner in which the top candidates are selected, i.e. what is meant by the “seating tally” is not fully specified. This is recognized by the post's author.

      Furthermore, the method of power allocation can cause peculiar effects that are essentially majoritarian. As a simple example, if somehow there is a unanimously top-ranked candidate, then all power will be given solely to that top-ranked candidate, and the other seats will be completely powerless. This itself is a strange possibility, and I feel as though there are worse pathologies that may emerge, but I haven’t given it more than cursory consideration just now. This is also recognized by the post's author.

      Just generally, for any readers, considering the full implications of a new voting system can be very complicated, especially for people not practiced in analyzing them. I've been trying to get better at it over the years, but I'm not an expert. It’s good to try new things and think about the implications, but I think it’s generally good to err on the skeptical side, except regarding well-established, deeply analyzed methods with proven properties.

      If the author can write a transparent computer program that takes in ballots of a specified format and then returns a result according to their methodology, they probably should. Looking at some toy examples, though, it does seem to yield some interesting/compelling results. The fact that some seats may be vacated of power may be just a peculiarity.

      posted in Voting Theoretic Criteria
      C
      cfrank
    • Bioethics of Informed Consent

      The notion of consent is of foundational concern in political philosophy and has come up many times in this forum over the years. I wanted to share this lecture from a bioethics perspective, as food for thought:

      Youtube Video

      posted in Ethics
      C
      cfrank
    • RE: How do we technically give consent to our governments

      @tec referenda are interesting as an example, I would say they are more efficient in a sense, but the cost of that can be coherency. For example, referenda in California have led to incoherent policies, because the public often wants to have its cake and eat it too: the public wants service X, but simultaneously doesn’t want to pay for it, so they vote for X but also vote against tax increases that would pay for X. This effectively forces the government to borrow to reconcile public demands, which leads to debt that the public also doesn’t want.

      The government then gets criticized for borrowing, but in a sense, that is misplaced responsibility—the direct translation of an incoherent set of policies is the source of the issue, and borrowing is a symptom. This shows that representatives also serve the role of taking on coherent responsibility for coherent policy decisions, but citizens’ policy referenda can undermine that role in the kind of situation I described. Probably, the effects of this kind are less severe or even negligible in smaller, more internally cohesive populations like in Switzerland.

      Feedback is absolutely necessary, and structural issues as you indicate are major obstacles. If SAVE is a policy proposal generator, it seems to serve the role of a structured public forum. Is that accurate in your view? It seems like a more democratized form of a special interest group. How would the interests become translated into policy?

      posted in Political Theory
      C
      cfrank
    • RE: Fixing Participation Failure in “Approval vs B2R”

      I made a bit of conceptual progress with a potential proof (or route to a counterexample?) of participation for approval vs. B2R.

      The setup is as follows: E is an electorate, C is a candidate set, and v is some single new voter that when combined with E forms E'=EU{v}.
      The electoral processes of interest are then E(C) and E'(C), where we hope that participation is satisfied in the sense that if the winner of E(C) is W, and the winner of E'(C) is W', then v does not strictly prefer W to W'.

      We can consider the sequence of B2R losers L1, L2, ... Lk, and the corresponding L1', L2', ... Lk'.
      Then since under B2R the first k losers can never include the top-sorted candidate who is the adversary of the B2R survivor, it follows that the participation property will propagate by an inductive hypothesis if at any point (as in for some k), the sets {L1, L2, .. Lk} and {L1', L2', ... Lk'} coincide.
      It could also be possibly useful to know that B2R is equivalent to BNR. Furthermore, the methodology is only really of interest when there are more than two candidates.

      In any case, I think this method makes sure that the winner is either a highly approved candidate, or a candidate that a majority prefers over a highly approved candidate, which to me is an interesting guarantee. I still have not been able to find an example of participation failure... I have found instances though where the Condorcet loser is elected. Still, the Condorcet loser criterion seems only possible to fail by small chance when small electorates vote over very few candidates.

      posted in Single-winner
      C
      cfrank
    • RE: How do we technically give consent to our governments

      @tec I agree, most informed, pro-democracy people demand structural change to improve faithfulness of policy as well as more civic engagement. Direct policy-focused voting is ideal in principle, but on a large scale, it could be very inefficient. In my conception, the purpose of representatives is essentially to specialize in the aggregation and fusion of policy information and then interface with policy decisions. Unfortunately, removing that layer of abstraction might lead to chaos in various ways.

      I think your question/title of this post gets to the practical heart of what voting theory is about. Since we're concerned with consent here, what do you think the "ingredients" of consent might be? For example, I roughly imagine that to give consent, an individual needs adequate information about a proposal, sufficient resources to process that information about it, and mitigated or minimized "unnecessary" conflicts of interest. Those are all normative constructs, but the idea is that maybe we can decompose a complex construct like consent into simpler pieces, and then examine how those pieces might or might not hang together in the right way.

      More thought can make the construct of consent more complicated and multidimensional, It’s definitely something I want to read more about. I think core “defeaters” of consent would be sufficient severity of avoidable conflict between interests, plus epistemic aspects, and maybe others not considered.

      To me, some of the main issues with our current system is that we lack various aspects of that triangle. Individually, we don't have enough information, we hardly have sufficient resources to process what information we do have, and conflicts of interest are baked right into our institutions. Including the vote-for-one system, where the conflicts of interest are obvious, it's essentially extortion. In my view, representatives should function to address the first two points about information processing. What are your thoughts?

      Also no worries about prior engagements with conference and delay, I'll be frank in that I also haven't been able to give your proposal more than cursory consideration because of my own business, but as we and possibly others discuss, I'm sure we'll be able to prepare more food for thought. On the surface, it seems like your proposal does address the ingredients of consent I outlined, but efficiency and practical adoption may be significant issues. Not insurmountable though, and you may have already considered as much.

      posted in Political Theory
      C
      cfrank
    • RE: How do we technically give consent to our governments

      @tec I think it sounds like it would yield good results. I think we might need to have a more step-by-step dialogue about this to get a real understanding of what you’re proposing.

      One thought that comes to mind is that while multiple rounds of voting is simple on its face, it’s also a radical adjustment to the approach we are currently entrenched in. For that to fly, it would require significant efforts to educate and familiarize the public with the concept. Generally speaking, the simpler a system is, the more likely it is to be adopted.

      So my point is that I do like the principle, but personally I see it as something that could only realistically exist after a more foundational adjustment is made for it to develop on top of. Does that make sense?

      For example, it seems to require adoption a priori of an approval voting paradigm. That in itself is a significant hurdle.

      posted in Political Theory
      C
      cfrank
    • Integration with Existing Infrastructure

      I was discussing voting systems with a friend, and he was curious about how alternative voting systems would integrate into existing infrastructure such as districting and the electoral college.

      This seems like it could be a ground-up, potentially idiosyncratic thing, but we have seen adoptions in certain states of alternative systems and they have obviously integrated into a national level system. My curiosity is about the logistics of this on a larger scale, and if there is a clear roadmap that offers generality of scope for other states to follow suit.

      I’m wondering if people have more knowledge on this subject, and if they would be willing to share or collect resources here for others to investigate. I’ll probably look into this myself. I’m also opening a subject about historical examples of alternative systems, please chime in with any thoughts or considerations!

      posted in Election Policy and Reform
      C
      cfrank