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:
|
||||
uv run ruff format src test
|
||||
|
||||
example:
|
||||
uv run python src/main.py test/test_ballot.csv
|
||||
|
||||
compile:
|
||||
uv run pyinstaller src/main.py
|
||||
uv run pyinstaller --clean -F src/main.py --name smithy
|
||||
|
||||
|
||||
clean:
|
||||
uv run pyclean src test
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ dependencies = [
|
|||
]
|
||||
|
||||
[project.scripts]
|
||||
smithy = "smithy:main"
|
||||
smithy = "smithy:cli"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.11.7,<0.12.0"]
|
||||
|
|
|
|||
|
|
@ -118,7 +118,9 @@ platformdirs==4.9.6
|
|||
pluggy==1.6.0
|
||||
# via pytest
|
||||
polars==1.40.1
|
||||
# via marimo
|
||||
# via
|
||||
# smithy (pyproject.toml)
|
||||
# marimo
|
||||
polars-runtime-32==1.40.1
|
||||
# via polars
|
||||
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,14 +7,10 @@ from rich.panel import Panel
|
|||
|
||||
from .rcv import smith_set
|
||||
|
||||
console = Console()
|
||||
|
||||
@click.command()
|
||||
@click.argument(
|
||||
"spreadsheet",
|
||||
type=click.Path(exists=True, dir_okay=False)
|
||||
)
|
||||
def main(spreadsheet: str) -> None:
|
||||
@click.argument("spreadsheet", type=click.Path(exists=True, dir_okay=False))
|
||||
def cli(spreadsheet: str) -> None:
|
||||
"""
|
||||
Compute the Smith set from a ranked-choice ballot spreadsheet.
|
||||
|
||||
|
|
@ -22,8 +18,9 @@ def main(spreadsheet: str) -> None:
|
|||
in the set they are guaranteed the Condorcet i.e. Majority winner.
|
||||
"""
|
||||
|
||||
try:
|
||||
console = Console()
|
||||
|
||||
try:
|
||||
# Load spreadsheet
|
||||
if spreadsheet.endswith(".csv"):
|
||||
df = pl.read_csv(spreadsheet)
|
||||
|
|
@ -33,17 +30,21 @@ def main(spreadsheet: str) -> None:
|
|||
|
||||
else:
|
||||
console.print(
|
||||
"[bold red]Unsupported file type.[/bold red]\n"
|
||||
"Use CSV or Excel."
|
||||
"[bold red]Unsupported file type.[/bold red]\nUse CSV or Excel."
|
||||
)
|
||||
raise SystemExit(1)
|
||||
|
||||
# Normalize numerical dataframe entries
|
||||
df = df.with_columns([ pl.col(c)
|
||||
df = df.with_columns(
|
||||
[
|
||||
pl.col(c)
|
||||
.cast(pl.Utf8)
|
||||
.str.strip_chars()
|
||||
.cast(pl.Int64, strict=False).fill_null(0)
|
||||
for c in df.columns ])
|
||||
.cast(pl.Int64, strict=False)
|
||||
.fill_null(0)
|
||||
for c in df.columns
|
||||
]
|
||||
)
|
||||
|
||||
# Compute Smith set
|
||||
smiths = smith_set(df)
|
||||
|
|
@ -66,14 +67,11 @@ def main(spreadsheet: str) -> None:
|
|||
Panel.fit(
|
||||
"\n".join(f"• {c}" for c in smiths),
|
||||
title="Resulting Smith Set",
|
||||
border_style="green"
|
||||
border_style="green",
|
||||
)
|
||||
)
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import polars as pl
|
||||
from itertools import combinations
|
||||
|
||||
|
||||
def smith_set(df: pl.DataFrame) -> list:
|
||||
"""
|
||||
Compute the Smith set from a Ranked-Choice ballot.
|
||||
|
|
@ -27,7 +28,7 @@ def smith_set(df: pl.DataFrame) -> list:
|
|||
candidates = df.columns
|
||||
|
||||
# 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):
|
||||
result = df.select(
|
||||
|
|
@ -46,17 +47,13 @@ def smith_set(df: pl.DataFrame) -> list:
|
|||
|
||||
# Find Smith set
|
||||
for size in range(1, len(candidates) + 1):
|
||||
|
||||
for sub in combinations(candidates, size):
|
||||
|
||||
subset = set(sub)
|
||||
out = set(candidates) - subset
|
||||
|
||||
dom = True
|
||||
|
||||
for member in subset:
|
||||
|
||||
# DIRECT dominance only
|
||||
if not out.issubset(graph[member]):
|
||||
dom = False
|
||||
break
|
||||
|
|
|
|||
|
|
@ -23,11 +23,16 @@ def _():
|
|||
@app.cell
|
||||
def _(mo, pl):
|
||||
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(
|
||||
[
|
||||
pl.col(c) # make safe, clean up
|
||||
.cast(pl.Utf8)
|
||||
.str.strip_chars()
|
||||
.cast(pl.Int64, strict=False).fill_null(df.width + 1)
|
||||
for c in df.columns ])
|
||||
.cast(pl.Int64, strict=False)
|
||||
.fill_null(df.width + 1)
|
||||
for c in df.columns
|
||||
]
|
||||
)
|
||||
df
|
||||
return (df,)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,35 +1,41 @@
|
|||
import polars as pl
|
||||
from smithy import smith_set
|
||||
|
||||
|
||||
def test_condorcet():
|
||||
df = pl.DataFrame({
|
||||
'A': [1, 1, 2, 1],
|
||||
'B': [2, 2, 1, 2],
|
||||
'C': [3, 3, 3, 3],
|
||||
})
|
||||
assert smith_set(df) == ['A']
|
||||
df = pl.DataFrame(
|
||||
{
|
||||
"A": [1, 1, 2, 1],
|
||||
"B": [2, 2, 1, 2],
|
||||
"C": [3, 3, 3, 3],
|
||||
}
|
||||
)
|
||||
assert smith_set(df) == ["A"]
|
||||
|
||||
|
||||
def test_rockpprscrcycle():
|
||||
df = pl.DataFrame({
|
||||
'A': [1, 2, 3],
|
||||
'B': [2, 3, 1],
|
||||
'C': [3, 1, 2],
|
||||
})
|
||||
assert smith_set(df) == ['A', 'B', 'C']
|
||||
df = pl.DataFrame(
|
||||
{
|
||||
"A": [1, 2, 3],
|
||||
"B": [2, 3, 1],
|
||||
"C": [3, 1, 2],
|
||||
}
|
||||
)
|
||||
assert smith_set(df) == ["A", "B", "C"]
|
||||
|
||||
|
||||
def test_abpair():
|
||||
df = pl.DataFrame({
|
||||
"A": [1, 2, 1, 3],
|
||||
"B": [2, 1, 3, 1],
|
||||
"C": [3, 3, 2, 2]
|
||||
})
|
||||
assert smith_set(df) == ['A', 'B']
|
||||
df = pl.DataFrame({"A": [1, 2, 1, 3], "B": [2, 1, 3, 1], "C": [3, 3, 2, 2]})
|
||||
assert smith_set(df) == ["A", "B"]
|
||||
|
||||
|
||||
def test_fourcycle():
|
||||
df = pl.DataFrame({
|
||||
"A": [1,2,3,4],
|
||||
"B": [2,3,4,1],
|
||||
"C": [3,4,1,2],
|
||||
"D": [4,1,2,3],
|
||||
})
|
||||
assert smith_set(df) == ['A', 'B', 'C', 'D']
|
||||
df = pl.DataFrame(
|
||||
{
|
||||
"A": [1, 2, 3, 4],
|
||||
"B": [2, 3, 4, 1],
|
||||
"C": [3, 4, 1, 2],
|
||||
"D": [4, 1, 2, 3],
|
||||
}
|
||||
)
|
||||
assert smith_set(df) == ["A", "B", "C", "D"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue