Skip to content

Commit

Permalink
StableMarriage rank internals (#175)
Browse files Browse the repository at this point in the history
* Write init test for new single matching

* Move to ruff over black for formatting

* Remove limit on parallel CI

* Write test for SingleMatching repr dunder method

* Check dictionary is in single matching repr

* Write test for equivalence dunder

* Write test for single matching inverter

* Remove old SingleMatching tests

* Implement SingleMatching class

* Move matching classes to subpackage

* Write test for instantiating SM classes

* Fix ruff config and add pre-commit

* Write tests for utility builder

* Write test for preference list builder (slow!)

* Reduce test size for preference builder

* Implement builders for SM

* Write input validator tests

* Implement input validator

* Ensure higher utility leads to higher ranking

* Reintegrate tests into modular structure

* Translate SM algorithm test to rankings

* Write test for player set attribute inverter

* Move rank matrices to class attributes

* Write tests for solver

* Implement solver and algorithm

* Rewrite preference converter test

* Write test for converting incomplete preferences

* Reimplement preference converter

* Include preference lookup in attributes

* Write optimality tests

* Rewrite example tests

* Ensure rejections are recorded

* Write test for preference matching converter

* Revert example tests to use preference terms

* Implement a preference matching converter

* Reformat code base and drop deprecated np.infty

* Bump actions and drop support for Python<3.10

* Stop doctesting paper and fix examples in README

* Update all docs that SM touches

* Remove creation from instances how-to

* Remove SM solver test module

* Wrap SR tutorial solution with `dict()` for now

The changes to `SingleMatching` are causing issues downstream.

Still not sure what this will look like for SR in the future, but this
will do for now.

* Make small formatting changes

* Add in manual matching update for SR test

Again, the new `SingleMatching` class is causing issues for SR...

* Remove change to SM discussion

* Stop rejections leading to immediate forget
  • Loading branch information
daffidwilde authored Oct 24, 2024
1 parent fff4bf7 commit ee04279
Show file tree
Hide file tree
Showing 34 changed files with 1,123 additions and 830 deletions.
14 changes: 6 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@ jobs:
run:
shell: bash
strategy:
max-parallel: 10
matrix:
os: [ubuntu-latest, windows-latest]
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- name: Check out repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
Expand All @@ -37,16 +36,15 @@ jobs:
- name: Run tests
run: |
python -m doctest README.md
python -m doctest paper.md
python -m pytest docs --nbval --nbval-current-env -p no:randomly
python -m pytest tests \
--cov=matching --cov-fail-under=100 --hypothesis-profile=ci
- name: Install and run linters (3.11-ubuntu only)
- name: Install and run linters (3.12-ubuntu only)
if: |
matrix.python-version == '3.11' &&
matrix.python-version == '3.12' &&
matrix.os == 'ubuntu-latest'
run: |
python -m pip install ".[lint]"
python -m black --check .
python -m ruff check .
python -m ruff format --check .
8 changes: 4 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ jobs:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Install Python and dependencies
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.9"
python-version: "3.12"
cache: "pip"
- run: |
python -m pip install ".[dev]"
Expand All @@ -28,4 +28,4 @@ jobs:
with:
target: gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-added-large-files
name: Check for files larger than 5 MB
args: [ "--maxkb=5120" ]
- id: end-of-file-fixer
name: Check for a blank line at the end of scripts (auto-fixes)
exclude: '\.Rd'
- id: trailing-whitespace
name: Check for trailing whitespaces (auto-fixes)
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.0
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ keywords:
- matching
- python
- game
license: MIT
license: MIT
46 changes: 14 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,60 +76,42 @@ We can construct these preferences using dictionaries:
Then to solve this matching game, we make use of the `StableMarriage`
class, like so:


```python
>>> from matching.games import StableMarriage
>>> game = StableMarriage.create_from_dictionaries(
>>> game = StableMarriage.from_preferences(
... suitor_preferences, reviewer_preferences
... )
>>> game.solve()
{A: E, B: D, C: F}
>>> matching = game.solve()
>>> dict(matching)
{'F': 'C', 'D': 'B', 'E': 'A'}

```

## The `Matching` object

This matching is not a standard Python dictionary, though it does
The matching itself is not a standard Python dictionary, though it does
largely look and behave like one. It is in fact an instance of the
`SingleMatching` class:

```python
>>> matching = game.matching
>>> type(matching)
<class 'matching.matchings.SingleMatching'>
<class 'matching.matchings.single.SingleMatching'>
>>> isinstance(matching, dict)
True
>>> matching
SingleMatching({'F': 'C', 'D': 'B', 'E': 'A'}, keys="reviewers", values="suitors")

```

This dictionary-like object is primarily useful as a teaching device
that eases the process of manipulating a matching after a solution has
been found.

## `Player` classes

Despite passing dictionaries of strings here, the matching displays
instances of `matching.player.Player`:
This object allows for straightforward manipulation of the underlying
dictionary, for instance by inverting it:

```python
>>> matching = game.matching
>>> for suitor in matching:
... print(type(suitor))
<class 'matching.players.player.Player'>
<class 'matching.players.player.Player'>
<class 'matching.players.player.Player'>
>>> dict(matching.invert())
{'C': 'F', 'B': 'D', 'A': 'E'}

```

This is because `create_from_dictionaries` creates instances of the
appropriate player classes first and passes them to the game class.
Using dictionaries like this can be an efficient way of creating large
games but it does require the names of the players in each party to be
unique.

With all games, Matching uses a `Player` class to represent the members
of the "applying" party, i.e. residents and students. For HR and SA,
there are specific classes to represent the roles of `Hospital`,
`Project` and `Supervisor`.

## A note on performance

One of the limitations of this library is the time complexities of the
Expand Down
6 changes: 3 additions & 3 deletions _quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ website:
contents: docs/discussion
- title: reference
contents: docs/reference


page-footer:
left: >
Expand All @@ -53,15 +53,15 @@ bibliography: docs/assets/bibliography.bib

toc: true

metadata-files:
metadata-files:
- docs/_sidebar.yml

quartodoc:
title: API reference
package: matching
dir: docs/reference
sidebar: docs/_sidebar.yml

sections:
- title: Games
desc: Objects for handling game instances.
Expand Down
6 changes: 3 additions & 3 deletions docs/discussion/stable_marriage.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ efficient, robust extension of the original algorithm, taken from
@GI89, is given below.

0. Assign all suitors and reviewers to be unmatched.
1. Take any suitor $s$ that is not currently matched, and consider
their favourite reviewer $r$.
1. Take any suitor $s$ that is not currently matched but has a non-empty
preference list, and consider their favourite reviewer $r$.
2. If $r$ is matched, get their current match $s' = M^{-1}(r)$ and
unmatch the pair.
unmatch them.
3. Match $s$ and $r$, i.e. set $M(s) = r$.
4. For each successor, $t$, to $s$ in $g(r)$, delete the pair $(t, r)$
from the game by removing $r$ from $f(t)$ and $t$ from $g(r)$.
Expand Down
14 changes: 7 additions & 7 deletions docs/how-to/choose_optimality.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
{
"data": {
"text/plain": [
"{A: X, B: Z, C: Y}"
"SingleMatching({'Y': 'C', 'Z': 'B', 'X': 'A'}, keys=\"reviewers\", values=\"suitors\")"
]
},
"execution_count": 2,
Expand All @@ -56,7 +56,7 @@
}
],
"source": [
"game = StableMarriage.create_from_dictionaries(\n",
"game = StableMarriage.from_preferences(\n",
" suitor_preferences, reviewer_preferences\n",
")\n",
"\n",
Expand All @@ -71,7 +71,7 @@
{
"data": {
"text/plain": [
"{A: Y, B: Z, C: X}"
"SingleMatching({'Y': 'A', 'Z': 'B', 'X': 'C'}, keys=\"reviewers\", values=\"suitors\")"
]
},
"execution_count": 3,
Expand All @@ -80,7 +80,7 @@
}
],
"source": [
"game = StableMarriage.create_from_dictionaries(\n",
"game = StableMarriage.from_preferences(\n",
" suitor_preferences, reviewer_preferences\n",
")\n",
"\n",
Expand All @@ -90,9 +90,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "matching-docs",
"display_name": "matching",
"language": "python",
"name": "matching-docs"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand All @@ -104,7 +104,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.16"
"version": "3.11.10"
}
},
"nbformat": 4,
Expand Down
90 changes: 0 additions & 90 deletions docs/how-to/create_from_instances.ipynb

This file was deleted.

15 changes: 4 additions & 11 deletions docs/tutorials/hospital_resident.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -225,13 +225,6 @@
"unmatched_residents"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
Expand All @@ -245,9 +238,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "matching-docs",
"display_name": "matching",
"language": "python",
"name": "matching-docs"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand All @@ -259,7 +252,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.16"
"version": "3.11.10"
}
},
"nbformat": 4,
Expand Down
Loading

0 comments on commit ee04279

Please sign in to comment.