跳转到主要内容

STAR选举系统及其他选举的计票器

项目描述

starvote

STAR投票选举计票器

版权所有 © 2023 Larry Hastings

# test badge # coverage badge # python versions badge

STAR投票 是一种相对较新的 “选举系统”——一种进行选举的方法。STAR投票很简单——投票简单,计票简单。虽然完全公平和完美的选举系统是不可能的,但STAR投票的方法做出了合理的权衡,避免了最严重的陷阱。真是太好了!

此模块 starvote 实现了STAR投票计票器。它需要Python 3.7或更高版本,但也支持CPython 3.6。(starvote 依赖于字典保留插入顺序,这自Python 3.7起得到了保证,但恰好也适用于CPython 3.6。)

功能

  • 支持五种 选举系统

    • STAR投票,一种新的单胜者投票系统。
    • 区块STAR投票,STAR投票的多胜者变体,用最热门的候选人填满多个席位。
    • 分配分数投票,一种 比例代表 选举系统,迄今为止是唯一获得官方授权的“比例STAR投票”方法。
    • 重新加权范围投票(简称“RRV”),一种替代性比例代表选举系统。RRV不是STAR变体,但它像STAR一样是一种 得分投票 的形式。因此,选票和给选民的说明与STAR-PR选举相同。RRV算法比比例STAR投票简单得多,而且它的“永不丢弃选民”的方法很有吸引力。
    • 顺序花费得分,基于得分的比例代表制选举系统的一种。
  • 实现了STAR选举和集团STAR选举的官方平局决断协议

  • 如果选举(或选举的一轮)以平局结束,提供用户可配置的最终平局决断机制。

    • 默认的平局决断机制是在运行选举之前随机打乱所有候选人,然后根据列表中最早出现的候选人来打破平局。
    • 或者,您可以根据需要随机选择平局候选人(或候选人)来打破平局。
    • 您还可以实现自己的自定义平局决断。
    • 如果您指定不应有平局决断,并且选举以不可打破的平局结束,将引发一个UnbreakableTieError异常。
  • 所有选举统计计算仅使用整数和分数进行。这保证了结果在所有平台上100%一致和准确。浮点数舍入错误是不可能的——因为永远不会使用浮点数!

  • 支持使用https://star.vote/格式运行在CSV文件中指定的选举。

  • 还支持使用方便的称为starvote格式的自定义文件格式运行在指定中的选举。

  • starvote 2.0.3在所有支持的版本上通过了其测试套件,100%覆盖。

  • starvote没有外部依赖。

STAR Voting快速入门

当您使用STAR Voting投票时,您的选票看起来像这样

Amy    0 1 2 3 4 5
Brad   0 1 2 3 4 5
Chuck  0 1 2 3 4 5
Darcy  0 1 2 3 4 5

投票时,为每位候选人评分0到5分。5分表示您最喜欢他们,0分表示您最不喜欢他们。(如果您没有选择一个分数,那就等同于0分。)如果您给两个候选人相同的分数,这意味着您对他们同等喜欢——您对他们之间没有偏好。

统计选举非常简单!您应用的是STAR方法:评分,然后自动跑票。

在第一轮,即评分轮,您将所有候选人的分数相加。得分最高的两位候选人自动晋级下一轮。

在第二轮,即自动跑票轮,您检查每一张选票,看看他们更喜欢哪两位剩余的候选人。如果一个得分更高,那么该选票更喜欢该候选人。如果选票对两个候选人的评分相同,则他们没有偏好。得票更多的候选人获胜。就这么简单!

请注意——您始终检查每一张选票。STAR Voting永远不会丢弃选票。当您投票时,您的投票始终重要,每一步都很重要。

STAR Voting有什么好处?

选举系统是一个出人意料深的主题。它们已经被研究了数百年,有许多不同的方法。选举系统有许多理想和不良的特性。而且坏消息是:不可能存在一个最佳可能的投票系统。有一些相互排斥的理想特性,还有一些理想的特性会带来负面影响。您不能制作一个适合所有人的最佳系统,以避免所有问题——每个选举系统都必须是一种妥协。维基百科有关于这个主题的一篇全面的文章

STAR Voting避免了选举系统最严重的问题。选择的不良特性是尽可能的最坏选择。

以下是STAR Voting具有的一些理想特性

  • 它是单调的。给候选人更高的分数永远不会伤害他们,给候选人更低的分数永远不会帮助他们。(是的,这并不是所有投票系统都如此。越来越受欢迎的即时跑票投票就失败了;矛盾的是,通过给候选人一个更高的分数,可能会对他们造成伤害。)
  • 它是可解的。[可解性标准]。平局的可能性很小。
  • 它符合多数 loser 标准。如果一个多数的候选人最不喜欢某个候选人,那么这个候选人永远不会在 STAR Voting 选举中获胜。

但以下是 STAR Voting 所不具备的一些理想特性

  • 它不是Condorcet 方法,这是选举系统的一个非常特殊的属性。假设你有一个三个候选人 A、B 和 C 的选举。你要求每位选民进行三次一对一投票:"你更喜欢 A 还是 B?"、"你更喜欢 B 还是 C?" 和 "你更喜欢 A 还是 C?" 如果有一个候选人在选举中的每一轮一对一投票中都获胜,那么他们就是 "Condorcet 胜者",保证 "Condorcet 胜者" 将会赢得选举的选举系统被称为 "Condorcet 方法"。STAR 不是 Condorcet 方法,因为 Condorcet 没有考虑到偏好的强度。因此,STAR 可以说是给出了更好的结果。(另一方面,STAR 也保证了相反的情况:一个 Condorcet 失败者永远不会赢得 STAR 选举。而且,作为一个实际问题,STAR 选举的获胜者往往是 Condorcet 胜者。)
  • 它不满足多数 标准。多数标准要求:"如果一个候选人被多数选民排在第一位,那么这个候选人必须获胜"。
  • 它不满足后续无害 标准。后续无害要求:如果你已经在选票上对一个候选人表达了一个偏好,你不应该能够通过在选票上对另一个候选人表达另一个偏好来伤害这个候选人。STAR 未能满足这一标准;对一个不那么受偏爱的候选人的更高投票可能意味着你的更受偏爱的候选人无法当选。STAR Voting 团队撰写了一篇关于他们为何放弃这一标准的文章。简而言之:满足后续无害的选举系统通常也表现出 泄密效应,这是一个更差的属性。但是,实现后续无害并且避免泄密效应会使你的选举系统变得更糟!

(如果没有最好的选举系统,那么最差的选举系统是什么呢?也许有!如果真的有一个最差的选举系统,那么它几乎肯定是多数投票... 在美国这里占主导地位的选举系统。唉。)

API

选举

要使用 starvote,首先 导入 starvote,然后调用其 election 函数

def election(method, ballots, *,
    maximum_score=5,
    print=None,
    seats=1,
    tiebreaker=predefined_permutation_tiebreaker,
    verbosity=0,
    ):

election 根据你传入的参数统计选举,并返回一个包含获胜者的 list。即使在单胜者 STAR Voting 中,该列表也只包含一个元素。)

method 指定你想要在本选举中使用哪种方法(哪种 "选举系统"),通过预定义的 Method 对象来实现。支持的值有

  • starvote.STAR_Voting,
  • starvote.Bloc_STAR_Voting,
  • starvote.Allocated_Score_Voting,
  • starvote.Reweighted_Range_Voting,和
  • starvote.Sequentially_Spent_Score.

由于这些名称很长,starvote 还为这些方法提供了昵称,分别是

  • starvote.star,
  • starvote.bloc,
  • starvote.allocated,
  • starvote.rrv,和
  • starvote.sss.

ballots 应该是一个包含单个选票的可迭代对象。选票是一个将候选人映射到该选票对候选人得分的 dict。候选人可以是任何可散列的 Python 值;分数必须是 int

maximum_score 指定任何选票上任何投票允许的最大分数。

seats指定选举应填补多少个席位。STAR投票是一种单胜者方法,因此在使用STAR投票时,应设置为1;对于其他方法,seats必须大于或等于2

tiebreaker指定如何打破平局。它可以是平局函数、平局类或平局类的实例。有关预定义平局函数的列表,请参阅平局部分。

verbosity指定您想要多少输出。当前支持的值是0(无输出)和1(输出);大于1的值目前等同于1

print允许您指定自己的打印函数。默认情况下,election将使用builtins.print;您的替换print函数应具有相同的签名。

特定选举系统的函数

starvote还导出实现每个支持选举系统的函数

  • star_voting实现单胜者STAR投票。
  • bloc_star_voting实现多胜者Bloc STAR投票。
  • allocated_score_voting实现分配分数投票。
  • reweighted_range_voting实现重新加权范围投票。
  • sequentially_spent_score实现顺序花费分数。

这些函数的签名与election非常相似,但有以下变化

  • 它们没有method参数;方法在函数中是隐含的。所有五个函数仅接受一个位置参数ballots,它是必需的。
  • star没有seats参数。其他函数接受一个关键字参数seats,它是必需的--它没有默认值。(在Python中,必需的关键字参数相当罕见!)
  • 注意,与election一样,这些函数始终返回一个列表,即使是star_voting

分配分数投票的参考实现

starvote提供分配分数投票的参考实现副本。由于这需要NumPyPandas,因此默认不导入。(我不想让starvote有任何外部依赖。无论是否安装这些外部依赖项,单元测试套件都能正确运行。)

您可以使用import starvote.reference导入它;可直接调用的函数是starvote.reference.allocated_score_voting_reference。您还可以使用Method对象starvote.reference.Allocated_Score_Voting_reference,昵称为starvote.reference.allocated_r

如果您想将其集成到starvote模块中,请调用starvote.reference.monkey_patch()。这做了三件事

  • 直接将函数和Method对象添加到starvote模块中。

    • 还将其添加到starvote.__all__中,以便通过from starvote import *导入。
  • 将适当的Method对象添加到starvote.methods中,使用名称'分配分数投票(参考)'和昵称allocated_r

注意:参考实现不支持平局。尽管allocated_score_voting_reference接受一个tiebreaker参数,但目前它必须为None

平局

STAR投票选举很少在现实世界中导致平局。但平局仍然可能发生。好消息是,STAR投票有一套合理的、全面的平局打破协议。坏消息是,并非所有平局都可以打破--一些平局真正代表了选民的犹豫不决。

starvote通过election和特定选举函数的tiebreaker参数,让您控制如何打破选举中的平局。starvote还有两个预定义的平局函数,但您可以使用自己的函数--或者一个都不用。

starvote中的默认平局函数是predefined_permutation_tiebreaker,传递candidates=None

预定义排列平局函数

对于 starvote,主要决定胜负的因素是 predefined_permutation_tiebreaker。这是一个类;在运行选举时,您应该实例化它,并将实例作为 tiebreaker 参数传入。

predefined_permutation_tiebreaker 通过优先考虑候选人列表来解决平局。

在解决平局时,考虑以下三个变量

  • candidates,一个按顺序排列的 所有 参与选举的候选人的列表,
  • tie,一个平局候选人的可迭代对象,以及
  • desired,我们想要的获胜者数量。

predefined_permutation_tiebreaker 按以下方式计算平局中的获胜者

winners = [c for c in candidates if c in tie][:desired]

换句话说,它返回 desired 长度的 tie 子集,优先考虑在 candidates 中较早出现的候选人,而不是较晚出现的候选人。

predefined_permutation_tiebreaker 接受一个 candidates 参数,该参数应为一个预先选择的、按顺序排列的所有候选人的列表。默认值为 None,这将指导 predefined_permutation_tiebreaker 在选举开始时扫描选票,生成自己的所有候选人的列表,随机打乱该列表,并使用该列表作为 candidates 列表。

如果您传入自己的 candidates 列表,还可以传入一个用于 description 关键字参数的字符串,该字符串应描述此按顺序排列的候选人的来源。

请注意,如果您传入自己的 candidates 列表,starvote 的计票将完全是确定性和可重复的。您可以运行一百万次该选举,并且您总是会得到相同的结果。

predefined_permutation_tiebreaker 还接受一个 random 关键字参数。这应该是一个 random.Random 对象,它将在 predefined_permutation_tiebreaker 内部的所有随机化中使用。传入一个带有固定预定义种子的 random.Random 实例将使 predefined_permutation_tiebreaker 完全确定。默认值 None 意味着 predefined_permutation_tiebreaker 将使用 random 模块的所有随机化需求。

on_demand_random_tiebreaker

如果您希望生活中有更多的不可预测性,您可以选择 on_demand_random_tiebreaker 来解决平局。 on_demand_random_tiebreaker 将使用 Python 的 random.sample 函数,按需随机选择候选人(或候选人)。 on_demand_random_tiebreaker 是一个函数;您只需将其作为 tiebreaker 参数传入。

如果您希望生活中有 更少 的不可预测性,您还可以使用 random 关键字参数。此参数与 predefined_permutation_tiebreakerrandom 参数具有相同的语义。如果您传入一个使用固定种子生成的随机数生成器,on_demand_random_tiebreaker 将是完全确定的。

为了与 starvote.election(等)一起使用,您必须使用部分应用来预绑定 random 参数,类似于以下内容

t = functools.partial(
    starvote.on_demand_random_tiebreaker,
    random=random.Random(54321))
result = starvote.election(..., tiebreaker=t)

UnbreakableTieError

如果您明确将 tiebreaker 设置为 None,并且选举结果为平局,starvote 将引发 UnbreakableTieError 异常。您可以通过对异常调用 str 来获取平局的文本描述;您还可以通过其 candidates 属性获取平局候选人的列表。

编写自己的决定胜负函数

您可以编写自己的决定胜负函数,并将其作为 tiebreaker 参数传递给所选的选举函数。自定义决定胜负函数可以是函数或类。

自定义决定胜负函数应具有以下签名

def custom_tiebreaker(options, tie, desired, exception):

以下是这四个参数将包含的内容

  • options 是一个 starvote Options 对象。 options 具有映射到您传递给 election 的参数的属性。您应该在决定是否打印时遵守 options.verbosity,并使用 options.print 进行打印。 options 还具有以下便捷方法

    • options.heading 是一个上下文管理器,如果 options.verbosity 大于或等于 1,则会将标题打印到输出。您可以将标题作为字符串传递。您还可以嵌套标题。
    • options.print_candidates 将打印候选人的可迭代对象。您将可迭代对象作为第一个(也是唯一的)位置参数传递。它还接受一个 numbered 参数;如果 numbered 为 true,则按顺序打印候选人,并在其前面加上它们的序数(从 1 开始)。如果 numbered 为 false,则 print_candidates 在打印之前尝试对候选人进行排序。
  • tie 是一组并列候选人。

  • desired 是期望的获胜者数量。

  • exception 是此并列的 UnbreakableTieError。如果您无法打破并列,则应引发此对象。

注意,starvote 还会解析并列解决函数的文档字符串。文档字符串的第一行将用作选举初始化期间打印的“标题”,其余的文档字符串将作为该标题的正文打印,如果 options.verbosity 大于或等于 1。

您还可以编写一个自定义的并列解决类。唯一的要求是类从 starvote.Tiebreaker 继承,并且它支持具有与自定义并列解决函数相同签名的 __call__ 方法。(然而,在这里忽略文档字符串;由您调用 options.headingoptions.print。)

Tiebreaker 类也可以可选地有一个 initialize 方法

def initialize(self, options, ballots):

这将在初始化选举之前调用,在处理选票之前。以下是两个参数的解释

  • options 与传递给自定义并列解决函数的 options 相同。

  • ballots 是传递给选举函数的选票可迭代对象。

无论您编写哪种类型的并列解决函数,都应将其添加到 starvote.tiebreakers 中。这是一个将名称映射到并列解决的字典。添加到该字典的并列解决将在 starvote 格式选举 中可用,这些选举由 parse_starvote 解析。

代码示例

以下是在 Amy、Brian 和 Chuck 之间进行民意调查的示例

import starvote

ballots = [
    {'Amy': 1, 'Brian': 3, 'Chuck': 5},
    {'Amy': 5, 'Brian': 2, 'Chuck': 3},
    {'Amy': 4, 'Brian': 4, 'Chuck': 5},
]

winners = starvote.election(starvote.star, ballots, verbosity=1)

(此示例包含在 starvote Git 存储库中的 example.py。)

在此示例代码完成后,winners 变量将包含列表 ['Chuck']。该示例还产生以下输出

[STAR Voting]
  Tabulating 3 ballots.
  Maximum score is 5.

[STAR Voting: Initializing ordered permutation tiebreaker]
  Computing a random permutation of all the candidates.
  Permuted list of candidates:
    1. Brian
    2. Chuck
    3. Amy
  Tiebreaker candidates will be selected from this list, preferring candidates with lower numbers.

[STAR Voting: Scoring Round]
  The two highest-scoring candidates advance to the next round.
    Chuck -- 13 (average 4+1/3) -- First place
    Amy   -- 10 (average 3+1/3) -- Second place
    Brian --  9 (average 3)
  Chuck and Amy advance.

[STAR Voting: Automatic Runoff Round]
  The candidate preferred in the most head-to-head matchups wins.
    Chuck         -- 2 -- First place
    Amy           -- 1
    No Preference -- 0
  Chuck wins.

[STAR Voting: Winner]
  Chuck

starvote 格式

starvote 还定义了一个用于指定选举的自定义文本格式,称为 starvote 格式。它被广泛用于测试 starvote 本身,尽管它也可以用于运行真实的选举。

starvote 格式 看起来有点像 INI 格式,但它并不完全相同。

我为什么要写这个?我厌倦了 CSV 文件。将有关如何运行选举的元数据添加到选举文件本身非常有用。而且 starvote 格式 比等效的 https://star.vote/-格式 CSV 文件更容易阅读和编辑。

定义

包含 starvote 格式 选举的字符串称为 starvote 格式选举

starvote 格式 是面向行的格式。每行的首尾空白被忽略(并删除)。以 # 开头的行是注释。空行和注释(主要)被忽略。

非注释的非空行要么是 赋值 行,要么是 指令 行,要么是 部分 行。

指令行以冒号(:)结尾。指令行是自由形式的;目前定义了一个指令。在解析行时,指令优先于赋值。

赋值行必须包含等于号(=);等于号之前是“名称”,等于号之后是“值”。它产生的影响取决于我们所在的章节。

[开头并以]结尾的行定义了一个部分。您在方括号中指定部分的名称。您只能指定一个部分。目前只支持两个部分:optionsballots

options部分指定如何运行选举。在options部分中的赋值行指定选项;每个名称映射到election函数的一个参数。以下是所有支持的名字

    csv_path = <string>
    maximum score = <integer>
    method = <string>
    seats = <integer>
    starvote_path = <string>
    tiebreaker = <string | list>
    verbosity = <integer>

一个starvote格式选举可以指定这些选项中的每一个最多一次。

options部分中的赋值行还可以使用列表模式列表模式允许您将选项设置为一系列值,而不是单个值。要使用它,将名称设置为值[。将“名称”设置为空字符串,starvote格式解析器会激活列表模式。在列表模式中,非空行被追加到当前正在定义的列表中。要取消激活列表模式并完成列表定义,请在单独的一行中指定]。注意

  • 如果您将值设置为[],它将被设置为空列表,解析器不会启用列表模式
  • 列表模式中,您不能使用预处理器或更改部分。
  • 您不能嵌套列表。

唯一支持列表的赋值是options部分中的tiebreaker选项。tiebreaker也支持字符串值。

如果tiebreaker设置为字符串,parse_starvote会在options.tiebreakers字典中查找该字符串,并使用那里找到的解决方法。该字符串可以可选地以括号中用逗号分隔的name=value选项列表结束。当前定义的唯一选项是seedseed应设置为整数;这个整数将用于初始化Python random.Random实例,并将该实例用于所有随机化操作,从而使其完全确定。示例

tiebreaker=predefined_permutation_tiebreaker(seed=12345)

如果tiebreaker设置为列表,这定义了一个预排列的候选人列表,并将其传递给predefined_permutation_tiebreaker。这允许您预先定义一个排列后的候选人列表,从而使选举完全确定。示例

tiebreaker=[
    Amy
    Brian
    Carol
    ]

ballots部分定义选票。在本部分中,名称是候选人名称,值是该候选人的得分。单个选票由空白行和/或注释行分隔——要开始新的选票,只需添加一个空白行或注释行。

ballots部分支持一个预处理器:ballots。这允许您多次重复选票。要使用,请按以下方式在ballots部分中添加一行

    n ballots:

其中n是要重复选票的次数。这将重复后续选票n次。例如,要重复选票5次,请在上面的选票前添加此行

    5 ballots:

(您明确允许在ballots预处理器和要重复的选票之间有空白行。)

starvote_pathcsv_path选项允许您将其他文件“导入”到当前选举中。它们可以指定相对路径或绝对路径;如果是相对路径,则将使用包含starvote格式文件目录(如果starvote格式选举是从文件加载的)或当前目录来解析。这两个设置都将从指定的文件中加载选票;《code>starvote_path还将从指定的文件中加载选项,但是当前选举中设置的任何选项将覆盖这些选项。

请注意,starvote_path是完全递归的。Starvote文件A可以导入starvote文件B,B再导入C,依此类推。

示例

以下是一个星号投票格式选举的示例

[options]

seats=3
method=Bloc
tiebreaker = [
  Chuck
  Brian
  Amy
  ]
verbosity = 1

[ballots]

Amy = 1
Brian = 2
Chuck = 5

Amy = 1
Brian = 5
Chuck = 3

Amy = 1
Brian = 3
Chuck = 3

3 ballots:
Amy = 1
Brian = 3
Chuck = 3

APIs

starvote模块提供了两个处理starvote格式的函数。

第一个是parse_starvote

starvote.parse_starvote(starvote, *, path="<string>")

parse_starvote 函数接受一个位置参数:election,它必须是一个符合 starvote 格式 的字符串。它解析这个字符串并返回一个 kwargs 字典。你可以通过调用 election 并传入这个字典,使用 ** 将字典内容转换为关键字参数来运行这个选举。

starvote.election(**kwargs)

你也可以传入一个关键字参数 path,它应表示从文件加载的 starvote 格式选举 的文件名;如果指定了,它将被包含在异常中,以便提供上下文。

第二个是 load_starvote_file

starvote.load_starvote_file(path, *, encoding='utf-8')

load_starvote_file 函数接受一个位置参数:path,它必须是一个 strbytespathlib.Path 对象。它打开该文件,读取并解码其内容,将这些内容传递给 parse_starvote 函数,并返回结果。你可以使用 encoding 关键字参数指定文本编码;默认编码为 'utf-8'

命令行模块

starvote 模块支持作为脚本运行(python -m starvote)。运行它而不带参数可以查看用法。

使用时,指定命令行上的单个文件路径。starvote 将读取该文件,运行选举,并打印结果。

路径可以是 CSV 文件。CSV 文件应以文件扩展名 .csv 结尾,并采用 https://star.vote/ 格式。默认情况下,CSV 文件中的选举使用 STAR Voting 运行,一个席位,verbosity=1 和默认的平局解决方法。

或者,路径可以是 starvote 格式 文件。starvote 格式 文件应以文件扩展名 .starvote 结尾,并包含一个用 UTF-8 编码的 starvote 格式选举starvote 格式选举 明确指定了选举的所有参数。

例如,你可以从这个源代码库的根目录运行以下命令

% python3 -m starvote test_elections/test_election_breakable_tie_in_automatic_runoff_round_using_max_score_count_round.starvote

来查看 starvote 在自动复选轮中如何处理平局。

多席位选举

starvote 还实现了几个多席位选举系统。你只需要在调用 election 时传入一个多席位方法,例如 starvote.blocstarvote.allocatedstarvote.rrv 即可。

poll = election(starvote.bloc, ballots, seats=2)

你也可以使用模块的命令行版本进行实验。只需指定方法(使用 -m)、席位数(使用 -s)和最大得分(使用 -x)。这些将覆盖 starvote 格式 文件中的设置(以及 CSV 文件的默认设置)。

多席位与比例

以下是对“比例投票”在多席位选举中含义的简要解释。与其他所有主题一样,你可以在 维基百科 上了解更多信息。

简单的多席位选举意味着你是在选举 2 个或更多候选人,而不是一个候选人。获得最多选票的候选人——或者以任何方式计票——获胜。

但是,只选举最受欢迎的候选人可能无法很好地代表选民。假设你有一个城市选举,需要填补五个席位。在这个城市,60% 的选民只投票给党派 A,40% 的选民只投票给党派 B。块状 STAR Voting 很可能将所有五个席位都授予党派 A 的候选人。这公平吗?这似乎是 “多数暴政”

全球政治体系中,存在一种分配席位的替代方法,称为“比例代表制”。其核心思想是,你有N个席位,并根据代表民众比例来分配这些席位。在上面的例子中,你可能想要给政党A分配三个席位,给政党B分配两个席位。哪个系统可以实现这一点呢?

通常,比例代表制是基于对政党的投票,而不是对候选人的投票。这被称为政党名单比例代表制,并且它被用于全球范围内的政府机构选举。

但是,也有一些投票方法允许直接对候选人投票,并产生比例代表制。分配得分投票、重新加权范围投票和顺序消费得分就是这样的三种方法。它们的工作原理大致如下

  • 每一票都是一个数字,数字越大表示偏好越强。
  • 你进行N轮选举来填补N个席位,每一轮选举选出一名候选人。
  • 当一名候选人被选中时,我们可以将选票分配给该候选人,这意味着我们认为这些选票已被使用,并将它们从选举中移除。如果你为候选人A1投票,并且A1赢得了第一轮,你的选票可能会被“分配”给A1,并在剩余的选举中忽略。
  • 或者,我们可以对为该候选人投票的选民的重加权投票。这意味着我们降低这些选票的投票权重。如果在选举中你为候选人A1投票,并且A1赢得了第一轮,那么在第二轮你的投票将计较少。但是,如果我给A1的得分为0,我的选票仍将保持全部权重。

三种方法之间的区别在于它们如何分配和重新加权选票。分配得分投票和顺序消费得分都分配选票,重新加权范围投票从不分配选票——在重新加权范围投票中,每一轮每一票都被计算。此外,这三种系统在计算每一轮后选票的新权重时使用不同的公式。在分配得分和顺序消费得分中,新权重基于填补配额(称为哈里配额)所需的过剩选民数量;在重新加权范围投票中,新权重基于你对获胜候选人提供的得分总和。

starvote提供了一个示例选举,很好地展示了直接选举比例代表制选举如何运作

test_elections/test_election_reweighted_range_sample_election.starvote

这个选举就像我在上面描述中使用的示例选举一样。它使用重新加权范围投票来填补三个席位,每票的最高得分为10。60名选民偏好政党A,并为候选人A1、A2和A3给出高分;40名选民偏好政党B,并为候选人B1和B2给出高分。

您可以在starvote主目录中运行以下命令来进行选举

% python3 -m starvote test_elections/test_election_reweighted_range_sample_election.starvote

要查看如何使用分配得分投票进行计票,请运行以下命令

% python3 -m starvote -m allocated test_elections/test_election_reweighted_range_sample_election.starvote

您还可以通过运行此命令来查看顺序消费得分的变化

% python3 -m starvote -m allocated test_elections/test_election_reweighted_range_sample_election.starvote

要查看多数暴政的作用,此命令将使用区块STAR投票进行计票

% python3 -m starvote -m bloc test_elections/test_election_reweighted_range_sample_election.starvote

警告

我还没有找到一个针对区块STAR投票的单个测试语料库。我尽量遵循规则,得到的结果看起来合理。但到目前为止,我无法确认我的实现是正确的——我确实有可能搞错了。

我确实有一个重新加权范围投票的样本调查,因此我对我的实现有一定的信心。并且我对分配得分投票的参考实现进行了一些测试。

谢谢

衷心感谢Tim Peters在开发这个库期间持续的贡献。尽管Tim对库本身没有提供任何意见——如果你不喜欢这个库,那100%是我的责任!——但他不厌其烦地回答了我关于投票的所有问题,并多次说服我改变我的方法。特别是,Tim的反馈推动了我开发tiebreaker插件接口。

许可证

starvote使用的是MIT许可协议。请参阅LICENSE文件。

这里特别需要重复的是:此软件不提供任何保证。我已经尽我所能实现了这个选举系统计票器。但这个软件可能有错误,或者我对规则的理解可能错误,这都可能影响你使用此软件进行的选举结果。请自行承担风险。

源代码库包括从https://star.vote/下载的样本选票。这些样本选票的许可情况不明确,但它们被认为是公共领域或可以自由重新分配的。

更新日志

2.1.2 - 2023/10/02

  • 新功能:您可以在starvote格式选举中为用于随机平局的伪随机数生成器(PRNG)指定一个种子。种子只是一个整数。这要求为tiebreaker选项提供新的语法:您可以使用类似于Python函数调用且仅带有关键字参数的语法来指定选项。例如,要使用带有以数字12345为种子的伪随机数生成器的predefined_permutation_tiebreaker,您需要在starvote格式选举的options部分添加以下内容

    tiebreaker=predefined_permutation_tiebreaker(seed=12345)

  • 新功能:predefined_permutation_tiebreakeron_demand_random_tiebreaker现在都接受一个名为random的关键字参数,它应该是random.Random的实例或None。这允许您传递一个具有固定种子的random.Random实例,平局处理程序将使用它来进行所有随机化——这是一种使平局处理程序完全确定性的便利方式。默认值None表示平局处理程序将使用random模块本身进行随机化。

  • starvote现在有超过一百个测试!

2.1.1 - 2023/09/20

  • 错误修复:starvote格式以前允许在同一个选票上多次指定同一候选人;只有最后的投票会被计算。现在它会检测这种情况,并在第二次实例中引发ValueError修复了#8。

2.1 - 2023/09/20

  • verbosity的更改。如果verbosity为1,则排列平局处理程序在初始化时不打印任何输出。相反,如果存在平局处理程序,它将在打印平局处理程序的结果之前立即打印预排列的候选人列表。请注意,这不会在初始化完成后改变;当候选人列表由starvote随机排列时,这仍然是在运行选举之前完成的。如果verbosity为2或更高,则排列平局处理程序的初始化将在初始化时打印其输出,就像以前一样。修复了#3。
  • 功能请求:在详细输出之前添加了空白行以分隔章节标题。修复了#2。
  • 错误修复:当在偏好轮次的输出中打印“无偏好”时,这应该是表达候选人间无偏好的选票数量。以前它打印的是选民表达无偏好的总对决次数,而每张选票可能有多个这种对决。 修复了#7。
  • 修复错误:对于多胜选选举,seats 必须小于或等于候选人数量。现在 starvote 在被要求统计不符合此条件的选举时将引发错误。修复 #6。
  • 轻微的内部 API 变更:Tiebreaker.print_description 接受两个参数,第二个是描述字符串或列表。由于总是使用 self.description,现在默认值为 None,这意味着它使用 self.description

2.0.6 - 2023/07/22

非常小的版本更新。没有新功能或错误修复。

  • 添加了 GitHub Actions 集成。每次提交后都在云端运行测试和覆盖率。感谢 Dan Pope 轻柔地引导我完成这个过程!
  • 修复了 pyproject.toml 文件中的元数据。
  • 添加了测试、覆盖率和支持的 Python 版本的徽章。

2.0.5 - 2023/07/05

  • 没有代码更改,只是调整了许可协议,以删除公认的含糊其辞的短语“版权所有。” 同时,将许可协议嵌入到模块中。

2.0.4 - 2023/06/05

  • 添加了对顺序花费分数投票的支持。
  • 略微改变了分配分数的表示:现在使用选举中所有 所有 选票的平均值,包括分配的选票。(之前平均是使用仅有的 剩余 选票计算的。)这不会改变选举结果,只是表示上的变化。
  • 移除了“STAR-PR”的最后痕迹,我以为这是分配分数的另一个名称。
  • 文档更改。将拼写统一为“multiwinner”,而不是“multi-winner”。

2.0.3 - 2023/06/01

  • 规范了 Method 对象的命名。现在,每个方法都有一个官方的正确拼写和正确的大写名称,以及一个“昵称”。昵称始终为小写,并会返回 True 对 isidentifier。这些在 starvote.method 映射中使用;这些名称的变体也被绑定为模块属性(已更改以使其成为有效的标识符)。
  • 改进了 starvote.reference.monkey_patch。现在它是数据驱动的,因此更可靠。
  • 添加了 starvote.reference.__all__,以防您想要 from starvote.reference import *
  • 文档修复。

2.0.2 - 2023/06/01

  • tiebreaker 参数重命名为 tie
  • starvote 模块中删除了一些仅用于测试的平局解决方法。现在它们在自己的脚本中,该脚本仅在处理测试套件时加载。

2.0.1 - 2023/06/01

  • 将分配分数投票参考版本的昵称更改为 "Allocated-R"。

2.0 - 2023/06/01

完全重写!1.x 代码库相当糟糕。这个代码库要干净得多——我认为我还解决了几个错误。

starvote 有一个全新的、功能性的 API。 election 为您运行选举,并接受两个必需的位置参数:method,指定您想要哪种选举制度,和 ballots,一个选票字典的可迭代对象。

