mirror of
https://github.com/tgorordo/smithy.git
synced 2026-06-05 16:22:15 -07:00
some formatting and add cli compilation
This commit is contained in:
parent
8836c49091
commit
4a624a4847
9 changed files with 115 additions and 61 deletions
6
justfile
6
justfile
|
|
@ -13,8 +13,12 @@ test:
|
||||||
format:
|
format:
|
||||||
uv run ruff format src test
|
uv run ruff format src test
|
||||||
|
|
||||||
|
example:
|
||||||
|
uv run python src/main.py test/test_ballot.csv
|
||||||
|
|
||||||
compile:
|
compile:
|
||||||
uv run pyinstaller src/main.py
|
uv run pyinstaller --clean -F src/main.py --name smithy
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
uv run pyclean src test
|
uv run pyclean src test
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
smithy = "smithy:main"
|
smithy = "smithy:cli"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build>=0.11.7,<0.12.0"]
|
requires = ["uv_build>=0.11.7,<0.12.0"]
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,9 @@ platformdirs==4.9.6
|
||||||
pluggy==1.6.0
|
pluggy==1.6.0
|
||||||
# via pytest
|
# via pytest
|
||||||
polars==1.40.1
|
polars==1.40.1
|
||||||
# via marimo
|
# via
|
||||||
|
# smithy (pyproject.toml)
|
||||||
|
# marimo
|
||||||
polars-runtime-32==1.40.1
|
polars-runtime-32==1.40.1
|
||||||
# via polars
|
# via polars
|
||||||
psutil==7.2.2
|
psutil==7.2.2
|
||||||
|
|
|
||||||
38
smithy.spec
Normal file
38
smithy.spec
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['src/main.py'],
|
||||||
|
pathex=[],
|
||||||
|
binaries=[],
|
||||||
|
datas=[],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='smithy',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from smithy import cli
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli()
|
||||||
|
|
@ -7,23 +7,20 @@ from rich.panel import Panel
|
||||||
|
|
||||||
from .rcv import smith_set
|
from .rcv import smith_set
|
||||||
|
|
||||||
console = Console()
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument(
|
@click.argument("spreadsheet", type=click.Path(exists=True, dir_okay=False))
|
||||||
"spreadsheet",
|
def cli(spreadsheet: str) -> None:
|
||||||
type=click.Path(exists=True, dir_okay=False)
|
|
||||||
)
|
|
||||||
def main(spreadsheet: str) -> None:
|
|
||||||
"""
|
"""
|
||||||
Compute the Smith set from a ranked-choice ballot spreadsheet.
|
Compute the Smith set from a ranked-choice ballot spreadsheet.
|
||||||
|
|
||||||
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
||||||
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
console = Console()
|
||||||
|
|
||||||
|
try:
|
||||||
# Load spreadsheet
|
# Load spreadsheet
|
||||||
if spreadsheet.endswith(".csv"):
|
if spreadsheet.endswith(".csv"):
|
||||||
df = pl.read_csv(spreadsheet)
|
df = pl.read_csv(spreadsheet)
|
||||||
|
|
@ -33,17 +30,21 @@ def main(spreadsheet: str) -> None:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
console.print(
|
console.print(
|
||||||
"[bold red]Unsupported file type.[/bold red]\n"
|
"[bold red]Unsupported file type.[/bold red]\nUse CSV or Excel."
|
||||||
"Use CSV or Excel."
|
|
||||||
)
|
)
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
# Normalize numerical dataframe entries
|
# Normalize numerical dataframe entries
|
||||||
df = df.with_columns([ pl.col(c)
|
df = df.with_columns(
|
||||||
.cast(pl.Utf8)
|
[
|
||||||
.str.strip_chars()
|
pl.col(c)
|
||||||
.cast(pl.Int64, strict=False).fill_null(0)
|
.cast(pl.Utf8)
|
||||||
for c in df.columns ])
|
.str.strip_chars()
|
||||||
|
.cast(pl.Int64, strict=False)
|
||||||
|
.fill_null(0)
|
||||||
|
for c in df.columns
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# Compute Smith set
|
# Compute Smith set
|
||||||
smiths = smith_set(df)
|
smiths = smith_set(df)
|
||||||
|
|
@ -66,14 +67,11 @@ def main(spreadsheet: str) -> None:
|
||||||
Panel.fit(
|
Panel.fit(
|
||||||
"\n".join(f"• {c}" for c in smiths),
|
"\n".join(f"• {c}" for c in smiths),
|
||||||
title="Resulting Smith Set",
|
title="Resulting Smith Set",
|
||||||
border_style="green"
|
border_style="green",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||||
console.print(
|
|
||||||
f"[bold red]Error:[/bold red] {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import polars as pl
|
import polars as pl
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
|
|
||||||
|
|
||||||
def smith_set(df: pl.DataFrame) -> list:
|
def smith_set(df: pl.DataFrame) -> list:
|
||||||
"""
|
"""
|
||||||
Compute the Smith set from a Ranked-Choice ballot.
|
Compute the Smith set from a Ranked-Choice ballot.
|
||||||
|
|
||||||
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
The Smith set is the minimal set of candidates which can beat all others pairwise - if there is a single winner
|
||||||
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
||||||
|
|
||||||
parameters
|
parameters
|
||||||
---
|
---
|
||||||
df : pl.DataFrame
|
df : pl.DataFrame
|
||||||
|
|
@ -27,7 +28,7 @@ def smith_set(df: pl.DataFrame) -> list:
|
||||||
candidates = df.columns
|
candidates = df.columns
|
||||||
|
|
||||||
# Build pairwise majority graph
|
# Build pairwise majority graph
|
||||||
graph: dict[str, set[str]] = { c: set() for c in candidates }
|
graph: dict[str, set[str]] = {c: set() for c in candidates}
|
||||||
|
|
||||||
for a, b in combinations(candidates, 2):
|
for a, b in combinations(candidates, 2):
|
||||||
result = df.select(
|
result = df.select(
|
||||||
|
|
@ -46,17 +47,13 @@ def smith_set(df: pl.DataFrame) -> list:
|
||||||
|
|
||||||
# Find Smith set
|
# Find Smith set
|
||||||
for size in range(1, len(candidates) + 1):
|
for size in range(1, len(candidates) + 1):
|
||||||
|
|
||||||
for sub in combinations(candidates, size):
|
for sub in combinations(candidates, size):
|
||||||
|
|
||||||
subset = set(sub)
|
subset = set(sub)
|
||||||
out = set(candidates) - subset
|
out = set(candidates) - subset
|
||||||
|
|
||||||
dom = True
|
dom = True
|
||||||
|
|
||||||
for member in subset:
|
for member in subset:
|
||||||
|
|
||||||
# DIRECT dominance only
|
|
||||||
if not out.issubset(graph[member]):
|
if not out.issubset(graph[member]):
|
||||||
dom = False
|
dom = False
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,23 @@ def _():
|
||||||
@app.cell
|
@app.cell
|
||||||
def _(mo, pl):
|
def _(mo, pl):
|
||||||
df = pl.read_csv(mo.notebook_dir() / "test_ballot.csv")
|
df = pl.read_csv(mo.notebook_dir() / "test_ballot.csv")
|
||||||
df = df.with_columns([ pl.col(c) # make safe, clean up
|
df = df.with_columns(
|
||||||
.cast(pl.Utf8)
|
[
|
||||||
.str.strip_chars()
|
pl.col(c) # make safe, clean up
|
||||||
.cast(pl.Int64, strict=False).fill_null(df.width + 1)
|
.cast(pl.Utf8)
|
||||||
for c in df.columns ])
|
.str.strip_chars()
|
||||||
|
.cast(pl.Int64, strict=False)
|
||||||
|
.fill_null(df.width + 1)
|
||||||
|
for c in df.columns
|
||||||
|
]
|
||||||
|
)
|
||||||
df
|
df
|
||||||
return (df,)
|
return (df,)
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
@app.cell
|
||||||
def _(df, smith_set):
|
def _(df, smith_set):
|
||||||
smith_set(df) # find the smith set (should be "Alice" and "Bob" as a pair)
|
smith_set(df) # find the smith set (should be "Alice" and "Bob" as a pair)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,41 @@
|
||||||
import polars as pl
|
import polars as pl
|
||||||
from smithy import smith_set
|
from smithy import smith_set
|
||||||
|
|
||||||
|
|
||||||
def test_condorcet():
|
def test_condorcet():
|
||||||
df = pl.DataFrame({
|
df = pl.DataFrame(
|
||||||
'A': [1, 1, 2, 1],
|
{
|
||||||
'B': [2, 2, 1, 2],
|
"A": [1, 1, 2, 1],
|
||||||
'C': [3, 3, 3, 3],
|
"B": [2, 2, 1, 2],
|
||||||
})
|
"C": [3, 3, 3, 3],
|
||||||
assert smith_set(df) == ['A']
|
}
|
||||||
|
)
|
||||||
|
assert smith_set(df) == ["A"]
|
||||||
|
|
||||||
|
|
||||||
def test_rockpprscrcycle():
|
def test_rockpprscrcycle():
|
||||||
df = pl.DataFrame({
|
df = pl.DataFrame(
|
||||||
'A': [1, 2, 3],
|
{
|
||||||
'B': [2, 3, 1],
|
"A": [1, 2, 3],
|
||||||
'C': [3, 1, 2],
|
"B": [2, 3, 1],
|
||||||
})
|
"C": [3, 1, 2],
|
||||||
assert smith_set(df) == ['A', 'B', 'C']
|
}
|
||||||
|
)
|
||||||
|
assert smith_set(df) == ["A", "B", "C"]
|
||||||
|
|
||||||
|
|
||||||
def test_abpair():
|
def test_abpair():
|
||||||
df = pl.DataFrame({
|
df = pl.DataFrame({"A": [1, 2, 1, 3], "B": [2, 1, 3, 1], "C": [3, 3, 2, 2]})
|
||||||
"A": [1, 2, 1, 3],
|
assert smith_set(df) == ["A", "B"]
|
||||||
"B": [2, 1, 3, 1],
|
|
||||||
"C": [3, 3, 2, 2]
|
|
||||||
})
|
|
||||||
assert smith_set(df) == ['A', 'B']
|
|
||||||
|
|
||||||
def test_fourcycle():
|
def test_fourcycle():
|
||||||
df = pl.DataFrame({
|
df = pl.DataFrame(
|
||||||
"A": [1,2,3,4],
|
{
|
||||||
"B": [2,3,4,1],
|
"A": [1, 2, 3, 4],
|
||||||
"C": [3,4,1,2],
|
"B": [2, 3, 4, 1],
|
||||||
"D": [4,1,2,3],
|
"C": [3, 4, 1, 2],
|
||||||
})
|
"D": [4, 1, 2, 3],
|
||||||
assert smith_set(df) == ['A', 'B', 'C', 'D']
|
}
|
||||||
|
)
|
||||||
|
assert smith_set(df) == ["A", "B", "C", "D"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue