mirror of
https://github.com/tgorordo/carousel.git
synced 2026-06-05 18:12:14 -07:00
Compare commits
2 commits
6cdc579270
...
f478c69c65
| Author | SHA1 | Date | |
|---|---|---|---|
| f478c69c65 | |||
| cbedbbeb48 |
12 changed files with 1875 additions and 611 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,30 +14,29 @@ bibliography: REFERENCES.bib
|
||||||
|
|
||||||
### Gale-Shapley Deferred Acceptance
|
### Gale-Shapley Deferred Acceptance
|
||||||
|
|
||||||
The most basic version(s) of the stable matching problem was outlined and solved in [@gale&shapley1962] and won Shapley the
|
- Gives a solution to the basic problem.
|
||||||
[2012 Nobel Prize in Economics](https://www.nobelprize.org/prizes/economic-sciences/2012/popular-information/).
|
|
||||||
|
|
||||||
The basic problem being solved is as follows:
|
### GS + Rotation Enumeration of Solutions
|
||||||
|
|
||||||
#### Stable Marriage Problem
|
- Can implement post-selection measures/constraints/criteria.
|
||||||
The "Stable Marriage problem" TODO
|
|
||||||
|
|
||||||
TODO
|
### Integer Programming (Huang++)
|
||||||
|
|
||||||
#### College Admissions Problem
|
### Polytope Solution Sampling (Large Problems)
|
||||||
Solving the stable marriage problem also provides a solution to the "College Admissions Problem",
|
|
||||||
with just a little more work.
|
|
||||||
|
|
||||||
TODO
|
### Brute-Force Combinatoric Search
|
||||||
|
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"),
|
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.
|
||||||
as well as the preferences/rankings for the reviwers of applicants.
|
Input should be in one of two forms:
|
||||||
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,
|
||||||
|
|
@ -61,10 +60,13 @@ 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, *or* a matrix encoding both rankings at once:
|
a pair of corresponding rankings. A ranking matrix is a concise way to express
|
||||||
|
a pair of rankings - used to display rankings:
|
||||||
|
|
||||||
| names | Alice | Bob | Charlie |
|
| names | Alice | Bob | Charlie |
|
||||||
|---------|------------|---------|-----------|
|
|---------|------------|---------|-----------|
|
||||||
|
|
@ -72,6 +74,25 @@ a pair of corresponding rankings, *or* a matrix encoding both rankings at once:
|
||||||
| 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
|
||||||
|
|
@ -112,6 +133,9 @@ 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
|
||||||
|
|
@ -141,13 +165,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.
|
by a post-selection on a sampling of solutions.
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Here are some usage examples:
|
Here are some usage examples:
|
||||||
|
|
||||||
### Departmental TA Assignments
|
### University of Oregon Physics Department Graduate TA Assignments
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
### Caltech Housing Rotation
|
### Caltech Housing Rotation
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
@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},
|
||||||
|
|
@ -14,3 +12,64 @@
|
||||||
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,14 +1,11 @@
|
||||||
list:
|
list:
|
||||||
just --list
|
just --list
|
||||||
|
|
||||||
run:
|
#run:
|
||||||
uv run carousel
|
# uv run carousel
|
||||||
|
|
||||||
python *arguments:
|
python *arguments:
|
||||||
uv run python -c "import code; from rich import pretty; pretty.install(); code.interact()" {{arguments}}
|
uv run python -c {{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
|
||||||
|
|
@ -16,7 +13,16 @@ test:
|
||||||
format:
|
format:
|
||||||
uv run ruff format src test
|
uv run ruff format src test
|
||||||
|
|
||||||
compile:
|
check:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -29,5 +35,3 @@ 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,42 +1,137 @@
|
||||||
# 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 hypothesis
|
# via
|
||||||
|
# hypothesis
|
||||||
|
# jsonschema
|
||||||
|
# referencing
|
||||||
|
certifi==2025.8.3
|
||||||
|
# via
|
||||||
|
# httpcore
|
||||||
|
# httpx
|
||||||
click==8.1.8
|
click==8.1.8
|
||||||
# via carousel (pyproject.toml)
|
# via
|
||||||
|
# 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 carousel (pyproject.toml)
|
# via
|
||||||
|
# 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 rich
|
# via
|
||||||
|
# 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
|
||||||
|
|
@ -53,10 +148,24 @@ 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 carousel (pyproject.toml:dev)
|
# via
|
||||||
|
# carousel (pyproject.toml:dev)
|
||||||
|
# marimo
|
||||||
setuptools==78.1.0
|
setuptools==78.1.0
|
||||||
# via
|
# via
|
||||||
# pyinstaller
|
# pyinstaller
|
||||||
|
|
@ -66,7 +175,39 @@ 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 pyright
|
# via
|
||||||
|
# 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,126 +24,7 @@ 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(applicant_rankings, reviewer_rankings):
|
def brute_match(applicant_rankings, reviewer_rankings):
|
||||||
"""Brute force search for stable matches."""
|
"""Brute force combinatoric search for stable matches."""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,126 @@
|
||||||
def deferred_acceptance(applicant_rankings, reviewer_rankings):
|
from .util import *
|
||||||
"""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"}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
152
src/carousel/util.py
Normal file
152
src/carousel/util.py
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
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_valid_pref(pp) is False
|
assert crsl.check_pref_allunique(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_valid_pref(rr) is False
|
assert crsl.check_pref_allunique(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_valid_rank(R)
|
assert crsl.check_rank_noties(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_valid_pref(P)
|
assert crsl.check_pref_allunique(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_unstable(match, ar, rr)
|
assert crsl.check_match_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_stable(match, ar, rr)
|
assert crsl.check_match_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.deferred_acceptance(applicant_rankings, reviewer_rankings)
|
match = crsl.matchby_deferred_acceptance(applicant_rankings, reviewer_rankings)
|
||||||
assert crsl.check_stable(match, applicant_rankings, reviewer_rankings)
|
assert crsl.check_match_stable(match, applicant_rankings, reviewer_rankings)
|
||||||
|
|
|
||||||
124
test/nbs/__marimo__/session/hovses.py.json
Normal file
124
test/nbs/__marimo__/session/hovses.py.json
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
{
|
||||||
|
"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,18 +1,22 @@
|
||||||
import marimo
|
import marimo
|
||||||
|
|
||||||
__generated_with = "0.13.6"
|
__generated_with = "0.18.3"
|
||||||
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"""# Caltech Hovse Rotation Example""")
|
mo.md(r"""
|
||||||
|
# Caltech Hovse Rotation Example
|
||||||
|
""")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(mo):
|
def _(mo):
|
||||||
mo.md(r"""## Imports""")
|
mo.md(r"""
|
||||||
|
## Imports
|
||||||
|
""")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22,27 +26,73 @@ 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"""## Generating Rotation Rankings""")
|
mo.md(r"""
|
||||||
|
## Generating Rotation Rankings
|
||||||
|
""")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
@app.cell
|
||||||
def _(crsl, pl):
|
def _(crsl, pl):
|
||||||
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]})
|
ar = pl.DataFrame(
|
||||||
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_stable(m, ar, rr)
|
crsl.check_match_stable(m, ar, rr)
|
||||||
|
return ar, rr
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _(ar):
|
||||||
|
ar
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@app.cell
|
@app.cell
|
||||||
def _():
|
def _():
|
||||||
hovses = ["Blacker", "Dabney", "Ricketts", "Fleming", "Page", "Lloyd", "Venerable", "Avery"]
|
hovses = [
|
||||||
|
"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