Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,34 @@ 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", "3.13"]

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

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
- name: Install uv and set the python version
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"

- name: Update pip and install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install ".[docs,test]"
- name: Install the project
run: uv sync --all-extras

- 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 \
uv run python -m doctest README.md
uv run python -m doctest paper.md
uv run pytest docs --nbval --nbval-current-env -p no:randomly
uv run pytest tests \
--cov=matching --cov-fail-under=100 --hypothesis-profile=ci

- name: Install and run linters (3.11-ubuntu only)
- name: Run linters (3.13-ubuntu only)
if: |
matrix.python-version == '3.11' &&
matrix.python-version == '3.13' &&
matrix.os == 'ubuntu-latest'
run: |
python -m pip install ".[lint]"
python -m black --check .
python -m ruff check .
uv run ruff format --check .
uv run ruff check .
24 changes: 15 additions & 9 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,25 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v3

- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Install Python and dependencies
uses: actions/setup-python@v4
with:
python-version: "3.9"
cache: "pip"
- run: |
python -m pip install ".[dev]"
quartodoc build

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Set up Python
run: |
uv python install

- name: Install package and build docs
run: |
uv sync --all-extras
uv run quartodoc build

- name: Render and publish
uses: quarto-dev/quarto-actions/publish@v2
with:
target: gh-pages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 1 addition & 3 deletions docs/how-to/check_matching_status.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@
"\n",
"project_supervisors = {\"X1\": \"X\", \"X2\": \"X\", \"Y1\": \"Y\", \"Y2\": \"Y\"}\n",
"project_capacities = {project: 1 for project in project_supervisors}\n",
"supervisor_capacities = {\n",
" supervisor: 2 for supervisor in supervisor_preferences\n",
"}\n",
"supervisor_capacities = {supervisor: 2 for supervisor in supervisor_preferences}\n",
"\n",
"\n",
"game = StudentAllocation.create_from_dictionaries(\n",
Expand Down
8 changes: 2 additions & 6 deletions docs/how-to/choose_optimality.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@
}
],
"source": [
"game = StableMarriage.create_from_dictionaries(\n",
" suitor_preferences, reviewer_preferences\n",
")\n",
"game = StableMarriage.create_from_dictionaries(suitor_preferences, reviewer_preferences)\n",
"\n",
"game.solve(optimal=\"suitor\")"
]
Expand All @@ -80,9 +78,7 @@
}
],
"source": [
"game = StableMarriage.create_from_dictionaries(\n",
" suitor_preferences, reviewer_preferences\n",
")\n",
"game = StableMarriage.create_from_dictionaries(suitor_preferences, reviewer_preferences)\n",
"\n",
"game.solve(optimal=\"reviewer\")"
]
Expand Down
4 changes: 1 addition & 3 deletions docs/how-to/create_from_dictionaries.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@
"\n",
"project_supervisors = {\"X1\": \"X\", \"X2\": \"X\", \"Y1\": \"Y\", \"Y2\": \"Y\"}\n",
"project_capacities = {project: 1 for project in project_supervisors}\n",
"supervisor_capacities = {\n",
" supervisor: 2 for supervisor in supervisor_preferences\n",
"}"
"supervisor_capacities = {supervisor: 2 for supervisor in supervisor_preferences}"
]
},
{
Expand Down
4 changes: 1 addition & 3 deletions docs/tutorials/hospital_resident.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,7 @@
" for resident in residents:\n",
" matched_residents.append(resident.name)\n",
"\n",
"unmatched_residents = set(resident_preferences.keys()).difference(\n",
" matched_residents\n",
")\n",
"unmatched_residents = set(resident_preferences.keys()).difference(matched_residents)\n",
"unmatched_residents"
]
},
Expand Down
74 changes: 22 additions & 52 deletions docs/tutorials/student_allocation.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,9 @@
"metadata": {},
"outputs": [],
"source": [
"raw_students = pd.read_csv(\n",
" \"https://zenodo.org/record/3514287/files/students.csv\"\n",
")\n",
"raw_projects = pd.read_csv(\n",
" \"https://zenodo.org/record/3514287/files/projects.csv\"\n",
")\n",
"raw_supervisors = pd.read_csv(\n",
" \"https://zenodo.org/record/3514287/files/supervisors.csv\"\n",
")"
"raw_students = pd.read_csv(\"https://zenodo.org/record/3514287/files/students.csv\")\n",
"raw_projects = pd.read_csv(\"https://zenodo.org/record/3514287/files/projects.csv\")\n",
"raw_supervisors = pd.read_csv(\"https://zenodo.org/record/3514287/files/supervisors.csv\")"
]
},
{
Expand Down Expand Up @@ -360,11 +354,7 @@
}
],
"source": [
"students = (\n",
" raw_students.copy()\n",
" .dropna(subset=choices, how=\"all\")\n",
" .reset_index(drop=True)\n",
")\n",
"students = raw_students.copy().dropna(subset=choices, how=\"all\").reset_index(drop=True)\n",
"\n",
"students.head()"
]
Expand Down Expand Up @@ -778,9 +768,7 @@
"supervisor_to_preferences = {}\n",
"for supervisor in supervisor_names:\n",
" supervisor_preferences = []\n",
" supervisor_projects = [\n",
" p for p, s in project_to_supervisor.items() if s == supervisor\n",
" ]\n",
" supervisor_projects = [p for p, s in project_to_supervisor.items() if s == supervisor]\n",
"\n",
" for student in sorted_students:\n",
" student_preferences = student_to_preferences[student]\n",
Expand Down Expand Up @@ -823,9 +811,7 @@
}
],
"source": [
"unranked_supervisors = set(supervisor_names).difference(\n",
" supervisor_to_preferences.keys()\n",
")\n",
"unranked_supervisors = set(supervisor_names).difference(supervisor_to_preferences.keys())\n",
"\n",
"\n",
"unranked_projects = set(project_codes).difference(\n",
Expand Down Expand Up @@ -934,9 +920,7 @@
],
"source": [
"for supervisor, supervisor_capacity in supervisor_to_capacity.items():\n",
" supervisor_projects = [\n",
" p for p, s in project_to_supervisor.items() if s == supervisor\n",
" ]\n",
" supervisor_projects = [p for p, s in project_to_supervisor.items() if s == supervisor]\n",
" supervisor_project_capacities = [\n",
" project_to_capacity[project] for project in supervisor_projects\n",
" ]\n",
Expand Down Expand Up @@ -1052,13 +1036,11 @@
"outputs": [],
"source": [
"supervisor_free_spaces = {\n",
" supervisor: supervisor.capacity - len(supervisor.matching)\n",
" for supervisor in game.supervisors\n",
" supervisor: supervisor.capacity - len(supervisor.matching) for supervisor in game.supervisors\n",
"}\n",
"\n",
"supervisor_utilisation = {\n",
" supervisor: len(supervisor.matching) / supervisor.capacity\n",
" for supervisor in game.supervisors\n",
" supervisor: len(supervisor.matching) / supervisor.capacity for supervisor in game.supervisors\n",
"}"
]
},
Expand Down Expand Up @@ -1142,9 +1124,7 @@
"ax.hist(values)\n",
"\n",
"ylims = ax.get_ylim()\n",
"ax.vlines(\n",
" np.mean(list(values)), *ylims, \"tab:orange\", \"dashed\", label=\"Mean\", lw=3\n",
")\n",
"ax.vlines(np.mean(list(values)), *ylims, \"tab:orange\", \"dashed\", label=\"Mean\", lw=3)\n",
"ax.set_ylim(*ylims)\n",
"\n",
"ax.set_xlabel(\"Utilisation\")\n",
Expand All @@ -1171,13 +1151,11 @@
"outputs": [],
"source": [
"project_free_spaces = {\n",
" project.name: project.capacity - len(project.matching)\n",
" for project in game.projects\n",
" project.name: project.capacity - len(project.matching) for project in game.projects\n",
"}\n",
"\n",
"project_utilisation = {\n",
" project.name: len(project.matching) / project.capacity\n",
" for project in game.projects\n",
" project.name: len(project.matching) / project.capacity for project in game.projects\n",
"}"
]
},
Expand Down Expand Up @@ -1260,9 +1238,7 @@
"ax.hist(values)\n",
"\n",
"ylims = ax.get_ylim()\n",
"ax.vlines(\n",
" np.mean(list(values)), *ylims, \"tab:orange\", \"dashed\", label=\"Mean\", lw=3\n",
")\n",
"ax.vlines(np.mean(list(values)), *ylims, \"tab:orange\", \"dashed\", label=\"Mean\", lw=3)\n",
"ax.set_ylim(*ylims)\n",
"\n",
"ax.set_xlabel(\"Utilisation\")\n",
Expand Down Expand Up @@ -1298,9 +1274,7 @@
"for project, project_students in matching.items():\n",
" for student in project_students:\n",
" inverted_matching[student.name] = project.name\n",
" student_preference_of_matching.append(\n",
" student._pref_names.index(project.name)\n",
" )"
" student_preference_of_matching.append(student._pref_names.index(project.name))"
]
},
{
Expand Down Expand Up @@ -1692,9 +1666,7 @@
"outputs": [],
"source": [
"project_with_space_names = [\n",
" project.name\n",
" for project in game.projects\n",
" if len(project.matching) < project.capacity\n",
" project.name for project in game.projects if len(project.matching) < project.capacity\n",
"] + list(unranked_projects)\n",
"\n",
"supervisor_with_space_names = [\n",
Expand Down Expand Up @@ -1745,17 +1717,17 @@
"metadata": {},
"outputs": [],
"source": [
"projects_with_space[\"supervisor_capacity\"] = projects_with_space[\n",
" \"supervisor\"\n",
"].apply(lambda x: get_capacity(supervisors, \"supervisor\", x))\n",
"projects_with_space[\"supervisor_capacity\"] = projects_with_space[\"supervisor\"].apply(\n",
" lambda x: get_capacity(supervisors, \"supervisor\", x)\n",
")\n",
"\n",
"projects_with_space[\"project_matches\"] = projects_with_space[\"code\"].apply(\n",
" lambda x: get_number_of_matches(x, \"projects\", game)\n",
")\n",
"\n",
"projects_with_space[\"supervisor_matches\"] = projects_with_space[\n",
" \"supervisor\"\n",
"].apply(lambda x: get_number_of_matches(x, \"supervisors\", game))\n",
"projects_with_space[\"supervisor_matches\"] = projects_with_space[\"supervisor\"].apply(\n",
" lambda x: get_number_of_matches(x, \"supervisors\", game)\n",
")\n",
"\n",
"projects_with_space = projects_with_space[\n",
" [\n",
Expand Down Expand Up @@ -1873,9 +1845,7 @@
}
],
"source": [
"projects_with_space = projects_with_space.set_index(\n",
" [\"supervisor\", \"code\"]\n",
").sort_index()\n",
"projects_with_space = projects_with_space.set_index([\"supervisor\", \"code\"]).sort_index()\n",
"\n",
"projects_with_space"
]
Expand Down
34 changes: 14 additions & 20 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
[build-system]
requires = ["setuptools>=62"]
build-backend = "setuptools.build_meta"

[project]
name = "matching"
authors = [
Expand All @@ -11,7 +7,7 @@ authors = [
description = "A package for solving matching games"
readme = "README.md"
requires-python = ">=3.7"
license = {text = "MIT License"}
license = {text = "MIT Licence"}
keywords = [
"game-theory",
"gale-shapley",
Expand All @@ -33,6 +29,10 @@ dependencies = [
]
dynamic = ["version"]

[build-system]
requires = ["setuptools>=62"]
build-backend = "setuptools.build_meta"

[project.optional-dependencies]
test = [
"hypothesis>=6.31.6",
Expand All @@ -49,12 +49,10 @@ docs = [
"PyYAML>=6",
"quartodoc>=0.5.0; python_version>'3.8'",
]
lint = [
"black[jupyter]>=22.6.0,<23",
"ruff>=0.1.1",
]
dev = [
"matching[docs,lint,test]",
"matching[docs,test]",
"pytest-sugar>=1.0.0",
"ruff>=0.11.1",
]

[project.urls]
Expand All @@ -65,26 +63,22 @@ changelog = "https://github.com/daffidwilde/matching/blob/main/CHANGES.md"
[tool.setuptools.dynamic]
version = {attr = "matching.__version__"}

[tool.black]
line-length = 79

[tool.coverage.report]
omit = ["src/**/__init__.py"]

[tool.ruff]
line-length = 99
extend-include = ["*.ipynb"]

[tool.ruff.lint]
extend-select = ["D", "I", "W"]
ignore = ["D105", "D107", "D202"]
line-length = 79

[tool.ruff.isort]
[tool.ruff.lint.isort]
known-first-party = ["matching"]

[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"docs/*" = ["D100", "D103", "E402"]
"tests/**/*.py" = ["D104", "D401", "E741"]
"src/**/*.py" = ["D105", "D107"]
"src/matching/base.py" = ["D401"]

[tool.ruff.pydocstyle]
[tool.ruff.lint.pydocstyle]
convention = "numpy"
Loading