您还可以传递一个用于解决不可解平局的处理器。默认情况下,现在使用预先选择的随机候选人列表来解决不可解平局。(如果需要,election 仍然可以引发 UnbreakableTieError 异常。)

您还可以直接调用投票方法函数:starbloc_starproportional_starreweighted_range 都是函数。这些函数省略了 method 参数,但仍需要 ballots 参数。

还有一个新的用于存储选举的文本格式,称为 starvote 格式,具有 .starvote 文件扩展名。starvote 格式.csv 文件的不错替代品。

  • 添加了 electionstar_votingbloc_star_votingallocated_score_votingreweighted_range_voting 函数。

    • 删除了 Poll 类。
  • 现在始终使用所有方法的官方名称。

    • STAR Voting
    • STAR投票
    • 分配得分投票
    • 重新加权区间投票

    ("比例STAR投票"是一个 类别 的选举制度。它本身不是一个投票系统,并且绝对不是仅限于"分配得分投票"。"STAR-PR"是该类别的另一个名称。)

  • ElectoralSystem枚举替换为Method类。实例(例如starvote.STAR_Voting)包含election运行选举所需的所有元数据。

    • methods是一个模块级字典,将字符串映射到Method对象。
  • 实现了STAR投票和区块STAR投票的STAR投票官方平局裁决协议

  • 所有投票计票使用严格的intfractions.Fraction对象。计票现在从头到尾、在所有平台上都是100%一致和准确的。

  • 添加了parse_starvote函数,该函数解析一个starvote格式字符串,运行选举,并返回结果。

  • 添加了load_starvote_file函数,该函数从磁盘加载一个starvote格式文件,并使用parse_starvote进行解析。

  • 添加了Tiebreaker类,以及on_demand_random_tiebreakerpredefined_permutation_tiebreaker平局裁决器。

    • tiebreakers是一个模块级字典,将字符串映射到平局裁决器。
  • 添加了分配得分投票的参考实现。这需要NumPy和Pandas,因此默认不导入。您可以使用import starvote.reference导入它,并且可以通过调用starvote.reference.monkey_patch()将其集成到starvote模块中。参考实现不支持平局裁决。

  • 从命令行使用python -m starvote运行模块时

    • 您现在可以指定一个.starvote文件。其内容应是一个starvote格式选举,使用UTF-8编码。

    • 当指定.csv文件时,starvote使用几个默认值:方法为STAR投票,席位为1,详细程度为1。

1.5.1 - 2023/05/24

  • 重命名了API中的许多名称。
    • PollVariant枚举重命名为ElectoralSystem
    • variant参数重命名为electoral_system
    • max_score参数重命名为maximum_score
  • 更改了命令行模块选项以匹配。
    • -v|--variant更改为-e|--electoral-system
    • -m|--max_score更改为-m|--maximum_score

1.5 - 2023/05/22

  • 添加了对重新加权区间投票的支持,这是比例STAR的一个有吸引力的替代品。像STAR-PR一样,RRV是一个比例代表选举制度。但RRV更容易理解,更容易实现,并且永远不会丢弃选票。感谢Tim Peters提出建议!
  • max_score参数添加到Poll构造函数中。现在您可以使用任何范围。 (最低分仍然是始终为0。)
  • 更改了"Bloc STAR"的拼写。我认为"Bloc"总是正确的大写(作为"BLOC STAR"),但不是这样。

1.4 - 2023/05/21

  • 自动化了测试套件。
  • 为比例STAR的平局裁决偏好轮添加了日志打印。
  • 修复了在平局结束时结束的多席位选举的__main__中的展示。

1.3 - 2023/05/21

  • 添加了对比例STAR投票的支持。唯一可见的外部变化是新的Proportional_STAR枚举值。
  • Poll构造函数上的winners参数重命名为seats。很抱歉破坏了您的代码,全球所有已经开始使用该参数的人!但这个新名字是一个很大的改进。

1.2 - 2023/05/20

  • 添加了对区块STAR投票的支持。

    • 添加了包含STARBLOC_STAR值的PollVariant枚举。
    • variantwinners 参数添加到 Poll
  • 将并列候选人的列表添加到 UnbreakableTieError 异常中,作为新的 candidates 属性。

1.1 - 2023/05/20

  • 错误修复:如果存在三个并列的第二名,则抛出 UnbreakableTieError。之前 starvote 只注意到了第一名并列的情况。
  • sample_polls/ 中为每个示例投票添加了示例输出。这些输出已经通过检查确认是正确的,将来可以用作自动化测试套件的一部分。

1.0 - 2023/05/20

  • 初始版本。

项目详情


下载文件

下载适用于您平台的应用程序。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源代码发行版

starvote-2.1.2.tar.gz (2.0 MB 查看哈希值)

上传时间 源代码

构建发行版

starvote-2.1.2-py3-none-any.whl (44.1 kB 查看哈希值)

上传时间 Python 3

由以下组织支持