preference-grouping-solver
Assigns students to capacity-limited groups so that the sum of their preference ranks is minimal, solved exactly as a mixed-integer linear program with SciPy/HiGHS.
Installation
uv sync # runtime
uv sync --all-extras # + faker for generating synthetic data
Input
Each entry ranks groups from most to least preferred:
{
"student_id": 1000000,
"group_order": [2, 1, 6, 14, 3, 8, 4, 7, 9, 5]
}
group_ordermay be partial; every unranked group receives the worst rank (groups).- Groups must be unique and within
1..groups. - Input is either a directory of
*.jsonfiles (one entry or a list of entries per file) or a single JSON file containing a list of entries.
Invalid entries are skipped and reported with their location (file or file[row]) and kind:
| kind | cause |
|---|---|
parse |
unreadable file or invalid JSON |
schema |
missing/invalid field, repeated or unknown group |
duplicate |
student_id already loaded from an earlier entry |
Configuration
| field | default | description |
|---|---|---|
input |
required | directory or JSON file |
output |
group_assignments.csv |
CSV export (student,group,rank) |
groups |
16 |
number of groups, numbered 1..groups |
max_group_size |
9 |
maximum students per group |
min_group_size |
0 |
minimum students per group |
rank_exponent |
1.0 |
cost is rank ** exponent; > 1 avoids very bad placements |
Usage
CLI
preference-grouping-solver --input preferences/ --groups 16 --max-group-size 9
preference-grouping-solver --config examples/config.json --rank-exponent 1
python -m preference_grouping_solver --help
Values from --config (JSON, paths relative to the working directory) are overridden by CLI flags.
The exit code is 0 on success, 1 if no assignment exists and 2 for invalid arguments.
Python
from preference_grouping_solver import Solver, SolverConfig, load
config = SolverConfig(groups=8, max_group_size=9)
loaded = load("examples/preferences.json", groups=config.groups)
solution = Solver(config).solve(loaded.preferences)
solution.assignments # student -> group, rank
solution.groups # size, mean_rank, first_choices per group
solution.rank_distribution # students per assigned rank
solution.to_csv("group_assignments.csv")
Output example
groups
┏━━━━━━━┳━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ group ┃ size ┃ mean_rank ┃ first_choices ┃
┡━━━━━━━╇━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ 1 │ 9/9 │ 1.00 │ 11 │
│ 2 │ 6/9 │ 1.17 │ 5 │
│ … │ … │ … │ … │
└───────┴──────┴───────────┴───────────────┘
assigned ranks
┏━━━━━━┳━━━━━━━━━━┓
┃ rank ┃ students ┃
┡━━━━━━╇━━━━━━━━━━┩
│ 1 │ 54 │
│ 2 │ 6 │
└──────┴──────────┘
students: 60, mean rank: 1.10
assignments written to examples/group_assignments.csv
Examples
examples/ contains 60 synthetic students for 8 groups, both as a directory and as a combined file:
uv run python examples/generate.py # regenerate the data with faker
uv run python examples/run.py # Python API
uv run preference-grouping-solver --config examples/config.json
Development
uv sync --all-extras
uv run pytest
Tests use faker-generated data and verify optimality against brute force on small instances.
Metadata
Release files for preference-grouping-solver 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| preference_grouping_solver-0.2.0.tar.gz | 65.0 kB | Without provenance | Details for preference_grouping_solver-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| preference_grouping_solver-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for preference_grouping_solver-0.2.0-py3-none-any.whl |
Total release size: 74.7 kB
Release files / preference_grouping_solver-0.2.0.tar.gz
| Download URL | preference_grouping_solver-0.2.0.tar.gz |
|---|---|
| Size | 65.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
25a31040b30e86156e6ba38c46384d02ad9284c7e68d4171dc75f8771ebc89db
|
|
BLAKE2b-256 checksum How to use checksums |
14af71f29b0183ab1488968824774be0c226be717b4fce1327f80b261b1862d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.16
|
Release files / preference_grouping_solver-0.2.0-py3-none-any.whl
| Download URL | preference_grouping_solver-0.2.0-py3-none-any.whl |
|---|---|
| Size | 9.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9ab6ef7044f44604ef6a7f2fb9475a67b20960fe27e1cb62d0afaa85cc266c66
|
|
BLAKE2b-256 checksum How to use checksums |
8448376ae3a65db5fe91a419a5e26cd35be39da9f85da95e58772ccfddca373c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.6.16
|
Security for preference-grouping-solver 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.