mirror of
https://github.com/tgorordo/carousel.git
synced 2026-06-12 20:42:13 -07:00
Compare commits
No commits in common. "f478c69c658a987c294ce7dbb552d0b5569ed18f" and "6cdc579270213cd753194fa70271a09417a40e50" have entirely different histories.
f478c69c65
...
6cdc579270
12 changed files with 609 additions and 1873 deletions
58
README.md
58
README.md
|
|
@ -3,7 +3,7 @@ bibliography: REFERENCES.bib
|
||||||
...
|
...
|
||||||
|
|
||||||
# Carousel
|
# Carousel
|
||||||
*A simple Stable Matching solver.*
|
*A simple Stable Matching Solver.*
|
||||||
|
|
||||||
`carousel` is a solver for the
|
`carousel` is a solver for the
|
||||||
[Envy-free](https://en.wikipedia.org/wiki/Envy-free_matching)
|
[Envy-free](https://en.wikipedia.org/wiki/Envy-free_matching)
|
||||||
|
|
@ -14,29 +14,30 @@ bibliography: REFERENCES.bib
|
||||||
|
|
||||||
### Gale-Shapley Deferred Acceptance
|
### Gale-Shapley Deferred Acceptance
|
||||||
|
|
||||||
- Gives a solution to the basic problem.
|
The most basic version(s) of the stable matching problem was outlined and solved in [@gale&shapley1962] and won Shapley the
|
||||||
|
[2012 Nobel Prize in Economics](https://www.nobelprize.org/prizes/economic-sciences/2012/popular-information/).
|
||||||
|
|
||||||
### GS + Rotation Enumeration of Solutions
|
The basic problem being solved is as follows:
|
||||||
|
|
||||||
- Can implement post-selection measures/constraints/criteria.
|
#### Stable Marriage Problem
|
||||||
|
The "Stable Marriage problem" TODO
|
||||||
|
|
||||||
### Integer Programming (Huang++)
|
TODO
|
||||||
|
|
||||||
### Polytope Solution Sampling (Large Problems)
|
#### College Admissions Problem
|
||||||
|
Solving the stable marriage problem also provides a solution to the "College Admissions Problem",
|
||||||
|
with just a little more work.
|
||||||
|
|
||||||
### Brute-Force Combinatoric Search
|
TODO
|
||||||
A benchmark/testbed implementation. Beware.
|
|
||||||
|
|
||||||
### More?
|
|
||||||
Contribute!
|
|
||||||
|
|
||||||
## Data - Input/Output
|
## Data - Input/Output
|
||||||
All [input table formats supported by `polars`](https://docs.pola.rs/user-guide/io/) are supported by `carousel` (`csv`, `excel`, `json` to name a few),
|
All [input table formats supported by `polars`](https://docs.pola.rs/user-guide/io/) are supported by `carousel` (`csv`, `excel`, `json` to name a few),
|
||||||
which accepts a few inter-related tabular schemes for the input/output data.
|
which accepts a few inter-related tabular schemes for the input/output data.
|
||||||
|
|
||||||
### Input
|
### Input
|
||||||
Input describes the preferences/rankings of the "applicants" of "reviewers" to which they will be matched (possibly many-to-one, as in the "College Admission Problem"), as well as the preferences/rankings for the reviwers of applicants.
|
Input describes the preferences/rankings of the "applicants" of "reviewers" to which they will be matched (possibly many-to-one, as in the "College Admission Problem"),
|
||||||
Input should be in one of two forms:
|
as well as the preferences/rankings for the reviwers of applicants.
|
||||||
|
Input should be in one of three forms:
|
||||||
|
|
||||||
#### Preferences
|
#### Preferences
|
||||||
Preferences enumerate by-name some preferences in descending order,
|
Preferences enumerate by-name some preferences in descending order,
|
||||||
|
|
@ -60,13 +61,10 @@ e.g. Alice, Bob and Charlie rank the fruit apples, bananas and cherries as:
|
||||||
| banana | 3 | 1 | 2 |
|
| banana | 3 | 1 | 2 |
|
||||||
| cherry | 2 | 3 | 1 |
|
| cherry | 2 | 3 | 1 |
|
||||||
|
|
||||||
### Intermediate
|
|
||||||
|
|
||||||
#### Ranking Matrix
|
#### Ranking Matrix
|
||||||
In order to perform a matching, `carousel` either needs a pair of preferences
|
In order to perform a matching, `carousel` either needs a pair of preferences
|
||||||
(e.g. a set of doctor's preferences for residencies, and a set of residencies' preferences for doctors),
|
(e.g. a set of doctor's preferences for residencies, and a set of residencies' preferences for doctors),
|
||||||
a pair of corresponding rankings. A ranking matrix is a concise way to express
|
a pair of corresponding rankings, *or* a matrix encoding both rankings at once:
|
||||||
a pair of rankings - used to display rankings:
|
|
||||||
|
|
||||||
| names | Alice | Bob | Charlie |
|
| names | Alice | Bob | Charlie |
|
||||||
|---------|------------|---------|-----------|
|
|---------|------------|---------|-----------|
|
||||||
|
|
@ -74,25 +72,6 @@ a pair of rankings - used to display rankings:
|
||||||
| CaseMed | (3, 2) | (1, 1) | (2, 3) |
|
| CaseMed | (3, 2) | (1, 1) | (2, 3) |
|
||||||
| Emory | (2, 1) | (3, 3) | (1, 2) |
|
| Emory | (2, 1) | (3, 3) | (1, 2) |
|
||||||
|
|
||||||
|
|
||||||
#### Priorities
|
|
||||||
Internally, preferences/rankings are often serialized to rows as a priority listing of the form:
|
|
||||||
|
|
||||||
| App. | Rank | Rev |
|
|
||||||
|-------|---|--------|
|
|
||||||
| Alice | 1 | apple |
|
|
||||||
| Alice | 2 | cherry |
|
|
||||||
| Alice | 3 | banana |
|
|
||||||
| Bob | 1 | banana |
|
|
||||||
| Bob | 2 | apple |
|
|
||||||
| Bob | 3 | cherry |
|
|
||||||
| Charlie | 1 | cherry |
|
|
||||||
| Charlie | 2 | banana |
|
|
||||||
| Charlie | 3 | apple |
|
|
||||||
|
|
||||||
(in no specific row order). This is not a supported input format in the frontends, but may be relevant
|
|
||||||
when interacting with the package programmatically - priorities can be followed as edges in a graph.
|
|
||||||
|
|
||||||
### Output
|
### Output
|
||||||
|
|
||||||
#### Matching
|
#### Matching
|
||||||
|
|
@ -133,9 +112,6 @@ using the [pages.uoregon.edu CGI feature](https://service.uoregon.edu/TDClient/2
|
||||||
Submit your applicant and reviewer preferences in tabular form,
|
Submit your applicant and reviewer preferences in tabular form,
|
||||||
or as excel uploads and the server will return a table of matches for you to choose from.
|
or as excel uploads and the server will return a table of matches for you to choose from.
|
||||||
|
|
||||||
This resource has *very* limited compute, so excessive usage might result in limitations or restricted access.
|
|
||||||
Try not to ruin a good thing! A moderate number of problems on the scale of those in the Examples section below should be sustainable.
|
|
||||||
|
|
||||||
### Command Line Binary
|
### Command Line Binary
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
@ -165,13 +141,13 @@ TODO
|
||||||
It's often desirable to enforce additional criteria on solutions
|
It's often desirable to enforce additional criteria on solutions
|
||||||
that are not well-posed within the core optimization problem.
|
that are not well-posed within the core optimization problem.
|
||||||
Since the solver itself is stochastic to some extent, these are often most easily implemented
|
Since the solver itself is stochastic to some extent, these are often most easily implemented
|
||||||
by a post-selection on a sampling of solutions.
|
by a post-selection.
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Here are some usage examples:
|
Here are some usage examples:
|
||||||
|
|
||||||
### University of Oregon Physics Department Graduate TA Assignments
|
### Departmental TA Assignments
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
### Caltech Housing Rotation
|
### Caltech Housing Rotation
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
@article{gale&shapley1962,
|
@article{gale&shapley1962,
|
||||||
ISSN = {0002989, 19300972},
|
ISSN = {0002989, 19300972},
|
||||||
URL = {https://www.jstor.org/stable/2312726},
|
URL = {https://www.jstor.org/stable/2312726},
|
||||||
|
|
@ -12,64 +14,3 @@
|
||||||
year = {1962},
|
year = {1962},
|
||||||
}
|
}
|
||||||
|
|
||||||
@article{doi:10.1137/0215048,
|
|
||||||
author = {Irving, Robert W. and Leather, Paul},
|
|
||||||
title = {The Complexity of Counting Stable Marriages},
|
|
||||||
journal = {SIAM Journal on Computing},
|
|
||||||
volume = {15},
|
|
||||||
number = {3},
|
|
||||||
pages = {655-667},
|
|
||||||
year = {1986},
|
|
||||||
doi = {10.1137/0215048},
|
|
||||||
URL = {https://doi.org/10.1137/0215048},
|
|
||||||
eprint = {https://doi.org/10.1137/0215048}
|
|
||||||
}
|
|
||||||
|
|
||||||
@article{doi:10.1137/0216010,
|
|
||||||
author = {Gusfield, Dan},
|
|
||||||
title = {Three Fast Algorithms for Four Problems in Stable Marriage},
|
|
||||||
journal = {SIAM Journal on Computing},
|
|
||||||
volume = {16},
|
|
||||||
number = {1},
|
|
||||||
pages = {111-128},
|
|
||||||
year = {1987},
|
|
||||||
doi = {10.1137/0216010},
|
|
||||||
URL = { https://doi.org/10.1137/0216010},
|
|
||||||
eprint = { https://doi.org/10.1137/0216010 }
|
|
||||||
}
|
|
||||||
|
|
||||||
@article{https://doi.org/10.3982/TE4830,
|
|
||||||
author = {Huang, Chao},
|
|
||||||
title = {Stable matching: An integer programming approach},
|
|
||||||
journal = {Theoretical Economics},
|
|
||||||
volume = {18},
|
|
||||||
number = {1},
|
|
||||||
pages = {37-63},
|
|
||||||
keywords = {Two-sided matching, stability, integer programming, many-to-one matching, complementarity, total unimodularity, demand type, C61, C78, D47, D63},
|
|
||||||
doi = {https://doi.org/10.3982/TE4830},
|
|
||||||
url = {https://onlinelibrary.wiley.com/doi/abs/10.3982/TE4830},
|
|
||||||
eprint = {https://onlinelibrary.wiley.com/doi/pdf/10.3982/TE4830}
|
|
||||||
}
|
|
||||||
|
|
||||||
@article{DELORME2019426,
|
|
||||||
title = {Mathematical models for stable matching problems with ties and incomplete lists},
|
|
||||||
journal = {European Journal of Operational Research},
|
|
||||||
volume = {277},
|
|
||||||
number = {2},
|
|
||||||
pages = {426-441},
|
|
||||||
year = {2019},
|
|
||||||
issn = {0377-2217},
|
|
||||||
doi = {https://doi.org/10.1016/j.ejor.2019.03.017},
|
|
||||||
url = {https://www.sciencedirect.com/science/article/pii/S0377221719302565},
|
|
||||||
author = {Maxence Delorme and Sergio García and Jacek Gondzio and Jörg Kalcsics and David Manlove and William Pettersson},
|
|
||||||
}
|
|
||||||
|
|
||||||
@misc{gutin2024findingstablematchingsassignment,
|
|
||||||
title={Finding all stable matchings with assignment constraints},
|
|
||||||
author={Gregory Gutin and Philip R. Neary and Anders Yeo},
|
|
||||||
year={2024},
|
|
||||||
eprint={2204.03989},
|
|
||||||
archivePrefix={arXiv},
|
|
||||||
primaryClass={econ.TH},
|
|
||||||
url={https://arxiv.org/abs/2204.03989},
|
|
||||||
}
|
|
||||||
|
|
|
||||||
22
justfile
22
justfile
|
|
@ -1,11 +1,14 @@
|
||||||
list:
|
list:
|
||||||
just --list
|
just --list
|
||||||
|
|
||||||
#run:
|
run:
|
||||||
# uv run carousel
|
uv run carousel
|
||||||
|
|
||||||
python *arguments:
|
python *arguments:
|
||||||
uv run python -c {{arguments}}
|
uv run python -c "import code; from rich import pretty; pretty.install(); code.interact()" {{arguments}}
|
||||||
|
|
||||||
|
check:
|
||||||
|
uv run pyright src
|
||||||
|
|
||||||
test:
|
test:
|
||||||
uv run pytest -vvv --tb=short --log-cli-level=INFO
|
uv run pytest -vvv --tb=short --log-cli-level=INFO
|
||||||
|
|
@ -13,16 +16,7 @@ test:
|
||||||
format:
|
format:
|
||||||
uv run ruff format src test
|
uv run ruff format src test
|
||||||
|
|
||||||
check:
|
compile:
|
||||||
uv run pyright src
|
|
||||||
|
|
||||||
sync:
|
|
||||||
uv sync --upgrade
|
|
||||||
|
|
||||||
lock:
|
|
||||||
uv pip compile pyproject.toml -o requirements.txt --group dev
|
|
||||||
|
|
||||||
build:
|
|
||||||
uv run pyinstaller src/cli.py
|
uv run pyinstaller src/cli.py
|
||||||
uv run pyinstaller src/gui.py
|
uv run pyinstaller src/gui.py
|
||||||
|
|
||||||
|
|
@ -35,3 +29,5 @@ wipe:
|
||||||
just clean
|
just clean
|
||||||
rm -rf .venv
|
rm -rf .venv
|
||||||
|
|
||||||
|
lock:
|
||||||
|
uv pip compile pyproject.toml -o requirements.txt --group dev
|
||||||
|
|
|
||||||
153
requirements.txt
153
requirements.txt
|
|
@ -1,137 +1,42 @@
|
||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile pyproject.toml -o requirements.txt --group dev
|
# uv pip compile pyproject.toml -o requirements.txt --group dev
|
||||||
altair==5.5.0
|
|
||||||
# via marimo
|
|
||||||
altgraph==0.17.4
|
altgraph==0.17.4
|
||||||
# via pyinstaller
|
# via pyinstaller
|
||||||
annotated-types==0.7.0
|
|
||||||
# via pydantic
|
|
||||||
anyio==4.10.0
|
|
||||||
# via
|
|
||||||
# httpx
|
|
||||||
# openai
|
|
||||||
# starlette
|
|
||||||
attrs==25.3.0
|
attrs==25.3.0
|
||||||
# via
|
# via hypothesis
|
||||||
# hypothesis
|
|
||||||
# jsonschema
|
|
||||||
# referencing
|
|
||||||
certifi==2025.8.3
|
|
||||||
# via
|
|
||||||
# httpcore
|
|
||||||
# httpx
|
|
||||||
click==8.1.8
|
click==8.1.8
|
||||||
# via
|
# via carousel (pyproject.toml)
|
||||||
# carousel (pyproject.toml)
|
|
||||||
# marimo
|
|
||||||
# uvicorn
|
|
||||||
distro==1.9.0
|
|
||||||
# via openai
|
|
||||||
docutils==0.22
|
|
||||||
# via marimo
|
|
||||||
duckdb==1.3.2
|
|
||||||
# via marimo
|
|
||||||
faker==37.6.0
|
|
||||||
# via carousel (pyproject.toml:dev)
|
|
||||||
fastjsonschema==2.21.2
|
|
||||||
# via nbformat
|
|
||||||
h11==0.16.0
|
|
||||||
# via
|
|
||||||
# httpcore
|
|
||||||
# uvicorn
|
|
||||||
httpcore==1.0.9
|
|
||||||
# via httpx
|
|
||||||
httpx==0.28.1
|
|
||||||
# via openai
|
|
||||||
hypothesis==6.131.5
|
hypothesis==6.131.5
|
||||||
# via carousel (pyproject.toml:dev)
|
# via carousel (pyproject.toml:dev)
|
||||||
idna==3.10
|
|
||||||
# via
|
|
||||||
# anyio
|
|
||||||
# httpx
|
|
||||||
iniconfig==2.1.0
|
iniconfig==2.1.0
|
||||||
# via pytest
|
# via pytest
|
||||||
itsdangerous==2.2.0
|
|
||||||
# via marimo
|
|
||||||
jedi==0.19.2
|
|
||||||
# via marimo
|
|
||||||
jinja2==3.1.6
|
|
||||||
# via altair
|
|
||||||
jiter==0.10.0
|
|
||||||
# via openai
|
|
||||||
jsonschema==4.25.1
|
|
||||||
# via
|
|
||||||
# altair
|
|
||||||
# nbformat
|
|
||||||
jsonschema-specifications==2025.4.1
|
|
||||||
# via jsonschema
|
|
||||||
jupyter-core==5.8.1
|
|
||||||
# via nbformat
|
|
||||||
loro==1.6.0
|
|
||||||
# via marimo
|
|
||||||
marimo==0.15.2
|
|
||||||
# via carousel (pyproject.toml:dev)
|
|
||||||
markdown==3.9
|
|
||||||
# via
|
|
||||||
# marimo
|
|
||||||
# pymdown-extensions
|
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
# via rich
|
# via rich
|
||||||
markupsafe==3.0.2
|
|
||||||
# via jinja2
|
|
||||||
mdurl==0.1.2
|
mdurl==0.1.2
|
||||||
# via markdown-it-py
|
# via markdown-it-py
|
||||||
narwhals==2.3.0
|
|
||||||
# via
|
|
||||||
# altair
|
|
||||||
# marimo
|
|
||||||
nbformat==5.10.4
|
|
||||||
# via marimo
|
|
||||||
nodeenv==1.9.1
|
nodeenv==1.9.1
|
||||||
# via pyright
|
# via pyright
|
||||||
numpy==2.2.4
|
numpy==2.2.4
|
||||||
# via carousel (pyproject.toml)
|
# via carousel (pyproject.toml)
|
||||||
openai==1.106.1
|
|
||||||
# via marimo
|
|
||||||
packaging==24.2
|
packaging==24.2
|
||||||
# via
|
# via
|
||||||
# altair
|
|
||||||
# marimo
|
|
||||||
# pyinstaller
|
# pyinstaller
|
||||||
# pyinstaller-hooks-contrib
|
# pyinstaller-hooks-contrib
|
||||||
# pytest
|
# pytest
|
||||||
parso==0.8.5
|
|
||||||
# via jedi
|
|
||||||
platformdirs==4.4.0
|
|
||||||
# via jupyter-core
|
|
||||||
pluggy==1.5.0
|
pluggy==1.5.0
|
||||||
# via pytest
|
# via pytest
|
||||||
polars==1.27.1
|
polars==1.27.1
|
||||||
# via
|
# via carousel (pyproject.toml)
|
||||||
# carousel (pyproject.toml)
|
|
||||||
# marimo
|
|
||||||
psutil==7.0.0
|
|
||||||
# via marimo
|
|
||||||
py-cpuinfo==9.0.0
|
py-cpuinfo==9.0.0
|
||||||
# via pytest-benchmark
|
# via pytest-benchmark
|
||||||
pyarrow==21.0.0
|
|
||||||
# via polars
|
|
||||||
pyclean==3.1.0
|
pyclean==3.1.0
|
||||||
# via carousel (pyproject.toml:dev)
|
# via carousel (pyproject.toml:dev)
|
||||||
pydantic==2.11.7
|
|
||||||
# via openai
|
|
||||||
pydantic-core==2.33.2
|
|
||||||
# via pydantic
|
|
||||||
pygments==2.19.1
|
pygments==2.19.1
|
||||||
# via
|
# via rich
|
||||||
# marimo
|
|
||||||
# rich
|
|
||||||
pyinstaller==6.13.0
|
pyinstaller==6.13.0
|
||||||
# via carousel (pyproject.toml:dev)
|
# via carousel (pyproject.toml:dev)
|
||||||
pyinstaller-hooks-contrib==2025.3
|
pyinstaller-hooks-contrib==2025.3
|
||||||
# via pyinstaller
|
# via pyinstaller
|
||||||
pymdown-extensions==10.16.1
|
|
||||||
# via marimo
|
|
||||||
pyright==1.1.399
|
pyright==1.1.399
|
||||||
# via carousel (pyproject.toml:dev)
|
# via carousel (pyproject.toml:dev)
|
||||||
pyside6==6.9.0
|
pyside6==6.9.0
|
||||||
|
|
@ -148,24 +53,10 @@ pytest==8.3.5
|
||||||
# pytest-benchmark
|
# pytest-benchmark
|
||||||
pytest-benchmark==5.1.0
|
pytest-benchmark==5.1.0
|
||||||
# via carousel (pyproject.toml:dev)
|
# via carousel (pyproject.toml:dev)
|
||||||
pyyaml==6.0.2
|
|
||||||
# via
|
|
||||||
# marimo
|
|
||||||
# pymdown-extensions
|
|
||||||
referencing==0.36.2
|
|
||||||
# via
|
|
||||||
# jsonschema
|
|
||||||
# jsonschema-specifications
|
|
||||||
rich==14.0.0
|
rich==14.0.0
|
||||||
# via carousel (pyproject.toml)
|
# via carousel (pyproject.toml)
|
||||||
rpds-py==0.27.1
|
|
||||||
# via
|
|
||||||
# jsonschema
|
|
||||||
# referencing
|
|
||||||
ruff==0.11.6
|
ruff==0.11.6
|
||||||
# via
|
# via carousel (pyproject.toml:dev)
|
||||||
# carousel (pyproject.toml:dev)
|
|
||||||
# marimo
|
|
||||||
setuptools==78.1.0
|
setuptools==78.1.0
|
||||||
# via
|
# via
|
||||||
# pyinstaller
|
# pyinstaller
|
||||||
|
|
@ -175,39 +66,7 @@ shiboken6==6.9.0
|
||||||
# pyside6
|
# pyside6
|
||||||
# pyside6-addons
|
# pyside6-addons
|
||||||
# pyside6-essentials
|
# pyside6-essentials
|
||||||
sniffio==1.3.1
|
|
||||||
# via
|
|
||||||
# anyio
|
|
||||||
# openai
|
|
||||||
sortedcontainers==2.4.0
|
sortedcontainers==2.4.0
|
||||||
# via hypothesis
|
# via hypothesis
|
||||||
sqlglot==27.12.0
|
|
||||||
# via marimo
|
|
||||||
starlette==0.47.3
|
|
||||||
# via marimo
|
|
||||||
tomlkit==0.13.3
|
|
||||||
# via marimo
|
|
||||||
tqdm==4.67.1
|
|
||||||
# via openai
|
|
||||||
traitlets==5.14.3
|
|
||||||
# via
|
|
||||||
# jupyter-core
|
|
||||||
# nbformat
|
|
||||||
ty==0.0.1a20
|
|
||||||
# via carousel (pyproject.toml:dev)
|
|
||||||
typing-extensions==4.13.2
|
typing-extensions==4.13.2
|
||||||
# via
|
# via pyright
|
||||||
# altair
|
|
||||||
# openai
|
|
||||||
# pydantic
|
|
||||||
# pydantic-core
|
|
||||||
# pyright
|
|
||||||
# typing-inspection
|
|
||||||
typing-inspection==0.4.1
|
|
||||||
# via pydantic
|
|
||||||
tzdata==2025.2
|
|
||||||
# via faker
|
|
||||||
uvicorn==0.35.0
|
|
||||||
# via marimo
|
|
||||||
websockets==15.0.1
|
|
||||||
# via marimo
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import logging, rich
|
import logging, rich
|
||||||
from rich.logging import RichHandler
|
from rich.logging import RichHandler
|
||||||
|
|
||||||
|
rich.traceback.install()
|
||||||
|
|
||||||
import itertools as it
|
import itertools as it
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import polars as pl
|
import polars as pl
|
||||||
import polars.selectors as pls
|
import polars.selectors as pls
|
||||||
|
|
||||||
rich.traceback.install()
|
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format="%(message)s",
|
format="%(message)s",
|
||||||
|
|
@ -24,7 +24,126 @@ logging.basicConfig(
|
||||||
)
|
)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
from .util import *
|
|
||||||
|
def rank_to_pref(ranking):
|
||||||
|
"""Converts a ranking to a preference."""
|
||||||
|
id_col_name = ranking.select(pls.by_index(0)).to_series().name
|
||||||
|
preferences = ranking.select(
|
||||||
|
[
|
||||||
|
pl.col(id_col_name).sort_by(c).alias(c)
|
||||||
|
for c in ranking.columns
|
||||||
|
if c != id_col_name
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return preferences
|
||||||
|
|
||||||
|
|
||||||
|
def pref_to_rank(preferences):
|
||||||
|
"""Converts a preference to a ranking."""
|
||||||
|
o = preferences.select(
|
||||||
|
pl.concat_list(preferences.columns).explode().unique().sort().alias("")
|
||||||
|
) # .with_row_index(offset=1)
|
||||||
|
|
||||||
|
ranking = pl.concat(
|
||||||
|
[
|
||||||
|
o.join(
|
||||||
|
preferences.with_row_index(offset=1),
|
||||||
|
how="full",
|
||||||
|
left_on="",
|
||||||
|
right_on=c,
|
||||||
|
maintain_order="left",
|
||||||
|
).select(pl.col("index").alias(c))
|
||||||
|
for c in preferences.columns
|
||||||
|
],
|
||||||
|
how="horizontal",
|
||||||
|
)
|
||||||
|
return pl.concat([o, ranking], how="horizontal")
|
||||||
|
|
||||||
|
|
||||||
|
""""
|
||||||
|
def ranking_matrix(A, B):
|
||||||
|
T = pl.concat([A, B], how="horizontal")
|
||||||
|
|
||||||
|
TT = T.with_columns(pl.concat_list(A.columns[0], B.columns[0]))
|
||||||
|
for ab in zip(A.columns[1:], B.columns[1:]):
|
||||||
|
TT = TT.with_columns(pl.concat_list(*ab))
|
||||||
|
TTT = TT.select(pl.col(A.columns))
|
||||||
|
|
||||||
|
return TTT.insert_column(0, pl.Series("names", B.columns))
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def check_valid_pref(preferences):
|
||||||
|
"""A valid set of preferences has all unique entries in each column, TODO"""
|
||||||
|
repeats = preferences.select(
|
||||||
|
(~pl.all_horizontal((pl.all().is_unique() | pl.all().is_null()).all())).alias(
|
||||||
|
"repeats"
|
||||||
|
)
|
||||||
|
).get_column("repeats")[0]
|
||||||
|
return not repeats
|
||||||
|
|
||||||
|
|
||||||
|
def check_valid_rank(ranking):
|
||||||
|
"""A valid ranking has no ties, TODO"""
|
||||||
|
ties = ranking.select(
|
||||||
|
(~pl.all_horizontal((pl.all().is_unique() | pl.all().is_null()).all())).alias(
|
||||||
|
"ties"
|
||||||
|
)
|
||||||
|
).get_column("ties")[0]
|
||||||
|
return not ties
|
||||||
|
|
||||||
|
|
||||||
|
def check_valid_match(match, applicants, reviewers):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def check_valid_assgn(assgn, applicants, reviewers):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def assgn_to_match(assgn):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def match_to_assgn(match):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_rank(ranking, ranker, ranked):
|
||||||
|
idx = ranking.select(pl.arg_where(pl.col("") == ranked)).item()
|
||||||
|
return ranking[ranker][idx]
|
||||||
|
|
||||||
|
|
||||||
|
def check_unstable(match, applicant_ranking, reviewer_ranking):
|
||||||
|
applicants = applicant_ranking.columns[1:] # assume unique applicants
|
||||||
|
for a, b in it.permutations(applicants, 2):
|
||||||
|
A = (
|
||||||
|
match.select(c for c in match.iter_columns() if a in c).to_series().name
|
||||||
|
) # the reviewer a is matched to
|
||||||
|
B = (
|
||||||
|
match.select(c for c in match.iter_columns() if b in c).to_series().name
|
||||||
|
) # the reviewer b is matched to
|
||||||
|
|
||||||
|
b_prefers_A = get_rank(applicant_ranking, b, A) < get_rank(
|
||||||
|
applicant_ranking, b, B
|
||||||
|
)
|
||||||
|
A_prefers_b = get_rank(reviewer_ranking, A, b) < get_rank(
|
||||||
|
reviewer_ranking, A, a
|
||||||
|
)
|
||||||
|
if b_prefers_A and A_prefers_b:
|
||||||
|
return True
|
||||||
|
# else
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def check_stable(*args, **kwargs):
|
||||||
|
return not check_unstable(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
from .brute import *
|
from .brute import *
|
||||||
from .def_acc import *
|
from .def_acc import *
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
def brute_match(applicant_rankings, reviewer_rankings):
|
def brute(applicant_rankings, reviewer_rankings):
|
||||||
"""Brute force combinatoric search for stable matches."""
|
"""Brute force search for stable matches."""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,126 +1,5 @@
|
||||||
from .util import *
|
def deferred_acceptance(applicant_rankings, reviewer_rankings):
|
||||||
|
"""Find Gale-Shapley deferred-acceptance stable matchings for preferences A, R."""
|
||||||
|
|
||||||
def preparefor_def_acc(applicant_rankings, reviewer_rankings):
|
|
||||||
"""Sanitize/Format applicant and reviewer rankings for the deferred acceptance solver."""
|
|
||||||
pass
|
|
||||||
# return app_ranks, rev_ranks
|
|
||||||
|
|
||||||
|
|
||||||
def matchby_deferred_acceptance(applicant_rankings, reviewer_rankings, revfirst=False):
|
|
||||||
"""Find the Gale-Shapley deferred-acceptance stable matching for rankings A, R. Default to A-first unless `revfirst=true`."""
|
|
||||||
|
|
||||||
app_prio = rank_to_prio(
|
|
||||||
applicant_rankings, prioritizer="applicant", priority="reviewer"
|
|
||||||
)
|
|
||||||
rev_prio = rank_to_prio(
|
|
||||||
reviewer_rankings, prioritizer="reviewer", priority="applicant"
|
|
||||||
)
|
|
||||||
|
|
||||||
state = app_prio.select(
|
|
||||||
[
|
|
||||||
pl.col("applicant").unique().alias("applicant"),
|
|
||||||
]
|
|
||||||
).with_columns([pl.lit(0).alias("next_rank"), pl.lit(True).alias("is_free")])
|
|
||||||
|
|
||||||
matches = pl.DataFrame(
|
|
||||||
{
|
|
||||||
"applicant": pl.Series([]),
|
|
||||||
"reviewer": pl.Series([]),
|
|
||||||
"current": pl.Series([], dtype=pl.Int64),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
max_iters = len(state) * len(rev_prio.select("applicant").unique())
|
|
||||||
|
|
||||||
for _ in range(max_iters):
|
|
||||||
props = (
|
|
||||||
state.filter(pl.col("is_free"))
|
|
||||||
.join(app_prio, on="applicant")
|
|
||||||
.filter(pl.col("rank") == pl.col("next_rank"))
|
|
||||||
.select(["applicant", "reviewer"])
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(props) == 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
props = props.join(rev_prio, on=["reviewer", "applicant"], how="left")
|
|
||||||
|
|
||||||
props = props.join(
|
|
||||||
matches.select(
|
|
||||||
["reviewer", pl.col("applicant").alias("proposer"), pl.col("current")]
|
|
||||||
),
|
|
||||||
on="reviewer",
|
|
||||||
how="left",
|
|
||||||
)
|
|
||||||
|
|
||||||
props = props.with_columns(
|
|
||||||
[
|
|
||||||
(
|
|
||||||
pl.col("proposer").is_null() | (pl.col("rank") < pl.col("current"))
|
|
||||||
).alias("accepted")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
accepted = proposals.filter(pl.col("accepted"))
|
|
||||||
rejected = proposals.filter(~pl.col("accepted"))
|
|
||||||
|
|
||||||
displaced = accepted.filter(pl.col("proposer").is_not_null()).select(
|
|
||||||
pl.col("proposer").alias("applicant")
|
|
||||||
)
|
|
||||||
|
|
||||||
matches = matches.join(accepted.select("reviewer"), on="reviewer", how="anti")
|
|
||||||
|
|
||||||
matches = pl.concat(
|
|
||||||
[
|
|
||||||
matches,
|
|
||||||
accepted.select(
|
|
||||||
["reviewer", "applicant", pl.col("rank").alias("current")]
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
rejected_apps = rejected.select("applicant")
|
|
||||||
accepted_apps = accepted.select("applicant")
|
|
||||||
|
|
||||||
state = (
|
|
||||||
state.join(
|
|
||||||
rejected_apps.with_columns(pl.lit(True).alias("was_rejected")),
|
|
||||||
on="applicant",
|
|
||||||
how="left",
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
accepted_apps.with_columns(pl.lit(True).alias("was_accepted")),
|
|
||||||
on="applicant",
|
|
||||||
how="left",
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
displaced.with_columns(pl.lit(True).alias("was_displaced")),
|
|
||||||
on="applicant",
|
|
||||||
how="left",
|
|
||||||
)
|
|
||||||
.with_columns(
|
|
||||||
[
|
|
||||||
pl.when(pl.col("was_rejected").fill_null(False))
|
|
||||||
.then(pl.col("next_rank") + 1)
|
|
||||||
.otherwise(pl.col("next_rank"))
|
|
||||||
.alias("next_rank"),
|
|
||||||
pl.when(pl.col("was_accepted").fill_null(False))
|
|
||||||
.then(False)
|
|
||||||
.when(pl.col("was_displaced").full_null(False))
|
|
||||||
.then(True)
|
|
||||||
.otherwise(pl.col("is_free"))
|
|
||||||
.alias("is_free"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
.select(["applicant", "next_rank", "is_free"])
|
|
||||||
)
|
|
||||||
|
|
||||||
return matches.select(["applicant", "reviewer"])
|
|
||||||
|
|
||||||
|
|
||||||
def deferred_acceptance_match(applicant_rankings, reviewer_rankings):
|
|
||||||
"""Find Gale-Shapley deferred-acceptance stable matching for rankings A, R."""
|
|
||||||
reviewer_rankings = reviewer_rankings.rename(
|
reviewer_rankings = reviewer_rankings.rename(
|
||||||
{reviewer_rankings.columns[0]: "applicant"}
|
{reviewer_rankings.columns[0]: "applicant"}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
import itertools as it
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
import polars as pl
|
|
||||||
import polars.selectors as pls
|
|
||||||
|
|
||||||
# Stability
|
|
||||||
|
|
||||||
|
|
||||||
def get_rank(ranking, ranker, ranked):
|
|
||||||
idx = ranking.select(pl.arg_where(pl.col("") == ranked)).item()
|
|
||||||
return ranking[ranker][idx]
|
|
||||||
|
|
||||||
|
|
||||||
def check_match_unstable(match, applicant_ranking, reviewer_ranking):
|
|
||||||
applicants = applicant_ranking.columns[1:] # assume unique applicants
|
|
||||||
for a, b in it.permutations(applicants, 2):
|
|
||||||
A = (
|
|
||||||
match.select(c for c in match.iter_columns() if a in c).to_series().name
|
|
||||||
) # the reviewer a is matched to
|
|
||||||
B = (
|
|
||||||
match.select(c for c in match.iter_columns() if b in c).to_series().name
|
|
||||||
) # the reviewer b is matched to
|
|
||||||
|
|
||||||
b_prefers_A = get_rank(applicant_ranking, b, A) < get_rank(
|
|
||||||
applicant_ranking, b, B
|
|
||||||
)
|
|
||||||
A_prefers_b = get_rank(reviewer_ranking, A, b) < get_rank(
|
|
||||||
reviewer_ranking, A, a
|
|
||||||
)
|
|
||||||
if b_prefers_A and A_prefers_b:
|
|
||||||
return True
|
|
||||||
# else
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def check_match_stable(*args, **kwargs):
|
|
||||||
return not check_match_unstable(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Conversions
|
|
||||||
|
|
||||||
|
|
||||||
def rank_to_pref(ranking):
|
|
||||||
"""Converts a ranking to a preference."""
|
|
||||||
id_col_name = ranking.select(pls.by_index(0)).to_series().name
|
|
||||||
preferences = ranking.select(
|
|
||||||
[
|
|
||||||
pl.col(id_col_name).sort_by(c).alias(c)
|
|
||||||
for c in ranking.columns
|
|
||||||
if c != id_col_name
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return preferences
|
|
||||||
|
|
||||||
|
|
||||||
def pref_to_rank(preferences):
|
|
||||||
"""Converts a preference to a ranking."""
|
|
||||||
o = preferences.select(
|
|
||||||
pl.concat_list(preferences.columns).explode().unique().sort().alias("")
|
|
||||||
) # .with_row_index(offset=1)
|
|
||||||
|
|
||||||
ranking = pl.concat(
|
|
||||||
[
|
|
||||||
o.join(
|
|
||||||
preferences.with_row_index(offset=1),
|
|
||||||
how="full",
|
|
||||||
left_on="",
|
|
||||||
right_on=c,
|
|
||||||
maintain_order="left",
|
|
||||||
).select(pl.col("index").alias(c))
|
|
||||||
for c in preferences.columns
|
|
||||||
],
|
|
||||||
how="horizontal",
|
|
||||||
)
|
|
||||||
return pl.concat([o, ranking], how="horizontal")
|
|
||||||
|
|
||||||
|
|
||||||
def ranks_to_mat(rankA, rankB):
|
|
||||||
return prefs_to_mat(rank_to_pref(rankA), rank_to_pref(rankB))
|
|
||||||
|
|
||||||
|
|
||||||
def prefs_to_mat(prefA, prefB):
|
|
||||||
return rank_to_mat(pref_to_rank(preferences), pref_to_rank(preferences[0]))
|
|
||||||
|
|
||||||
|
|
||||||
def mat_to_ranks(matrix):
|
|
||||||
pass # TODO
|
|
||||||
|
|
||||||
|
|
||||||
def mat_to_prefs(matrix):
|
|
||||||
rankA, rankB = mat_to_ranks(matrix)
|
|
||||||
return rank_to_pref(rankA), rank_to_preft(rankB)
|
|
||||||
|
|
||||||
|
|
||||||
def pref_to_prio(pref, prioritizer="prioritizer", priority="subject"):
|
|
||||||
return pref.with_row_index("rank").unpivot(
|
|
||||||
index="rank", variable_name=prioritizer, value_name=priority
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def rank_to_prio(rank, **kwargs):
|
|
||||||
return pref_to_prio(rank_to_pref(rank), **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
""""
|
|
||||||
def ranking_matrix(A, B):
|
|
||||||
T = pl.concat([A, B], how="horizontal")
|
|
||||||
|
|
||||||
TT = T.with_columns(pl.concat_list(A.columns[0], B.columns[0]))
|
|
||||||
for ab in zip(A.columns[1:], B.columns[1:]):
|
|
||||||
TT = TT.with_columns(pl.concat_list(*ab))
|
|
||||||
TTT = TT.select(pl.col(A.columns))
|
|
||||||
|
|
||||||
return TTT.insert_column(0, pl.Series("names", B.columns))
|
|
||||||
"""
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
|
|
||||||
def match_to_assignment(matching):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def assgn_to_match(assignment):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
## Internal
|
|
||||||
|
|
||||||
|
|
||||||
# Validity
|
|
||||||
|
|
||||||
|
|
||||||
def check_pref_allunique(preferences):
|
|
||||||
"""A valid set of preferences has all unique entries in each column."""
|
|
||||||
repeats = preferences.select(
|
|
||||||
(~pl.all_horizontal((pl.all().is_unique() | pl.all().is_null()).all())).alias(
|
|
||||||
"repeats"
|
|
||||||
)
|
|
||||||
).get_column("repeats")[0]
|
|
||||||
return not repeats
|
|
||||||
|
|
||||||
|
|
||||||
def check_rank_noties(ranking):
|
|
||||||
"""A valid ranking has no ties."""
|
|
||||||
ties = ranking.select(
|
|
||||||
(~pl.all_horizontal((pl.all().is_unique() | pl.all().is_null()).all())).alias(
|
|
||||||
"ties"
|
|
||||||
)
|
|
||||||
).get_column("ties")[0]
|
|
||||||
return not ties
|
|
||||||
|
|
@ -40,7 +40,7 @@ def test_invalid_pref():
|
||||||
pp = pl.DataFrame(
|
pp = pl.DataFrame(
|
||||||
{"a": ["A", "A", "B"], "b": ["B", "A", "C"], "c": ["C", "B", "A"]}
|
{"a": ["A", "A", "B"], "b": ["B", "A", "C"], "c": ["C", "B", "A"]}
|
||||||
)
|
)
|
||||||
assert crsl.check_pref_allunique(pp) is False
|
assert crsl.check_valid_pref(pp) is False
|
||||||
|
|
||||||
|
|
||||||
def test_pref_to_rank():
|
def test_pref_to_rank():
|
||||||
|
|
@ -51,7 +51,7 @@ def test_invalid_rank():
|
||||||
rr = pl.DataFrame(
|
rr = pl.DataFrame(
|
||||||
{"": ["A", "B", "C"], "a": [1, 1, 2], "b": [2, 1, 3], "c": [3, 2, 1]}
|
{"": ["A", "B", "C"], "a": [1, 1, 2], "b": [2, 1, 3], "c": [3, 2, 1]}
|
||||||
)
|
)
|
||||||
assert crsl.check_pref_allunique(rr) is False
|
assert crsl.check_valid_pref(rr) is False
|
||||||
|
|
||||||
|
|
||||||
def test_rank_to_pref():
|
def test_rank_to_pref():
|
||||||
|
|
@ -60,7 +60,7 @@ def test_rank_to_pref():
|
||||||
|
|
||||||
@given(rankings())
|
@given(rankings())
|
||||||
def test_valid_rank(R):
|
def test_valid_rank(R):
|
||||||
assert crsl.check_rank_noties(R)
|
assert crsl.check_valid_rank(R)
|
||||||
|
|
||||||
|
|
||||||
@given(rankings())
|
@given(rankings())
|
||||||
|
|
@ -70,7 +70,7 @@ def test_ranks_tofrom_prefs(R):
|
||||||
|
|
||||||
@given(preferences())
|
@given(preferences())
|
||||||
def test_valid_pref(P):
|
def test_valid_pref(P):
|
||||||
assert crsl.check_pref_allunique(P)
|
assert crsl.check_valid_pref(P)
|
||||||
|
|
||||||
|
|
||||||
@given(preferences())
|
@given(preferences())
|
||||||
|
|
@ -99,7 +99,7 @@ def test_eg2_unstable():
|
||||||
)
|
)
|
||||||
match = pl.DataFrame({"A": ["a"], "B": ["b"], "C": ["c"], "D": ["d"]})
|
match = pl.DataFrame({"A": ["a"], "B": ["b"], "C": ["c"], "D": ["d"]})
|
||||||
|
|
||||||
assert crsl.check_match_unstable(match, ar, rr)
|
assert crsl.check_unstable(match, ar, rr)
|
||||||
|
|
||||||
|
|
||||||
def test_eg2_isstable():
|
def test_eg2_isstable():
|
||||||
|
|
@ -123,7 +123,7 @@ def test_eg2_isstable():
|
||||||
)
|
)
|
||||||
match = pl.DataFrame({"A": ["c"], "B": ["d"], "C": ["a"], "D": ["b"]})
|
match = pl.DataFrame({"A": ["c"], "B": ["d"], "C": ["a"], "D": ["b"]})
|
||||||
|
|
||||||
assert crsl.check_match_stable(match, ar, rr)
|
assert crsl.check_stable(match, ar, rr)
|
||||||
|
|
||||||
|
|
||||||
@given(
|
@given(
|
||||||
|
|
@ -131,5 +131,5 @@ def test_eg2_isstable():
|
||||||
rankings(names=["A", "B", "C", "D"], choices=["a", "b", "c", "d"]),
|
rankings(names=["A", "B", "C", "D"], choices=["a", "b", "c", "d"]),
|
||||||
)
|
)
|
||||||
def test_defacc_isstable(applicant_rankings, reviewer_rankings):
|
def test_defacc_isstable(applicant_rankings, reviewer_rankings):
|
||||||
match = crsl.matchby_deferred_acceptance(applicant_rankings, reviewer_rankings)
|
match = crsl.deferred_acceptance(applicant_rankings, reviewer_rankings)
|
||||||
assert crsl.check_match_stable(match, applicant_rankings, reviewer_rankings)
|
assert crsl.check_stable(match, applicant_rankings, reviewer_rankings)
|
||||||
|
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
{
|
|
||||||
"version": "1",
|
|
||||||
"metadata": {
|
|
||||||
"marimo_version": "0.18.3"
|
|
||||||
},
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"id": "Hbol",
|
|
||||||
"code_hash": "bc65c35c6fad59890b50c502bb8affa4",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/markdown": "<span class=\"markdown prose dark:prose-invert contents\"><h1 id=\"caltech-hovse-rotation-example\">Caltech Hovse Rotation Example</h1></span>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "MJUe",
|
|
||||||
"code_hash": "ccb62cc29f2cec640b063832a90adfec",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/markdown": "<span class=\"markdown prose dark:prose-invert contents\"><h2 id=\"imports\">Imports</h2></span>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "vblA",
|
|
||||||
"code_hash": "9588e2b4004a32c73b7ecc0a968b666f",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/plain": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bkHC",
|
|
||||||
"code_hash": "4821038400db1f5dc63daf7ca5b26279",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/markdown": "<span class=\"markdown prose dark:prose-invert contents\"><h2 id=\"generating-rotation-rankings\">Generating Rotation Rankings</h2></span>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "lEQa",
|
|
||||||
"code_hash": "ecc9ae14c8c8f3875656fc620665c6b4",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/html": "<pre class='text-xs'>True</pre>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "NdeS",
|
|
||||||
"code_hash": "c582dec943ff7b743aa0691df291cea6",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/html": "<marimo-ui-element object-id='NdeS-0' random-id='7040b6c9-c598-beaa-38c6-9f1afe159be0'><marimo-table data-initial-value='[]' data-label='null' data-data='"[{\"\":\"A\",\"a\":1,\"b\":1,\"c\":2,\"d\":4},{\"\":\"B\",\"a\":2,\"b\":4,\"c\":1,\"d\":2},{\"\":\"C\",\"a\":3,\"b\":3,\"c\":3,\"d\":3},{\"\":\"D\",\"a\":4,\"b\":2,\"c\":4,\"d\":1}]"' data-total-rows='4' data-total-columns='5' data-max-columns='50' data-banner-text='""' data-pagination='true' data-page-size='10' data-field-types='[["",["string","str"]],["a",["integer","i64"]],["b",["integer","i64"]],["c",["integer","i64"]],["d",["integer","i64"]]]' data-show-filters='true' data-show-download='true' data-show-column-summaries='false' data-show-data-types='true' data-show-page-size-selector='false' data-show-column-explorer='true' data-show-chart-builder='true' data-row-headers='[]' data-has-stable-row-id='false' data-lazy='false' data-preload='false'></marimo-table></marimo-ui-element>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "PKri",
|
|
||||||
"code_hash": "b9f0f3a28a20d94d4b173bbbd49db37f",
|
|
||||||
"outputs": [],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "Xref",
|
|
||||||
"code_hash": "ccc8305d08e563d4fb1c9df31e9b7b69",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/html": "<marimo-ui-element object-id='Xref-0' random-id='b6beae57-9d1c-9561-11cf-94cfd5b3aa06'><marimo-table data-initial-value='[]' data-label='null' data-data='"[{\"a\":\"A\",\"b\":\"A\",\"c\":\"B\",\"d\":\"D\"},{\"a\":\"B\",\"b\":\"D\",\"c\":\"A\",\"d\":\"B\"},{\"a\":\"C\",\"b\":\"C\",\"c\":\"C\",\"d\":\"C\"},{\"a\":\"D\",\"b\":\"B\",\"c\":\"D\",\"d\":\"A\"}]"' data-total-rows='4' data-total-columns='4' data-max-columns='50' data-banner-text='""' data-pagination='true' data-page-size='10' data-field-types='[["a",["string","str"]],["b",["string","str"]],["c",["string","str"]],["d",["string","str"]]]' data-show-filters='true' data-show-download='true' data-show-column-summaries='false' data-show-data-types='true' data-show-page-size-selector='false' data-show-column-explorer='true' data-show-chart-builder='true' data-row-headers='[]' data-has-stable-row-id='false' data-lazy='false' data-preload='false'></marimo-table></marimo-ui-element>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "taaO",
|
|
||||||
"code_hash": "b712cc6f3d50763308dd5bc1c6703f77",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"type": "data",
|
|
||||||
"data": {
|
|
||||||
"text/html": "<marimo-ui-element object-id='taaO-0' random-id='1a8ede2c-9d90-fbec-5f1c-6258f3189f72'><marimo-table data-initial-value='[]' data-label='null' data-data='"[{\"rank\":0,\"prioritizer\":\"A\",\"subject\":\"d\"},{\"rank\":1,\"prioritizer\":\"A\",\"subject\":\"c\"},{\"rank\":2,\"prioritizer\":\"A\",\"subject\":\"a\"},{\"rank\":3,\"prioritizer\":\"A\",\"subject\":\"b\"},{\"rank\":0,\"prioritizer\":\"B\",\"subject\":\"b\"},{\"rank\":1,\"prioritizer\":\"B\",\"subject\":\"d\"},{\"rank\":2,\"prioritizer\":\"B\",\"subject\":\"a\"},{\"rank\":3,\"prioritizer\":\"B\",\"subject\":\"c\"},{\"rank\":0,\"prioritizer\":\"C\",\"subject\":\"d\"},{\"rank\":1,\"prioritizer\":\"C\",\"subject\":\"a\"}]"' data-total-rows='16' data-total-columns='3' data-max-columns='50' data-banner-text='""' data-pagination='true' data-page-size='10' data-field-types='[["rank",["integer","u32"]],["prioritizer",["string","str"]],["subject",["string","str"]]]' data-show-filters='true' data-show-download='true' data-show-column-summaries='true' data-show-data-types='true' data-show-page-size-selector='true' data-show-column-explorer='true' data-show-chart-builder='true' data-row-headers='[]' data-has-stable-row-id='false' data-lazy='false' data-preload='false'></marimo-table></marimo-ui-element>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"console": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "wJzy",
|
|
||||||
"code_hash": null,
|
|
||||||
"outputs": [],
|
|
||||||
"console": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
import marimo
|
import marimo
|
||||||
|
|
||||||
__generated_with = "0.18.3"
|
__generated_with = "0.13.6"
|
||||||
app = marimo.App(width="medium")
|
app = marimo.App(width="medium")
|
||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(mo):
|
def _(mo):
|
||||||
mo.md(r"""
|
mo.md(r"""# Caltech Hovse Rotation Example""")
|
||||||
# Caltech Hovse Rotation Example
|
|
||||||
""")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(mo):
|
def _(mo):
|
||||||
mo.md(r"""
|
mo.md(r"""## Imports""")
|
||||||
## Imports
|
|
||||||
""")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,73 +22,27 @@ def _():
|
||||||
import polars as pl, polars.selectors as pls
|
import polars as pl, polars.selectors as pls
|
||||||
import numpy as np, faker as fk
|
import numpy as np, faker as fk
|
||||||
import carousel as crsl
|
import carousel as crsl
|
||||||
|
|
||||||
return crsl, mo, pl
|
return crsl, mo, pl
|
||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(mo):
|
def _(mo):
|
||||||
mo.md(r"""
|
mo.md(r"""## Generating Rotation Rankings""")
|
||||||
## Generating Rotation Rankings
|
|
||||||
""")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
@app.cell
|
||||||
def _(crsl, pl):
|
def _(crsl, pl):
|
||||||
ar = pl.DataFrame(
|
ar = pl.DataFrame({ "": ["A", "B", "C", "D"], "a": [1, 2, 3, 4], "b": [1, 4, 3, 2], "c": [2, 1, 3, 4], "d": [4, 2, 3, 1]})
|
||||||
{
|
rr = pl.DataFrame({ "": ["a", "b", "c", "d"], "A": [3, 4, 2, 1], "B": [3, 1, 4, 2], "C": [2, 3, 4, 1], "D": [3, 2, 1, 4]})
|
||||||
"": ["A", "B", "C", "D"],
|
|
||||||
"a": [1, 2, 3, 4],
|
|
||||||
"b": [1, 4, 3, 2],
|
|
||||||
"c": [2, 1, 3, 4],
|
|
||||||
"d": [4, 2, 3, 1],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
rr = pl.DataFrame(
|
|
||||||
{
|
|
||||||
"": ["a", "b", "c", "d"],
|
|
||||||
"A": [3, 4, 2, 1],
|
|
||||||
"B": [3, 1, 4, 2],
|
|
||||||
"C": [2, 3, 4, 1],
|
|
||||||
"D": [3, 2, 1, 4],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
m = pl.DataFrame({"A": ["c"], "B": ["d"], "C": ["a"], "D": ["b"]})
|
m = pl.DataFrame({"A": ["c"], "B": ["d"], "C": ["a"], "D": ["b"]})
|
||||||
crsl.check_match_stable(m, ar, rr)
|
crsl.check_stable(m, ar, rr)
|
||||||
return ar, rr
|
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
|
||||||
def _(ar):
|
|
||||||
ar
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
@app.cell
|
||||||
def _():
|
def _():
|
||||||
hovses = [
|
hovses = ["Blacker", "Dabney", "Ricketts", "Fleming", "Page", "Lloyd", "Venerable", "Avery"]
|
||||||
"Blacker",
|
|
||||||
"Dabney",
|
|
||||||
"Ricketts",
|
|
||||||
"Fleming",
|
|
||||||
"Page",
|
|
||||||
"Lloyd",
|
|
||||||
"Venerable",
|
|
||||||
"Avery",
|
|
||||||
]
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
|
||||||
def _(ar, crsl):
|
|
||||||
crsl.rank_to_pref(ar)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
|
||||||
def _(crsl, rr):
|
|
||||||
crsl.pref_to_prio(crsl.rank_to_pref(rr))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue