ortidy 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ortidy-0.2.0/.claudeignore +21 -0
- ortidy-0.2.0/.gitattributes +3 -0
- ortidy-0.2.0/.github/dependabot.yml +20 -0
- ortidy-0.2.0/.github/workflows/ci.yml +36 -0
- ortidy-0.2.0/.github/workflows/docs.yml +30 -0
- ortidy-0.2.0/.github/workflows/publish.yml +35 -0
- ortidy-0.2.0/.gitignore +146 -0
- ortidy-0.2.0/.pre-commit-config.yaml +19 -0
- ortidy-0.2.0/.python-version +1 -0
- ortidy-0.2.0/CHANGELOG.md +71 -0
- ortidy-0.2.0/CITATION.cff +10 -0
- ortidy-0.2.0/CLAUDE.md +157 -0
- ortidy-0.2.0/CONTRIBUTING.md +55 -0
- ortidy-0.2.0/LICENSE.md +201 -0
- ortidy-0.2.0/PKG-INFO +126 -0
- ortidy-0.2.0/README.md +106 -0
- ortidy-0.2.0/RELEASING.md +56 -0
- ortidy-0.2.0/SECURITY.md +22 -0
- ortidy-0.2.0/binder/requirements.txt +6 -0
- ortidy-0.2.0/docs/api.md +39 -0
- ortidy-0.2.0/docs/index.md +49 -0
- ortidy-0.2.0/docs/result-shapes.md +55 -0
- ortidy-0.2.0/docs/solvers.md +30 -0
- ortidy-0.2.0/examples/binning.ipynb +154 -0
- ortidy-0.2.0/examples/travelling_salesperson.ipynb +110 -0
- ortidy-0.2.0/examples/vehicle_routing.ipynb +121 -0
- ortidy-0.2.0/mkdocs.yml +45 -0
- ortidy-0.2.0/ortidy/__init__.py +41 -0
- ortidy-0.2.0/ortidy/_narwhals.py +62 -0
- ortidy-0.2.0/ortidy/_scaling.py +58 -0
- ortidy-0.2.0/ortidy/assignment/__init__.py +5 -0
- ortidy-0.2.0/ortidy/assignment/assignment.py +101 -0
- ortidy-0.2.0/ortidy/binning/__init__.py +7 -0
- ortidy-0.2.0/ortidy/binning/bin_packing.py +122 -0
- ortidy-0.2.0/ortidy/binning/knapsack.py +80 -0
- ortidy-0.2.0/ortidy/binning/multi_knapsack.py +127 -0
- ortidy-0.2.0/ortidy/data/__init__.py +61 -0
- ortidy-0.2.0/ortidy/data/binning/bins.csv +6 -0
- ortidy-0.2.0/ortidy/data/binning/items_bin_packing.csv +12 -0
- ortidy-0.2.0/ortidy/data/binning/items_knapsack.csv +51 -0
- ortidy-0.2.0/ortidy/data/binning/items_multi.csv +16 -0
- ortidy-0.2.0/ortidy/data/routing/locations.csv +18 -0
- ortidy-0.2.0/ortidy/data/routing/pickups_and_deliveries.csv +9 -0
- ortidy-0.2.0/ortidy/data/routing/vehicles.csv +5 -0
- ortidy-0.2.0/ortidy/facility/__init__.py +5 -0
- ortidy-0.2.0/ortidy/facility/facility_location.py +140 -0
- ortidy-0.2.0/ortidy/flow/__init__.py +5 -0
- ortidy-0.2.0/ortidy/flow/flow.py +198 -0
- ortidy-0.2.0/ortidy/result.py +98 -0
- ortidy-0.2.0/ortidy/routing/__init__.py +6 -0
- ortidy-0.2.0/ortidy/routing/distance.py +92 -0
- ortidy-0.2.0/ortidy/routing/routing.py +318 -0
- ortidy-0.2.0/ortidy/scheduling/__init__.py +5 -0
- ortidy-0.2.0/ortidy/scheduling/shift_scheduling.py +152 -0
- ortidy-0.2.0/ortidy/schema.py +41 -0
- ortidy-0.2.0/pyproject.toml +80 -0
- ortidy-0.2.0/tests/__init__.py +0 -0
- ortidy-0.2.0/tests/conftest.py +29 -0
- ortidy-0.2.0/tests/test_assignment.py +60 -0
- ortidy-0.2.0/tests/test_bin_packing.py +58 -0
- ortidy-0.2.0/tests/test_distance.py +49 -0
- ortidy-0.2.0/tests/test_facility.py +63 -0
- ortidy-0.2.0/tests/test_flow.py +78 -0
- ortidy-0.2.0/tests/test_knapsack.py +69 -0
- ortidy-0.2.0/tests/test_multi_knapsack.py +70 -0
- ortidy-0.2.0/tests/test_result.py +32 -0
- ortidy-0.2.0/tests/test_routing.py +76 -0
- ortidy-0.2.0/tests/test_routing_extensions.py +71 -0
- ortidy-0.2.0/tests/test_scaling.py +28 -0
- ortidy-0.2.0/tests/test_scheduling.py +73 -0
- ortidy-0.2.0/uv.lock +3385 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Files Claude Code should skip when reading/searching the project.
|
|
2
|
+
# Generated, vendored, or noisy artifacts — not useful as context.
|
|
3
|
+
|
|
4
|
+
# Virtual environments
|
|
5
|
+
.venv/
|
|
6
|
+
|
|
7
|
+
# Build artifacts
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
site/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
|
|
13
|
+
# Tooling caches
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.uv_cache/
|
|
18
|
+
__pycache__/
|
|
19
|
+
|
|
20
|
+
# Lockfile (large, machine-generated)
|
|
21
|
+
uv.lock
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
# Python dependencies (uv / pyproject.toml + uv.lock)
|
|
4
|
+
- package-ecosystem: "uv"
|
|
5
|
+
directory: "/"
|
|
6
|
+
schedule:
|
|
7
|
+
interval: "weekly"
|
|
8
|
+
groups:
|
|
9
|
+
python-dependencies:
|
|
10
|
+
patterns: ["*"]
|
|
11
|
+
open-pull-requests-limit: 5
|
|
12
|
+
|
|
13
|
+
# GitHub Actions used in workflows
|
|
14
|
+
- package-ecosystem: "github-actions"
|
|
15
|
+
directory: "/"
|
|
16
|
+
schedule:
|
|
17
|
+
interval: "weekly"
|
|
18
|
+
groups:
|
|
19
|
+
actions:
|
|
20
|
+
patterns: ["*"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, windows-latest]
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v7
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: uv sync
|
|
26
|
+
|
|
27
|
+
- name: Lint
|
|
28
|
+
run: |
|
|
29
|
+
uv run ruff check .
|
|
30
|
+
uv run ruff format --check .
|
|
31
|
+
|
|
32
|
+
- name: Type-check
|
|
33
|
+
run: uv run mypy ortidy
|
|
34
|
+
|
|
35
|
+
- name: Test
|
|
36
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write # mkdocs gh-deploy pushes to the gh-pages branch
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
docs:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v7
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- run: uv sync --group docs
|
|
22
|
+
|
|
23
|
+
# Always validate the docs build (strict catches broken refs / nav).
|
|
24
|
+
- name: Build (strict)
|
|
25
|
+
run: uv run mkdocs build --strict
|
|
26
|
+
|
|
27
|
+
# Publish to GitHub Pages only from main.
|
|
28
|
+
- name: Deploy to GitHub Pages
|
|
29
|
+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
30
|
+
run: uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Build and publish ortidy to PyPI when a GitHub Release is published.
|
|
4
|
+
# Uses PyPI Trusted Publishing (OIDC) — no API token is stored in the repo.
|
|
5
|
+
#
|
|
6
|
+
# One-time PyPI setup (see RELEASING.md):
|
|
7
|
+
# https://pypi.org/manage/account/publishing/ → add a "pending publisher":
|
|
8
|
+
# PyPI project: ortidy
|
|
9
|
+
# Owner: lucasjamar Repository: ortidy
|
|
10
|
+
# Workflow: publish.yml Environment: pypi
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
release:
|
|
14
|
+
types: [published]
|
|
15
|
+
workflow_dispatch: # allow a manual run from the Actions tab
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
publish:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
environment: pypi
|
|
21
|
+
permissions:
|
|
22
|
+
id-token: write # required for Trusted Publishing
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v7
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
|
|
31
|
+
- name: Build sdist + wheel
|
|
32
|
+
run: uv build
|
|
33
|
+
|
|
34
|
+
- name: Publish to PyPI (Trusted Publishing)
|
|
35
|
+
run: uv publish --trusted-publishing always
|
ortidy-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
##########################
|
|
2
|
+
# Common files
|
|
3
|
+
|
|
4
|
+
# IntelliJ
|
|
5
|
+
.idea/
|
|
6
|
+
*.iml
|
|
7
|
+
out/
|
|
8
|
+
.idea_modules/
|
|
9
|
+
|
|
10
|
+
### macOS
|
|
11
|
+
*.DS_Store
|
|
12
|
+
.AppleDouble
|
|
13
|
+
.LSOverride
|
|
14
|
+
.Trashes
|
|
15
|
+
|
|
16
|
+
# Vim
|
|
17
|
+
*~
|
|
18
|
+
.*.swo
|
|
19
|
+
.*.swp
|
|
20
|
+
|
|
21
|
+
# emacs
|
|
22
|
+
*~
|
|
23
|
+
\#*\#
|
|
24
|
+
/.emacs.desktop
|
|
25
|
+
/.emacs.desktop.lock
|
|
26
|
+
*.elc
|
|
27
|
+
|
|
28
|
+
# JIRA plugin
|
|
29
|
+
atlassian-ide-plugin.xml
|
|
30
|
+
|
|
31
|
+
# C extensions
|
|
32
|
+
*.so
|
|
33
|
+
|
|
34
|
+
### Python template
|
|
35
|
+
# Byte-compiled / optimized / DLL files
|
|
36
|
+
__pycache__/
|
|
37
|
+
*.py[cod]
|
|
38
|
+
*$py.class
|
|
39
|
+
|
|
40
|
+
# Distribution / packaging
|
|
41
|
+
.Python
|
|
42
|
+
build/
|
|
43
|
+
develop-eggs/
|
|
44
|
+
dist/
|
|
45
|
+
downloads/
|
|
46
|
+
eggs/
|
|
47
|
+
.eggs/
|
|
48
|
+
lib/
|
|
49
|
+
lib64/
|
|
50
|
+
parts/
|
|
51
|
+
sdist/
|
|
52
|
+
var/
|
|
53
|
+
wheels/
|
|
54
|
+
*.egg-info/
|
|
55
|
+
.installed.cfg
|
|
56
|
+
*.egg
|
|
57
|
+
MANIFEST
|
|
58
|
+
|
|
59
|
+
# PyInstaller
|
|
60
|
+
# Usually these files are written by a python script from a template
|
|
61
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
62
|
+
*.manifest
|
|
63
|
+
*.spec
|
|
64
|
+
|
|
65
|
+
# Installer logs
|
|
66
|
+
pip-log.txt
|
|
67
|
+
pip-delete-this-directory.txt
|
|
68
|
+
|
|
69
|
+
# Unit test / coverage reports
|
|
70
|
+
htmlcov/
|
|
71
|
+
.tox/
|
|
72
|
+
.coverage
|
|
73
|
+
.coverage.*
|
|
74
|
+
.cache
|
|
75
|
+
nosetests.xml
|
|
76
|
+
coverage.xml
|
|
77
|
+
*.cover
|
|
78
|
+
.hypothesis/
|
|
79
|
+
|
|
80
|
+
# Translations
|
|
81
|
+
*.mo
|
|
82
|
+
*.pot
|
|
83
|
+
|
|
84
|
+
# Django stuff:
|
|
85
|
+
*.log
|
|
86
|
+
.static_storage/
|
|
87
|
+
.media/
|
|
88
|
+
local_settings.py
|
|
89
|
+
|
|
90
|
+
# Flask stuff:
|
|
91
|
+
instance/
|
|
92
|
+
.webassets-cache
|
|
93
|
+
|
|
94
|
+
# Scrapy stuff:
|
|
95
|
+
.scrapy
|
|
96
|
+
|
|
97
|
+
# Sphinx documentation
|
|
98
|
+
docs/_build/
|
|
99
|
+
|
|
100
|
+
# PyBuilder
|
|
101
|
+
target/
|
|
102
|
+
|
|
103
|
+
# Jupyter Notebook
|
|
104
|
+
.ipynb_checkpoints
|
|
105
|
+
|
|
106
|
+
# IPython
|
|
107
|
+
.ipython/profile_default/history.sqlite
|
|
108
|
+
.ipython/profile_default/startup/README
|
|
109
|
+
|
|
110
|
+
# pyenv
|
|
111
|
+
.python-version
|
|
112
|
+
|
|
113
|
+
# celery beat schedule file
|
|
114
|
+
celerybeat-schedule
|
|
115
|
+
|
|
116
|
+
# SageMath parsed files
|
|
117
|
+
*.sage.py
|
|
118
|
+
|
|
119
|
+
# Environments
|
|
120
|
+
.env
|
|
121
|
+
.venv
|
|
122
|
+
env/
|
|
123
|
+
venv/
|
|
124
|
+
ENV/
|
|
125
|
+
env.bak/
|
|
126
|
+
venv.bak/
|
|
127
|
+
|
|
128
|
+
# mkdocs documentation
|
|
129
|
+
/site
|
|
130
|
+
|
|
131
|
+
# mypy
|
|
132
|
+
.mypy_cache/
|
|
133
|
+
dist/
|
|
134
|
+
site/
|
|
135
|
+
|
|
136
|
+
# uv selects the dev interpreter from this file — keep it tracked
|
|
137
|
+
!.python-version
|
|
138
|
+
|
|
139
|
+
##########################
|
|
140
|
+
# ortidy / tooling
|
|
141
|
+
.pytest_cache/
|
|
142
|
+
.ruff_cache/
|
|
143
|
+
.mypy_cache/
|
|
144
|
+
.uv_cache/
|
|
145
|
+
site/
|
|
146
|
+
dist/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.6.9
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/kynan/nbstripout
|
|
10
|
+
rev: 0.7.1
|
|
11
|
+
hooks:
|
|
12
|
+
- id: nbstripout
|
|
13
|
+
|
|
14
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
15
|
+
rev: v1.11.2
|
|
16
|
+
hooks:
|
|
17
|
+
- id: mypy
|
|
18
|
+
additional_dependencies: [narwhals]
|
|
19
|
+
files: ^ortidy/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
|
|
5
|
+
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2026-06-26 - Renamed to `ortidy`, rebuilt on Narwhals
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
* **Assignment & facility location** (assignment-matrix shape): `assignment`
|
|
11
|
+
(linear sum assignment) and `facility_location` (uncapacitated).
|
|
12
|
+
* **Network flow** (edge-flow shape): `max_flow`, `min_cost_flow`, `shortest_path`.
|
|
13
|
+
* **Shift scheduling** (`shift_scheduling`) — the interval-schedule result shape.
|
|
14
|
+
* **Routing promises**: VRPTW (time windows) and pickups & deliveries in
|
|
15
|
+
`solve_routing`, plus a `distance_matrix` helper (euclidean / haversine) so
|
|
16
|
+
routing can take locations instead of a precomputed matrix.
|
|
17
|
+
* mkdocs-material documentation.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
* **Renamed `pandas-or` → `ortidy`** ("OR on tidy dataframes"). The importable
|
|
21
|
+
module is now `ortidy`; the old `pandas-or` 0.1.3 on PyPI is left unmaintained.
|
|
22
|
+
* **Backend-agnostic** via Narwhals: pandas in → pandas out, Polars in → Polars
|
|
23
|
+
out. pandas remains the reference backend.
|
|
24
|
+
* Every solver returns a structured **`SolveResult`** (frame, status, objective,
|
|
25
|
+
metadata) instead of a bare frame / `None`. A `FEASIBLE` solution is a success.
|
|
26
|
+
* Rows are identified by **explicit id columns**, never a positional index.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
* `knapsack`: migrated to the modern `ortools.algorithms.python.knapsack_solver`
|
|
30
|
+
import and honest return type; added float→int unit-scaling.
|
|
31
|
+
* `multi_knapsack` / `bin_packing`: rebuilt on CP-SAT, removing the per-row
|
|
32
|
+
`apply(..BoolVar.., axis=1)` variable-construction anti-pattern.
|
|
33
|
+
* `solve_routing`: removed the phantom final route row and the dead
|
|
34
|
+
`"capacity" in column_names` branch; promoted the hardcoded `3000` / `100` /
|
|
35
|
+
`1s` magic numbers to parameters.
|
|
36
|
+
* Replaced the always-failing `__version__ == "0.1.0"` test with a real suite
|
|
37
|
+
(correctness, golden-file, infeasible-status, backend-parity).
|
|
38
|
+
|
|
39
|
+
### Tooling
|
|
40
|
+
* Python floor raised to 3.10; **uv** for dependency management (PEP 735
|
|
41
|
+
`[dependency-groups]`) with **hatchling** as the build backend; `ruff`, `mypy`,
|
|
42
|
+
`pre-commit`, and GitHub Actions CI (test matrix + strict docs build).
|
|
43
|
+
* Depend on `ortools>=9.12` (current 9.15), supporting Python 3.10–3.14.
|
|
44
|
+
* Stripped ~8 MB of embedded notebook output.
|
|
45
|
+
|
|
46
|
+
## [0.1.3] - 2022-08-07
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
* Replaced `operations research` with `optimization research` in docs.
|
|
50
|
+
|
|
51
|
+
## [0.1.2] - 2022-08-07
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
* Fixed multi_knapsack to assign each item to only one bin.
|
|
55
|
+
* Added separate dataset for multi_knapsack and bin_packing compared to knapsack.
|
|
56
|
+
|
|
57
|
+
### Additions
|
|
58
|
+
* Added docstrings.
|
|
59
|
+
* Added CITATION.cff
|
|
60
|
+
|
|
61
|
+
## [0.1.1] - 2022-08-04
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
* Added binder requirements.
|
|
65
|
+
* Filtering of bin_packing with resetting of binId.
|
|
66
|
+
|
|
67
|
+
## [0.1.0] - 2022-07-31 - Initial release
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
* Functions for solving knapsack type problems.
|
|
71
|
+
* Basic vehicle routing solving functions.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: ortidy
|
|
3
|
+
version: 0.2.0
|
|
4
|
+
date-released: 2026-06-26
|
|
5
|
+
url: https://github.com/lucasjamar/ortidy
|
|
6
|
+
message: 'If you use this software, please cite it as below.'
|
|
7
|
+
type: software
|
|
8
|
+
authors:
|
|
9
|
+
- given-names: Lucas
|
|
10
|
+
family-names: Jamar
|
ortidy-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# CLAUDE.md — ortidy
|
|
2
|
+
|
|
3
|
+
Guidance for Claude Code working in this repository. Read this fully before making changes.
|
|
4
|
+
|
|
5
|
+
> **Naming note:** this project is the revival of `pandas-or` (PyPI, last release `0.1.3`, dormant ~4 years), renamed to **`ortidy`** to reflect its new backend-agnostic design. "ortidy" = **OR** (operations research) on **tidy** dataframes. See *Renaming & migration* below for the mechanics.
|
|
6
|
+
|
|
7
|
+
## What this project is
|
|
8
|
+
|
|
9
|
+
`ortidy` is a dataframe-native façade over [Google OR-Tools](https://developers.google.com/optimization). The thesis: bridge operations research and the data ecosystem so that solver outputs come back as tidy dataframes ready for analysis, plotting, and dashboards.
|
|
10
|
+
|
|
11
|
+
**Core principle to internalize:** the dataframe is *glue*, not the compute engine. All heavy lifting happens inside OR-Tools (C++). Our code does input validation, light reshaping (joins, group-bys), the solver handoff, and result assembly. Do not optimize dataframe operations for speed — the bottleneck is always the solve. Optimize for *clarity, correctness, and a consistent API*.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Hard decisions already made (do not relitigate)
|
|
16
|
+
|
|
17
|
+
1. **Dataframe backend: Narwhals, not raw pandas or Polars.**
|
|
18
|
+
The library must be backend-agnostic. Use [Narwhals](https://narwhals-dev.github.io/narwhals/) as the internal layer so a user who passes pandas gets pandas back, a user who passes Polars gets Polars back. pandas remains the *default/reference* backend. Polars, pyarrow, etc. come for free.
|
|
19
|
+
- Import the stable namespace: `import narwhals.stable.v1 as nw`. This carries a backward-compatibility guarantee and shields us from upstream pandas/Polars churn — the exact rot that killed the original.
|
|
20
|
+
- Accept native frames at the public boundary, convert with `nw.from_native(...)`, do internal work in Narwhals expressions, return with `.to_native()`. The `@nw.narwhalify` decorator is acceptable for simple functions.
|
|
21
|
+
- At the **solver boundary**, extract plain Python lists/ints/floats from columns. OR-Tools does not consume dataframes; this handoff is backend-neutral by design.
|
|
22
|
+
|
|
23
|
+
2. **No implicit pandas index.** Narwhals follows the Polars index-free model. Identify rows with explicit ID columns (`itemId`, `binId`, `vehicleId`, `locationId`, …), never with a positional index. This is also required by the return contract below.
|
|
24
|
+
|
|
25
|
+
3. **Name: `ortidy`** (renamed from `pandas-or`). The importable module is `ortidy` (`import ortidy`). The old `pandas-or` 0.1.3 stays on PyPI unmaintained (not deleted). Update all references, badges, and the module folder accordingly.
|
|
26
|
+
|
|
27
|
+
4. **Modern solver choice:** prefer **CP-SAT** (`from ortools.sat.python import cp_model`) for packing/assignment/scheduling models over the legacy `pywraplp`/SCIP path. CP-SAT is Google's recommended modern solver and removes the variable-construction mess in the old code. Keep the dedicated routing library (`ortools.constraint_solver.pywrapcp`) for routing, and the dedicated graph solvers for flow.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Renaming & migration (pandas-or → ortidy)
|
|
32
|
+
|
|
33
|
+
The two platforms behave differently — handle each correctly.
|
|
34
|
+
|
|
35
|
+
- **GitHub: rename the existing repo in place. Do NOT start a fresh repo.** Renaming preserves history, stars, issues, and watchers, and GitHub auto-redirects the old URL (web, clones, fetches, API) to the new name. Redirects break only if a new repo is later created at the old path — so don't reuse it.
|
|
36
|
+
- Before the rewrite lands, preserve the old version: tag it (`v0.1.3-legacy`) and/or cut a `legacy/0.1.x` branch. Do the rewrite on `main`.
|
|
37
|
+
- **PyPI: there is no rename feature — register `ortidy` as a new project.** Publish the new code under `ortidy`. **Decision:** *no* `pandas-or` deprecation/redirect shim is shipped — `pandas-or` had effectively no adoption, so a redirect shim isn't worth the maintenance. **Do not delete** the old `pandas-or` 0.1.3 project (deleting frees the name for typosquatters); leave it as-is. Its project page links to this GitHub repo, which redirects to `ortidy`.
|
|
38
|
+
- **Availability:** `ortidy` is confirmed free on PyPI and as an import name. If the name is ever changed again, re-check PyPI, the importable module name, GitHub, and Read the Docs *before* committing.
|
|
39
|
+
- **Surfaces to update during the rename:** `pyproject.toml` (name, urls), the module folder `pandas_or/` → `ortidy/`, `CITATION.cff`, README + badges, Read the Docs project, Binder links, and any `import pandas_or` in examples/notebooks.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Design contracts (apply to every solver)
|
|
44
|
+
|
|
45
|
+
These are the through-lines that make this a coherent library rather than a bag of functions.
|
|
46
|
+
|
|
47
|
+
### Consistent return contract
|
|
48
|
+
Every solver returns the user's original frame (same backend, same rows where applicable) with assignment columns added, plus a status. There are exactly **three result shapes** — design every feature to fit one of them:
|
|
49
|
+
- **assignment-matrix** — rows mapped to columns/bins/resources (knapsack, multi-knapsack, bin packing, assignment, transportation, facility location).
|
|
50
|
+
- **edge-flow** — values on an edge list (max-flow, min-cost-flow, shortest path, transshipment).
|
|
51
|
+
- **interval-schedule** — intervals placed on a timeline (shift scheduling, job-shop, RCPSP).
|
|
52
|
+
|
|
53
|
+
Do **not** invent a fourth shape without flagging it for discussion first.
|
|
54
|
+
|
|
55
|
+
### Result object, not print-and-None
|
|
56
|
+
The old code does `print("no optimal solution")` and implicitly returns `None`. Replace everywhere with a structured result carrying: the result frame, a status enum (`OPTIMAL` / `FEASIBLE` / `INFEASIBLE` / `UNBOUNDED` / `MODEL_INVALID`), objective value, and solve metadata (wall time, gap). **A `FEASIBLE` solution is a success, not a failure** — the old code wrongly discards anything that isn't `OPTIMAL`.
|
|
57
|
+
|
|
58
|
+
### Functional API (the canonical interface)
|
|
59
|
+
Solvers are plain functions: pass a native frame, get a `SolveResult` back in the
|
|
60
|
+
same backend, e.g. `ortidy.knapsack(df, capacity=100)` and
|
|
61
|
+
`ortidy.solve_routing(distances, vehicles=...)`. This is the canonical, fully
|
|
62
|
+
backend-agnostic surface — it works for *any* Narwhals backend.
|
|
63
|
+
|
|
64
|
+
> **Decision:** the `.or_` dataframe accessor was **removed**. It only worked on
|
|
65
|
+
> backends we explicitly registered (pandas, Polars) — undercutting the
|
|
66
|
+
> backend-agnostic promise — and mutated the global pandas/Polars namespace at
|
|
67
|
+
> import time. The functional API is the single, portable interface. If a
|
|
68
|
+
> chainable surface is wanted later, prefer an explicit, side-effect-free wrapper
|
|
69
|
+
> (`ortidy.wrap(df).knapsack(...)`) over a monkeypatch accessor.
|
|
70
|
+
|
|
71
|
+
### Schema validation
|
|
72
|
+
Standardize input validation. The old ad-hoc `if not {"value","weight"}.issubset(...)` pattern is a good instinct — formalize it with [Pandera](https://pandera.readthedocs.io/) (which supports Narwhals/Polars) or explicit schema checks. Raise precise, actionable errors (which column is missing, what dtype was expected). Prefer `ValueError`/`KeyError` over the `AttributeError` the old code raises.
|
|
73
|
+
|
|
74
|
+
### Surface solver controls
|
|
75
|
+
No magic numbers buried in function bodies. Expose `time_limit`, `random_seed`/determinism, and optimality `gap` as parameters with sensible defaults. (The old routing code hardcodes `3000` max distance, `100` span coefficient, `1`s time limit — these must become parameters.)
|
|
76
|
+
|
|
77
|
+
### Numeric data handling
|
|
78
|
+
OR-Tools knapsack requires integer values/weights; real data has floats. Provide a unit-scaling layer (scale floats to ints, solve, unscale) rather than silently passing floats through.
|
|
79
|
+
|
|
80
|
+
### Infeasibility diagnostics
|
|
81
|
+
When there is no solution, report *why* where the solver allows it (e.g. CP-SAT sufficient-assumptions-for-infeasibility, or an irreducible infeasible subset). This is a genuine differentiator — most libraries in this niche just say "no solution."
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Roadmap (phased — work in this order)
|
|
86
|
+
|
|
87
|
+
### P0 — Unbreak, rename, modernize (do first, no new features)
|
|
88
|
+
- **Execute the rename** per *Renaming & migration*: rename the GitHub repo, tag/branch the legacy version, rename the module folder `pandas_or/` → `ortidy/`, update `pyproject.toml`/`CITATION.cff`/README/badges, and register `ortidy` on PyPI (no `pandas-or` shim).
|
|
89
|
+
- **Fix the dead knapsack import.** Current OR-Tools moved the knapsack solver to `from ortools.algorithms.python import knapsack_solver` with snake_case methods. Verify exact method casing (`init` / `solve` / `best_solution_contains`) against the *installed* ortools version before committing.
|
|
90
|
+
- **Fix the broken test.** `tests/` asserts `__version__ == '0.1.0'` while the package was `0.1.3` — it has always failed. Replace with a real suite (see Testing).
|
|
91
|
+
- **Fix known bugs:**
|
|
92
|
+
- `knapsack` return type lies: docstring/annotation says `pd.Series` but `items.index.map(...)` returns an `Index`. Align with the new return contract.
|
|
93
|
+
- `single_vehicle_route` appends a phantom final row after the loop using an out-of-range index — drop it.
|
|
94
|
+
- `solve_routing` has a dead `if "capacity" in column_names` branch that checks a distance matrix's columns for a capacity field that cannot be there — remove.
|
|
95
|
+
- **Modernize packaging/tooling:** Python floor to 3.10+; PEP 621 `[project]` metadata with **uv** as the package/dependency manager (PEP 735 `[dependency-groups]`) and **hatchling** as the build backend; unpin to current `ortools` (`>=9.12`), add `narwhals`; add `ruff` (lint+format, replacing black-only), `mypy`, `pre-commit`; add GitHub Actions CI; add `nbstripout` and strip the embedded notebook output bloating the repo.
|
|
96
|
+
- **Migrate the working solvers to Narwhals + the return contract** without changing their math: `knapsack`, `multi_knapsack`, `bin_packing`, `solve_routing`. While here, kill the `items.apply(lambda x: solver.BoolVar(...), axis=1)` cross-product variable-construction anti-pattern (rebuild on CP-SAT).
|
|
97
|
+
|
|
98
|
+
### P1 — API consistency + new shapes (cheap, high-value)
|
|
99
|
+
- **Assignment & transportation** (assignment-matrix shape). Linear assignment via `ortools.graph.python.linear_sum_assignment`; generalized assignment (GAP) and transportation/transshipment via CP-SAT/MIP. A cost matrix *is* a dataframe — the most natural fit and the cheapest big win.
|
|
100
|
+
- **Network flow** (edge-flow shape). Max-flow, min-cost-flow, shortest path via the dedicated `ortools.graph.python` solvers. Naturally an edge-list frame.
|
|
101
|
+
- **Finish the routing promises** already advertised in the old README: **time windows (VRPTW)** and **pickups & deliveries**. Also expose multiple depots and heterogeneous fleets.
|
|
102
|
+
- **Separate distance-matrix construction from routing.** The old API secretly requires `df` to be a precomputed distance matrix. Add a helper that builds the matrix from lat/long (haversine) or x/y (euclidean) so the routing API takes *locations* — what users actually have.
|
|
103
|
+
- Ship the **result object** across all P0/P1 solvers. (The `.or_` accessor was removed — see *Functional API* above.)
|
|
104
|
+
|
|
105
|
+
### P2 — Flagship + polish
|
|
106
|
+
- **Scheduling / rostering** (interval-schedule shape) on CP-SAT: shift scheduling (the long-promised feature), job-shop/flow-shop, RCPSP. Build this only after the result-object design has proven itself on P1.
|
|
107
|
+
- **Facility location & covering:** facility location, p-median, set cover/partition.
|
|
108
|
+
- **Docs:** stand up `mkdocs-material` (the README has promised "Read The Docs coming soon" for four years). Document the three result shapes as the conceptual spine.
|
|
109
|
+
- Optional routing extras: optional visits / prize-collecting, backhauls.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## What to preserve from the existing code
|
|
114
|
+
|
|
115
|
+
- `pandas_or/binning/knapsack.py` — cleanest file; keep its structure and docstring style, fix the bugs above.
|
|
116
|
+
- The column-validation instinct — formalize, don't discard.
|
|
117
|
+
- The **route feature-engineering** (`tripsSinceStart`, `tripsTillEnd`, `distanceSinceStart`, `distanceTillEnd`) — the most original idea in the repo and the clearest expression of the OR-meets-analytics thesis. Reimplement on Narwhals `over` expressions (grouped `shift`, `cum_sum`); keep the concept.
|
|
118
|
+
- The sample CSVs under `pandas_or/data/` (move to `ortidy/data/`) and the Binder examples — good onboarding assets and the basis for golden-file tests.
|
|
119
|
+
- The Parameters/Returns/Link docstring convention.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Conventions
|
|
124
|
+
|
|
125
|
+
- **Style:** `ruff format` + `ruff check`. Type-annotate all public functions; `mypy` must pass.
|
|
126
|
+
- **Types:** annotate honestly. If a function returns the result object, say so — no more `pd.Series` annotations on things that aren't.
|
|
127
|
+
- **Errors:** precise and actionable. Name the missing column and expected dtype. Prefer `ValueError`/`KeyError` over `AttributeError`.
|
|
128
|
+
- **No magic numbers** in function bodies — promote to named parameters with documented defaults.
|
|
129
|
+
- **Determinism:** plumb a `random_seed` through solvers that support it so tests and examples are reproducible.
|
|
130
|
+
- **Public API** is re-exported from `ortidy/__init__.py` and listed in `__all__`; keep it curated.
|
|
131
|
+
|
|
132
|
+
## Testing
|
|
133
|
+
|
|
134
|
+
- `pytest`, run via `uv run pytest`.
|
|
135
|
+
- Every solver needs: a correctness test (assert the math), a **golden-file test** against the bundled sample CSVs, an **infeasible-input test** (assert the right status, not an exception), and a **backend-parity test** (same input as pandas *and* Polars yields equivalent results — this is the whole point of Narwhals).
|
|
136
|
+
- Assert solver *status*, not just output shape. Cover the `FEASIBLE`-is-success path explicitly.
|
|
137
|
+
|
|
138
|
+
## Dev commands
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv sync # install runtime + dev deps into .venv (uses .python-version = 3.12)
|
|
142
|
+
uv sync --group docs # also install the docs toolchain
|
|
143
|
+
uv run pytest
|
|
144
|
+
uv run ruff check . && uv run ruff format --check .
|
|
145
|
+
uv run mypy ortidy
|
|
146
|
+
uv run pre-commit run --all-files
|
|
147
|
+
uv build # build sdist + wheel (hatchling)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Guardrails for Claude Code
|
|
151
|
+
|
|
152
|
+
- Do not reintroduce a hard pandas dependency in solver logic — go through the Narwhals layer.
|
|
153
|
+
- Do not pin OR-Tools to an old version to make old import paths work; migrate to current imports instead.
|
|
154
|
+
- Do not add a result shape beyond the three above without raising it first.
|
|
155
|
+
- When unsure whether a solver returned its best answer, prefer accepting `FEASIBLE` over rejecting it.
|
|
156
|
+
- Verify any OR-Tools import path and method casing against the installed version — the bindings have drifted and will drift again.
|
|
157
|
+
- Do not start a fresh GitHub repo for the rename — rename in place to preserve history and redirects.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Contributing to ortidy
|
|
2
|
+
|
|
3
|
+
Thanks for your interest! `ortidy` is a backend-agnostic dataframe façade over
|
|
4
|
+
Google OR-Tools. Please read [CLAUDE.md](CLAUDE.md) for the design principles
|
|
5
|
+
before proposing larger changes.
|
|
6
|
+
|
|
7
|
+
## Development setup
|
|
8
|
+
|
|
9
|
+
`ortidy` uses [uv](https://docs.astral.sh/uv/). The dev interpreter is pinned by
|
|
10
|
+
`.python-version` (3.12).
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv sync # runtime + dev dependencies
|
|
14
|
+
uv sync --group docs # also the docs toolchain
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## The checks (all must pass)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run pytest # tests
|
|
21
|
+
uv run ruff check . && uv run ruff format --check . # lint + format
|
|
22
|
+
uv run mypy ortidy # types
|
|
23
|
+
uv run --group docs mkdocs build --strict # docs
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`uv run pre-commit run --all-files` runs ruff, nbstripout, and mypy locally.
|
|
27
|
+
|
|
28
|
+
## Design contracts
|
|
29
|
+
|
|
30
|
+
These are the through-lines that keep `ortidy` coherent — match them:
|
|
31
|
+
|
|
32
|
+
- **Three result shapes.** Every solver returns *assignment-matrix*, *edge-flow*,
|
|
33
|
+
or *interval-schedule* (see [docs/result-shapes.md](docs/result-shapes.md)).
|
|
34
|
+
Don't add a fourth without raising it first.
|
|
35
|
+
- **One result object.** Solvers return a `SolveResult` (frame, status, objective,
|
|
36
|
+
metadata); a `FEASIBLE` solution is a success.
|
|
37
|
+
- **Backend-agnostic.** Go through the Narwhals layer (`ortidy/_narwhals.py`);
|
|
38
|
+
accept native frames in, return the same backend out, extract plain Python at
|
|
39
|
+
the solver boundary. No hard pandas dependency in solver logic.
|
|
40
|
+
- **Explicit id columns**, never a positional index.
|
|
41
|
+
- **Verify OR-Tools import paths/method casing against the installed version** —
|
|
42
|
+
the bindings drift.
|
|
43
|
+
|
|
44
|
+
## Adding a solver
|
|
45
|
+
|
|
46
|
+
1. Validate inputs with `ortidy/schema.py` (precise `ValueError`/`KeyError`).
|
|
47
|
+
2. Return a `SolveResult`; expose `time_limit` / `random_seed` where supported.
|
|
48
|
+
3. Add it to `ortidy/__init__.py` `__all__` and the docs.
|
|
49
|
+
4. Tests: correctness, golden-file (bundled CSVs), infeasible-status, and
|
|
50
|
+
pandas + Polars backend-parity.
|
|
51
|
+
|
|
52
|
+
## Pull requests
|
|
53
|
+
|
|
54
|
+
Keep PRs focused; make sure the checks above are green. Update `CHANGELOG.md`
|
|
55
|
+
under an `## [Unreleased]` heading.
|