rimpy 0.1.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.
@@ -0,0 +1,79 @@
1
+ name: Build & Publish
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch: # manual trigger for testing
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ # ---------------------------------------------------------------
13
+ # Build wheels for each OS × Python version
14
+ # ---------------------------------------------------------------
15
+ build:
16
+ name: Build / ${{ matrix.os }} / ${{ matrix.python }}
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ os: [ubuntu-latest, windows-latest, macos-latest]
21
+ python: ["3.12", "3.13", "3.14"]
22
+ runs-on: ${{ matrix.os }}
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: PyO3/maturin-action@v1
28
+ with:
29
+ command: build
30
+ args: --release -o dist -i python${{ matrix.python }}
31
+
32
+ - uses: actions/upload-artifact@v4
33
+ with:
34
+ name: wheel-${{ matrix.os }}-py${{ matrix.python }}
35
+ path: dist/*.whl
36
+
37
+ # ---------------------------------------------------------------
38
+ # Build source distribution (fallback for unsupported platforms)
39
+ # ---------------------------------------------------------------
40
+ sdist:
41
+ name: Build sdist
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - uses: PyO3/maturin-action@v1
47
+ with:
48
+ command: sdist
49
+ args: -o dist
50
+
51
+ - uses: actions/upload-artifact@v4
52
+ with:
53
+ name: sdist
54
+ path: dist/*.tar.gz
55
+
56
+ # ---------------------------------------------------------------
57
+ # Publish everything to PyPI
58
+ # ---------------------------------------------------------------
59
+ publish:
60
+ name: Publish to PyPI
61
+ needs: [build, sdist]
62
+ runs-on: ubuntu-latest
63
+ environment: pypi
64
+ permissions:
65
+ id-token: write # trusted publishing (no API token needed)
66
+
67
+ steps:
68
+ - uses: actions/download-artifact@v4
69
+ with:
70
+ pattern: wheel-*
71
+ merge-multiple: true
72
+ path: dist
73
+
74
+ - uses: actions/download-artifact@v4
75
+ with:
76
+ name: sdist
77
+ path: dist
78
+
79
+ - uses: pypa/gh-action-pypi-publish@release/v1
rimpy-0.1.0/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ # Rust build artifacts
2
+ target/
3
+ Cargo.lock
4
+
5
+ # Compiled Python extensions (built by maturin)
6
+ *.pyd
7
+ *.so
8
+ *.dylib
9
+
10
+ # Python
11
+ __pycache__/
12
+ *.pyc
13
+ *.egg-info/
14
+ dist/
15
+ build/
16
+
17
+ # Virtual environments
18
+ .venv/
19
+
20
+ # IDE
21
+ .idea/
22
+ .vscode/
rimpy-0.1.0/Cargo.lock ADDED
@@ -0,0 +1,304 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
10
+
11
+ [[package]]
12
+ name = "crossbeam-deque"
13
+ version = "0.8.6"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
16
+ dependencies = [
17
+ "crossbeam-epoch",
18
+ "crossbeam-utils",
19
+ ]
20
+
21
+ [[package]]
22
+ name = "crossbeam-epoch"
23
+ version = "0.9.18"
24
+ source = "registry+https://github.com/rust-lang/crates.io-index"
25
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
26
+ dependencies = [
27
+ "crossbeam-utils",
28
+ ]
29
+
30
+ [[package]]
31
+ name = "crossbeam-utils"
32
+ version = "0.8.21"
33
+ source = "registry+https://github.com/rust-lang/crates.io-index"
34
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
35
+
36
+ [[package]]
37
+ name = "either"
38
+ version = "1.15.0"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
41
+
42
+ [[package]]
43
+ name = "equivalent"
44
+ version = "1.0.2"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
47
+
48
+ [[package]]
49
+ name = "hashbrown"
50
+ version = "0.16.1"
51
+ source = "registry+https://github.com/rust-lang/crates.io-index"
52
+ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
53
+
54
+ [[package]]
55
+ name = "heck"
56
+ version = "0.5.0"
57
+ source = "registry+https://github.com/rust-lang/crates.io-index"
58
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
59
+
60
+ [[package]]
61
+ name = "indexmap"
62
+ version = "2.13.0"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
65
+ dependencies = [
66
+ "equivalent",
67
+ "hashbrown",
68
+ ]
69
+
70
+ [[package]]
71
+ name = "libc"
72
+ version = "0.2.182"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
75
+
76
+ [[package]]
77
+ name = "matrixmultiply"
78
+ version = "0.3.10"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
81
+ dependencies = [
82
+ "autocfg",
83
+ "rawpointer",
84
+ ]
85
+
86
+ [[package]]
87
+ name = "ndarray"
88
+ version = "0.17.2"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d"
91
+ dependencies = [
92
+ "matrixmultiply",
93
+ "num-complex",
94
+ "num-integer",
95
+ "num-traits",
96
+ "portable-atomic",
97
+ "portable-atomic-util",
98
+ "rawpointer",
99
+ ]
100
+
101
+ [[package]]
102
+ name = "num-complex"
103
+ version = "0.4.6"
104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
105
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
106
+ dependencies = [
107
+ "num-traits",
108
+ ]
109
+
110
+ [[package]]
111
+ name = "num-integer"
112
+ version = "0.1.46"
113
+ source = "registry+https://github.com/rust-lang/crates.io-index"
114
+ checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
115
+ dependencies = [
116
+ "num-traits",
117
+ ]
118
+
119
+ [[package]]
120
+ name = "num-traits"
121
+ version = "0.2.19"
122
+ source = "registry+https://github.com/rust-lang/crates.io-index"
123
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
124
+ dependencies = [
125
+ "autocfg",
126
+ ]
127
+
128
+ [[package]]
129
+ name = "numpy"
130
+ version = "0.28.0"
131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
132
+ checksum = "778da78c64ddc928ebf5ad9df5edf0789410ff3bdbf3619aed51cd789a6af1e2"
133
+ dependencies = [
134
+ "libc",
135
+ "ndarray",
136
+ "num-complex",
137
+ "num-integer",
138
+ "num-traits",
139
+ "pyo3",
140
+ "pyo3-build-config",
141
+ "rustc-hash",
142
+ ]
143
+
144
+ [[package]]
145
+ name = "once_cell"
146
+ version = "1.21.3"
147
+ source = "registry+https://github.com/rust-lang/crates.io-index"
148
+ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
149
+
150
+ [[package]]
151
+ name = "portable-atomic"
152
+ version = "1.13.1"
153
+ source = "registry+https://github.com/rust-lang/crates.io-index"
154
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
155
+
156
+ [[package]]
157
+ name = "portable-atomic-util"
158
+ version = "0.2.5"
159
+ source = "registry+https://github.com/rust-lang/crates.io-index"
160
+ checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5"
161
+ dependencies = [
162
+ "portable-atomic",
163
+ ]
164
+
165
+ [[package]]
166
+ name = "proc-macro2"
167
+ version = "1.0.106"
168
+ source = "registry+https://github.com/rust-lang/crates.io-index"
169
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
170
+ dependencies = [
171
+ "unicode-ident",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "pyo3"
176
+ version = "0.28.1"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "14c738662e2181be11cb82487628404254902bb3225d8e9e99c31f3ef82a405c"
179
+ dependencies = [
180
+ "libc",
181
+ "once_cell",
182
+ "portable-atomic",
183
+ "pyo3-build-config",
184
+ "pyo3-ffi",
185
+ "pyo3-macros",
186
+ ]
187
+
188
+ [[package]]
189
+ name = "pyo3-build-config"
190
+ version = "0.28.1"
191
+ source = "registry+https://github.com/rust-lang/crates.io-index"
192
+ checksum = "f9ca0864a7dd3c133a7f3f020cbff2e12e88420da854c35540fd20ce2d60e435"
193
+ dependencies = [
194
+ "target-lexicon",
195
+ ]
196
+
197
+ [[package]]
198
+ name = "pyo3-ffi"
199
+ version = "0.28.1"
200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
201
+ checksum = "9dfc1956b709823164763a34cc42bbfd26b8730afa77809a3df8b94a3ae3b059"
202
+ dependencies = [
203
+ "libc",
204
+ "pyo3-build-config",
205
+ ]
206
+
207
+ [[package]]
208
+ name = "pyo3-macros"
209
+ version = "0.28.1"
210
+ source = "registry+https://github.com/rust-lang/crates.io-index"
211
+ checksum = "29dc660ad948bae134d579661d08033fbb1918f4529c3bbe3257a68f2009ddf2"
212
+ dependencies = [
213
+ "proc-macro2",
214
+ "pyo3-macros-backend",
215
+ "quote",
216
+ "syn",
217
+ ]
218
+
219
+ [[package]]
220
+ name = "pyo3-macros-backend"
221
+ version = "0.28.1"
222
+ source = "registry+https://github.com/rust-lang/crates.io-index"
223
+ checksum = "e78cd6c6d718acfcedf26c3d21fe0f053624368b0d44298c55d7138fde9331f7"
224
+ dependencies = [
225
+ "heck",
226
+ "proc-macro2",
227
+ "pyo3-build-config",
228
+ "quote",
229
+ "syn",
230
+ ]
231
+
232
+ [[package]]
233
+ name = "quote"
234
+ version = "1.0.44"
235
+ source = "registry+https://github.com/rust-lang/crates.io-index"
236
+ checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
237
+ dependencies = [
238
+ "proc-macro2",
239
+ ]
240
+
241
+ [[package]]
242
+ name = "rawpointer"
243
+ version = "0.2.1"
244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
245
+ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
246
+
247
+ [[package]]
248
+ name = "rayon"
249
+ version = "1.11.0"
250
+ source = "registry+https://github.com/rust-lang/crates.io-index"
251
+ checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
252
+ dependencies = [
253
+ "either",
254
+ "rayon-core",
255
+ ]
256
+
257
+ [[package]]
258
+ name = "rayon-core"
259
+ version = "1.13.0"
260
+ source = "registry+https://github.com/rust-lang/crates.io-index"
261
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
262
+ dependencies = [
263
+ "crossbeam-deque",
264
+ "crossbeam-utils",
265
+ ]
266
+
267
+ [[package]]
268
+ name = "rimpy_engine"
269
+ version = "0.1.0"
270
+ dependencies = [
271
+ "indexmap",
272
+ "numpy",
273
+ "pyo3",
274
+ "rayon",
275
+ ]
276
+
277
+ [[package]]
278
+ name = "rustc-hash"
279
+ version = "2.1.1"
280
+ source = "registry+https://github.com/rust-lang/crates.io-index"
281
+ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
282
+
283
+ [[package]]
284
+ name = "syn"
285
+ version = "2.0.115"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12"
288
+ dependencies = [
289
+ "proc-macro2",
290
+ "quote",
291
+ "unicode-ident",
292
+ ]
293
+
294
+ [[package]]
295
+ name = "target-lexicon"
296
+ version = "0.13.4"
297
+ source = "registry+https://github.com/rust-lang/crates.io-index"
298
+ checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba"
299
+
300
+ [[package]]
301
+ name = "unicode-ident"
302
+ version = "1.0.23"
303
+ source = "registry+https://github.com/rust-lang/crates.io-index"
304
+ checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
rimpy-0.1.0/Cargo.toml ADDED
@@ -0,0 +1,27 @@
1
+ [package]
2
+ name = "rimpy_engine"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ description = "Fast RIM/raking engine for rimpy"
6
+ readme = "README.md"
7
+
8
+ [lib]
9
+ name = "_rimpy_engine"
10
+ crate-type = ["cdylib"]
11
+
12
+ [dependencies]
13
+ pyo3 = { version = "0.28", features = ["extension-module"] }
14
+ numpy = "0.28" # Zero-copy NumPy array access
15
+ rayon = "1.10" # Parallel iteration for grouped raking
16
+ indexmap = "2" # Preserves insertion order like Python dict
17
+ # arrow = { version = "54", optional = true } # Future: zero-copy from Polars
18
+
19
+ [features]
20
+ default = []
21
+ # arrow = ["dep:arrow"] # Enable when ready for Arrow FFI path
22
+
23
+ [profile.release]
24
+ lto = true # Link-time optimization for max speed
25
+ codegen-units = 1 # Single codegen unit = better optimization
26
+ opt-level = 3
27
+ strip = true
rimpy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: rimpy
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Science/Research
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Dist: narwhals>=2.16.0
13
+ Requires-Dist: numpy>=2.4.2
14
+ Requires-Dist: polars>=1.38.1 ; extra == 'all'
15
+ Requires-Dist: pandas>=3.0.0 ; extra == 'all'
16
+ Requires-Dist: pytest>=9.0.2 ; extra == 'dev'
17
+ Requires-Dist: pandas>=3.0.0 ; extra == 'dev'
18
+ Requires-Dist: polars>=1.38.1 ; extra == 'dev'
19
+ Requires-Dist: pandas>=3.0.0 ; extra == 'pandas'
20
+ Requires-Dist: polars>=1.38.1 ; extra == 'polars'
21
+ Provides-Extra: all
22
+ Provides-Extra: dev
23
+ Provides-Extra: pandas
24
+ Provides-Extra: polars
25
+ Summary: Fast RIM (raking) survey weighting with narwhals - supports polars and pandas
26
+ Keywords: survey,weighting,raking,rim,rim-weighting,iterative-proportional-fitting,ipf,market-research,polars,pandas,narwhals
27
+ Author: Albert Li
28
+ License-Expression: MIT
29
+ Requires-Python: >=3.12
30
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
31
+ Project-URL: Documentation, https://github.com/albertxli/rimpy
32
+ Project-URL: Homepage, https://github.com/albertxli/rimpy
33
+ Project-URL: Repository, https://github.com/albertxli/rimpy
34
+
35
+ # rimpy — Rust-accelerated RIM engine
36
+
37
+ ## Architecture
38
+
39
+ ```
40
+ rimpy/
41
+ ├── Cargo.toml # Rust crate definition
42
+ ├── pyproject.toml # Python package (maturin build)
43
+ ├── src/ # Rust source
44
+ │ ├── lib.rs # PyO3 bindings + module exports
45
+ │ └── engine.rs # Core RIM algorithm (pure Rust)
46
+ ├── python/ # Python source (maturin mixed layout)
47
+ │ └── rimpy/
48
+ │ ├── __init__.py # Public API (unchanged)
49
+ │ ├── _engine.py # Shim: tries Rust → falls back to Python
50
+ │ ├── _engine_py.py # Pure Python/NumPy fallback
51
+ │ ├── _rake.py # Narwhals orchestration (unchanged)
52
+ │ └── _loaders.py # Scheme loaders (unchanged)
53
+ ├── tests/
54
+ │ └── test_backend_parity.py # Validates Rust == Python results
55
+ └── benchmarks/
56
+ └── bench_engine.py # Performance comparison
57
+ ```
58
+
59
+ ### What changed, what didn't
60
+
61
+ | File | Status | Why |
62
+ |------|--------|-----|
63
+ | `_rake.py` | **Unchanged** | Orchestration layer, not a bottleneck |
64
+ | `_loaders.py` | **Unchanged** | I/O bound, no benefit from Rust |
65
+ | `__init__.py` | **Unchanged** | Public API stays the same |
66
+ | `_engine.py` | **Now a shim** | Tries `rimpy_engine` (Rust), falls back to `_engine_py` |
67
+ | `_engine_py.py` | **Renamed original** | Pure Python fallback for portability |
68
+ | `src/engine.rs` | **New** | Core RIM loop in Rust (raw slices, zero alloc) |
69
+ | `src/lib.rs` | **New** | PyO3 bindings accepting NumPy arrays |
70
+
71
+ ### Why this design
72
+
73
+ - **Zero API changes**: `_rake.py` still does `from ._engine import RakeResult, rim_iterate`. Users see no difference.
74
+ - **Graceful fallback**: No Rust compiler? No problem — pure Python still works.
75
+ - **Check which backend**: `rimpy._engine.get_backend()` returns `"rust"` or `"python"`.
76
+
77
+ ## Building
78
+
79
+ ### Prerequisites
80
+
81
+ - Rust toolchain: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
82
+ - uv (already installed)
83
+
84
+ ### Development build
85
+
86
+ ```bash
87
+ # uv reads [build-system] → maturin, handles everything
88
+ uv pip install -e ".[dev]"
89
+
90
+ # Verify
91
+ uv run python -c "from rimpy._engine import get_backend; print(get_backend())"
92
+ # → "rust"
93
+ ```
94
+
95
+ ### Release build (wheel)
96
+
97
+ ```bash
98
+ uv build
99
+ # Output: dist/rimpy-0.2.0-cp312-cp312-*.whl
100
+ ```
101
+
102
+ ### Publish to PyPI
103
+
104
+ ```bash
105
+ uv publish
106
+ ```
107
+
108
+ ### Run benchmarks
109
+
110
+ ```bash
111
+ uv run python benchmarks/bench_engine.py
112
+ ```
113
+
114
+ ### Run tests
115
+
116
+ ```bash
117
+ uv run pytest tests/ -v
118
+ ```
119
+
120
+ ## Rust engine design decisions
121
+
122
+ ### Why raw `Vec<f64>` instead of ndarray or Arrow?
123
+
124
+ RIM's core operation is **indexed gather/scatter on a 1D array**:
125
+ - Gather: sum `weights[indices]` for each category
126
+ - Scatter: `weights[indices] *= multiplier`
127
+
128
+ This is trivially expressed with plain slices. `ndarray` would add abstraction overhead
129
+ and allocate on fancy indexing. Arrow is a columnar format, not a compute engine.
130
+
131
+ Raw slices let `rustc` + LLVM auto-vectorize with SIMD, and the hot loop does **zero
132
+ heap allocations** (the `old_weights` buffer is pre-allocated and reused via `copy_from_slice`).
133
+
134
+ ### Why Rayon for grouped raking?
135
+
136
+ Each country/segment gets its own weights vector — no shared mutable state.
137
+ This is embarrassingly parallel. `rayon::par_iter()` gives near-linear speedup
138
+ across CPU cores with zero synchronization overhead.
139
+
140
+ ### Data ingress
141
+
142
+ Currently accepts NumPy arrays via `pyo3-numpy` (near-zero-copy for contiguous arrays).
143
+
144
+ **Future**: Arrow FFI path for zero-copy from Polars. The engine itself stays the same —
145
+ only the ingress layer changes.
146
+
147
+ ## Integration with existing code
148
+
149
+ Your `_rake.py` and `_loaders.py` remain identical. The only structural change is:
150
+
151
+ ```python
152
+ # Before (in _rake.py):
153
+ from ._engine import RakeResult, rim_iterate
154
+
155
+ # After (same import — _engine.py is now a shim):
156
+ from ._engine import RakeResult, rim_iterate # tries Rust, falls back to Python
157
+ ```
158
+
159
+ The shim handles everything transparently.
160
+