statspai-hdfe 0.8.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,375 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 3
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 = "bitflags"
13
+ version = "2.11.1"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
16
+
17
+ [[package]]
18
+ name = "cfg-if"
19
+ version = "1.0.4"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
22
+
23
+ [[package]]
24
+ name = "crossbeam-deque"
25
+ version = "0.8.6"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
28
+ dependencies = [
29
+ "crossbeam-epoch",
30
+ "crossbeam-utils",
31
+ ]
32
+
33
+ [[package]]
34
+ name = "crossbeam-epoch"
35
+ version = "0.9.18"
36
+ source = "registry+https://github.com/rust-lang/crates.io-index"
37
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
38
+ dependencies = [
39
+ "crossbeam-utils",
40
+ ]
41
+
42
+ [[package]]
43
+ name = "crossbeam-utils"
44
+ version = "0.8.21"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
47
+
48
+ [[package]]
49
+ name = "either"
50
+ version = "1.15.0"
51
+ source = "registry+https://github.com/rust-lang/crates.io-index"
52
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
53
+
54
+ [[package]]
55
+ name = "heck"
56
+ version = "0.4.1"
57
+ source = "registry+https://github.com/rust-lang/crates.io-index"
58
+ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
59
+
60
+ [[package]]
61
+ name = "indoc"
62
+ version = "2.0.7"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
65
+ dependencies = [
66
+ "rustversion",
67
+ ]
68
+
69
+ [[package]]
70
+ name = "libc"
71
+ version = "0.2.186"
72
+ source = "registry+https://github.com/rust-lang/crates.io-index"
73
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
74
+
75
+ [[package]]
76
+ name = "lock_api"
77
+ version = "0.4.14"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
80
+ dependencies = [
81
+ "scopeguard",
82
+ ]
83
+
84
+ [[package]]
85
+ name = "matrixmultiply"
86
+ version = "0.3.10"
87
+ source = "registry+https://github.com/rust-lang/crates.io-index"
88
+ checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
89
+ dependencies = [
90
+ "autocfg",
91
+ "rawpointer",
92
+ ]
93
+
94
+ [[package]]
95
+ name = "memoffset"
96
+ version = "0.9.1"
97
+ source = "registry+https://github.com/rust-lang/crates.io-index"
98
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
99
+ dependencies = [
100
+ "autocfg",
101
+ ]
102
+
103
+ [[package]]
104
+ name = "ndarray"
105
+ version = "0.15.6"
106
+ source = "registry+https://github.com/rust-lang/crates.io-index"
107
+ checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
108
+ dependencies = [
109
+ "matrixmultiply",
110
+ "num-complex",
111
+ "num-integer",
112
+ "num-traits",
113
+ "rawpointer",
114
+ ]
115
+
116
+ [[package]]
117
+ name = "num-complex"
118
+ version = "0.4.6"
119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
120
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
121
+ dependencies = [
122
+ "num-traits",
123
+ ]
124
+
125
+ [[package]]
126
+ name = "num-integer"
127
+ version = "0.1.46"
128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
129
+ checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
130
+ dependencies = [
131
+ "num-traits",
132
+ ]
133
+
134
+ [[package]]
135
+ name = "num-traits"
136
+ version = "0.2.19"
137
+ source = "registry+https://github.com/rust-lang/crates.io-index"
138
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
139
+ dependencies = [
140
+ "autocfg",
141
+ ]
142
+
143
+ [[package]]
144
+ name = "numpy"
145
+ version = "0.21.0"
146
+ source = "registry+https://github.com/rust-lang/crates.io-index"
147
+ checksum = "ec170733ca37175f5d75a5bea5911d6ff45d2cd52849ce98b685394e4f2f37f4"
148
+ dependencies = [
149
+ "libc",
150
+ "ndarray",
151
+ "num-complex",
152
+ "num-integer",
153
+ "num-traits",
154
+ "pyo3",
155
+ "rustc-hash",
156
+ ]
157
+
158
+ [[package]]
159
+ name = "once_cell"
160
+ version = "1.21.4"
161
+ source = "registry+https://github.com/rust-lang/crates.io-index"
162
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
163
+
164
+ [[package]]
165
+ name = "parking_lot"
166
+ version = "0.12.5"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
169
+ dependencies = [
170
+ "lock_api",
171
+ "parking_lot_core",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "parking_lot_core"
176
+ version = "0.9.12"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
179
+ dependencies = [
180
+ "cfg-if",
181
+ "libc",
182
+ "redox_syscall",
183
+ "smallvec",
184
+ "windows-link",
185
+ ]
186
+
187
+ [[package]]
188
+ name = "portable-atomic"
189
+ version = "1.13.1"
190
+ source = "registry+https://github.com/rust-lang/crates.io-index"
191
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
192
+
193
+ [[package]]
194
+ name = "proc-macro2"
195
+ version = "1.0.106"
196
+ source = "registry+https://github.com/rust-lang/crates.io-index"
197
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
198
+ dependencies = [
199
+ "unicode-ident",
200
+ ]
201
+
202
+ [[package]]
203
+ name = "pyo3"
204
+ version = "0.21.2"
205
+ source = "registry+https://github.com/rust-lang/crates.io-index"
206
+ checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
207
+ dependencies = [
208
+ "cfg-if",
209
+ "indoc",
210
+ "libc",
211
+ "memoffset",
212
+ "parking_lot",
213
+ "portable-atomic",
214
+ "pyo3-build-config",
215
+ "pyo3-ffi",
216
+ "pyo3-macros",
217
+ "unindent",
218
+ ]
219
+
220
+ [[package]]
221
+ name = "pyo3-build-config"
222
+ version = "0.21.2"
223
+ source = "registry+https://github.com/rust-lang/crates.io-index"
224
+ checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50"
225
+ dependencies = [
226
+ "once_cell",
227
+ "target-lexicon",
228
+ ]
229
+
230
+ [[package]]
231
+ name = "pyo3-ffi"
232
+ version = "0.21.2"
233
+ source = "registry+https://github.com/rust-lang/crates.io-index"
234
+ checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403"
235
+ dependencies = [
236
+ "libc",
237
+ "pyo3-build-config",
238
+ ]
239
+
240
+ [[package]]
241
+ name = "pyo3-macros"
242
+ version = "0.21.2"
243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
244
+ checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c"
245
+ dependencies = [
246
+ "proc-macro2",
247
+ "pyo3-macros-backend",
248
+ "quote",
249
+ "syn",
250
+ ]
251
+
252
+ [[package]]
253
+ name = "pyo3-macros-backend"
254
+ version = "0.21.2"
255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
256
+ checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c"
257
+ dependencies = [
258
+ "heck",
259
+ "proc-macro2",
260
+ "pyo3-build-config",
261
+ "quote",
262
+ "syn",
263
+ ]
264
+
265
+ [[package]]
266
+ name = "quote"
267
+ version = "1.0.45"
268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
269
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
270
+ dependencies = [
271
+ "proc-macro2",
272
+ ]
273
+
274
+ [[package]]
275
+ name = "rawpointer"
276
+ version = "0.2.1"
277
+ source = "registry+https://github.com/rust-lang/crates.io-index"
278
+ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
279
+
280
+ [[package]]
281
+ name = "rayon"
282
+ version = "1.12.0"
283
+ source = "registry+https://github.com/rust-lang/crates.io-index"
284
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
285
+ dependencies = [
286
+ "either",
287
+ "rayon-core",
288
+ ]
289
+
290
+ [[package]]
291
+ name = "rayon-core"
292
+ version = "1.13.0"
293
+ source = "registry+https://github.com/rust-lang/crates.io-index"
294
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
295
+ dependencies = [
296
+ "crossbeam-deque",
297
+ "crossbeam-utils",
298
+ ]
299
+
300
+ [[package]]
301
+ name = "redox_syscall"
302
+ version = "0.5.18"
303
+ source = "registry+https://github.com/rust-lang/crates.io-index"
304
+ checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
305
+ dependencies = [
306
+ "bitflags",
307
+ ]
308
+
309
+ [[package]]
310
+ name = "rustc-hash"
311
+ version = "1.1.0"
312
+ source = "registry+https://github.com/rust-lang/crates.io-index"
313
+ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
314
+
315
+ [[package]]
316
+ name = "rustversion"
317
+ version = "1.0.22"
318
+ source = "registry+https://github.com/rust-lang/crates.io-index"
319
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
320
+
321
+ [[package]]
322
+ name = "scopeguard"
323
+ version = "1.2.0"
324
+ source = "registry+https://github.com/rust-lang/crates.io-index"
325
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
326
+
327
+ [[package]]
328
+ name = "smallvec"
329
+ version = "1.15.1"
330
+ source = "registry+https://github.com/rust-lang/crates.io-index"
331
+ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
332
+
333
+ [[package]]
334
+ name = "statspai_hdfe"
335
+ version = "0.8.0"
336
+ dependencies = [
337
+ "numpy",
338
+ "pyo3",
339
+ "rayon",
340
+ ]
341
+
342
+ [[package]]
343
+ name = "syn"
344
+ version = "2.0.117"
345
+ source = "registry+https://github.com/rust-lang/crates.io-index"
346
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
347
+ dependencies = [
348
+ "proc-macro2",
349
+ "quote",
350
+ "unicode-ident",
351
+ ]
352
+
353
+ [[package]]
354
+ name = "target-lexicon"
355
+ version = "0.12.16"
356
+ source = "registry+https://github.com/rust-lang/crates.io-index"
357
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
358
+
359
+ [[package]]
360
+ name = "unicode-ident"
361
+ version = "1.0.24"
362
+ source = "registry+https://github.com/rust-lang/crates.io-index"
363
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
364
+
365
+ [[package]]
366
+ name = "unindent"
367
+ version = "0.2.4"
368
+ source = "registry+https://github.com/rust-lang/crates.io-index"
369
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
370
+
371
+ [[package]]
372
+ name = "windows-link"
373
+ version = "0.2.1"
374
+ source = "registry+https://github.com/rust-lang/crates.io-index"
375
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
@@ -0,0 +1,34 @@
1
+ [package]
2
+ name = "statspai_hdfe"
3
+ version = "0.8.0"
4
+ edition = "2021"
5
+ authors = ["Bryce Wang <brycew6m@stanford.edu>"]
6
+ description = "Rust kernels (HDFE demeaning, Poisson IRLS, cluster meat) for the StatsPAI Python package"
7
+ license = "MIT"
8
+ readme = "README.md"
9
+ rust-version = "1.80"
10
+ repository = "https://github.com/brycewang-stanford/StatsPAI"
11
+
12
+ [lib]
13
+ name = "statspai_hdfe"
14
+ crate-type = ["cdylib"]
15
+
16
+ [dependencies]
17
+ # abi3: one wheel per platform serves every CPython >= 3.10 (the floor
18
+ # of StatsPAI's ``requires-python``).
19
+ pyo3 = { version = "0.21", features = ["abi3-py310"] }
20
+ numpy = "0.21"
21
+ rayon = "1.10"
22
+
23
+ [features]
24
+ # maturin turns this on (pyproject.toml); plain ``cargo test`` leaves it
25
+ # off so the test binary links against libpython.
26
+ extension-module = ["pyo3/extension-module"]
27
+
28
+ [profile.release]
29
+ opt-level = 3
30
+ lto = "fat"
31
+ codegen-units = 1
32
+ # Unwind, not abort: PyO3 turns a panic into a Python exception only
33
+ # when it can unwind; with "abort" any bug would kill the interpreter.
34
+ panic = "unwind"
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: statspai-hdfe
3
+ Version: 0.8.0
4
+ Classifier: License :: OSI Approved :: MIT License
5
+ Classifier: Programming Language :: Rust
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: Implementation :: CPython
8
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
9
+ Requires-Dist: numpy>=1.20
10
+ Summary: Rust kernels for StatsPAI: HDFE demeaning, Poisson IRLS, cluster-robust meat
11
+ Author-email: Biaoyue Wang <brycew6m@stanford.edu>
12
+ License: MIT
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
+ Project-URL: Homepage, https://github.com/brycewang-stanford/StatsPAI
16
+ Project-URL: Source, https://github.com/brycewang-stanford/StatsPAI/tree/main/rust/statspai_hdfe
17
+
18
+ # statspai-hdfe
19
+
20
+ Rust kernels for [StatsPAI](https://github.com/brycewang-stanford/StatsPAI):
21
+ high-dimensional fixed-effect (HDFE) demeaning, the native Poisson IRLS
22
+ behind `sp.fast.fepois`, and the cluster-robust sandwich meat.
23
+
24
+ ```bash
25
+ pip install "statspai[rust]" # or: pip install statspai-hdfe
26
+ ```
27
+
28
+ StatsPAI never requires this package. Without it the same functions run
29
+ on NumPy / Numba and agree with the Rust path to within the
30
+ residualisation tolerance. Check which one is in use:
31
+
32
+ ```python
33
+ import statspai as sp
34
+ sp.fast.backend_info() # {'available': True, 'version': '0.8.0', 'threads': 8, ...}
35
+ sp.fast.set_num_threads(4) # or set STATSPAI_NUM_THREADS before starting Python
36
+ ```
37
+
38
+ `STATSPAI_SKIP_RUST=1` makes StatsPAI ignore an installed extension.
39
+
40
+ ## What uses it
41
+
42
+ | StatsPAI entry point | Kernel |
43
+ | --- | --- |
44
+ | `sp.fast.feols`, `sp.fast.demean`, `sp.fast.within`, `sp.fast.event_study` | `demean_2d` |
45
+ | `sp.fast.feols(weights=)`, `sp.ppmlhdfe` (two or more FEs) | `demean_2d_weighted` |
46
+ | `sp.fast.fepois` | `fepois_irls`, `separation_mask` |
47
+ | `sp.iv(absorb=)`, `sp.didregress`, `sp.iv.iv_diag`, weak-IV diagnostics | `demean_2d` |
48
+ | `sp.regress(cluster=)` (one-way) | `cluster_meat` |
49
+ | singleton pruning in `sp.fast` | `singleton_mask` |
50
+
51
+ `sp.hdfe_ols` / `sp.Absorber`, `sp.panel` and the pyfixest-backed
52
+ `sp.feols` do not use it.
53
+
54
+ ## Algorithm
55
+
56
+ The fixed-effect projection is solved in the space of FE coefficients,
57
+ as `fixest` does (Bergé 2018; `paper.bib` key `berge2018efficient`):
58
+
59
+ * **Two FEs.** One Gauss-Seidel sweep needs only the group sums of the
60
+ column and the sparse cross-tabulation of the two FEs, so it costs
61
+ `O(nnz)` instead of `O(n)`; on panels where a unit meets few levels of
62
+ the other FE (worker x firm, firm x year) that is a small fraction of
63
+ the rows. The iteration runs on the coefficients of the smaller FE and
64
+ is accelerated with Irons-Tuck at every step.
65
+ * **Three or more FEs.** The two FEs with the most levels form a block
66
+ solved by the two-FE routine (warm-started) inside an outer
67
+ Gauss-Seidel over the others, accelerated with Irons-Tuck. The inner
68
+ tolerance starts loose and follows the outer change down.
69
+ * **Stopping rule.** Stop when the bound on the change of the residual
70
+ between two sweeps is at most `tol_abs + tol_rel * max|x|`, the rule of
71
+ the NumPy reference in `statspai.fast.demean`.
72
+ * **IRLS.** The FE structure is built once per `fepois` call; each
73
+ iteration re-weights it and restarts every column from its FE
74
+ coefficients of the previous iteration.
75
+
76
+ Columns are processed in parallel. The cluster meat sums fixed blocks of
77
+ clusters in parallel and adds the block sums in order, so its result is
78
+ the same at any thread count; the demeaning is per column and therefore
79
+ also independent of the thread count.
80
+
81
+ ## Safety
82
+
83
+ Every entry point validates lengths, contiguity and code ranges before
84
+ running and raises `ValueError` on bad input. The crate is built with
85
+ `panic = "unwind"`, so an unexpected panic surfaces as a Python exception
86
+ instead of aborting the interpreter. The kernels run on their own thread
87
+ pool (never Rayon's global one), which is rebuilt in a forked child.
88
+
89
+ ## Build from source
90
+
91
+ ```bash
92
+ pip install maturin
93
+ cd rust/statspai_hdfe
94
+ maturin develop --release # into the active environment
95
+ maturin build --release # a wheel in target/wheels
96
+ PYO3_PYTHON=python cargo test --release
97
+ ```
98
+
99
+ Wheels are abi3: one per platform serves every CPython >= 3.10. The
100
+ `rust-hdfe` GitHub workflow tests the crate on Linux, macOS and Windows,
101
+ builds wheels on demand, and publishes on an `hdfe-v*` tag.
102
+
103
+ ## Kernel API
104
+
105
+ `statspai_hdfe.API_VERSION` names the interface level; StatsPAI ignores
106
+ an extension older than the level it was written against
107
+ (`statspai._rust_backend.MIN_API_VERSION`) and warns once.
108
+
109
+ | Level | Crate | Change |
110
+ | --- | --- | --- |
111
+ | 1 | 0.1 – 0.7 | data-space alternating projections; inputs not validated |
112
+ | 2 | 0.8 | coefficient-space solver; validated inputs; `fepois_irls` reports `singular` / `fe_converged`; thread-pool control; reproducible `cluster_meat` |
113
+
114
+ ## License
115
+
116
+ MIT.
117
+
@@ -0,0 +1,99 @@
1
+ # statspai-hdfe
2
+
3
+ Rust kernels for [StatsPAI](https://github.com/brycewang-stanford/StatsPAI):
4
+ high-dimensional fixed-effect (HDFE) demeaning, the native Poisson IRLS
5
+ behind `sp.fast.fepois`, and the cluster-robust sandwich meat.
6
+
7
+ ```bash
8
+ pip install "statspai[rust]" # or: pip install statspai-hdfe
9
+ ```
10
+
11
+ StatsPAI never requires this package. Without it the same functions run
12
+ on NumPy / Numba and agree with the Rust path to within the
13
+ residualisation tolerance. Check which one is in use:
14
+
15
+ ```python
16
+ import statspai as sp
17
+ sp.fast.backend_info() # {'available': True, 'version': '0.8.0', 'threads': 8, ...}
18
+ sp.fast.set_num_threads(4) # or set STATSPAI_NUM_THREADS before starting Python
19
+ ```
20
+
21
+ `STATSPAI_SKIP_RUST=1` makes StatsPAI ignore an installed extension.
22
+
23
+ ## What uses it
24
+
25
+ | StatsPAI entry point | Kernel |
26
+ | --- | --- |
27
+ | `sp.fast.feols`, `sp.fast.demean`, `sp.fast.within`, `sp.fast.event_study` | `demean_2d` |
28
+ | `sp.fast.feols(weights=)`, `sp.ppmlhdfe` (two or more FEs) | `demean_2d_weighted` |
29
+ | `sp.fast.fepois` | `fepois_irls`, `separation_mask` |
30
+ | `sp.iv(absorb=)`, `sp.didregress`, `sp.iv.iv_diag`, weak-IV diagnostics | `demean_2d` |
31
+ | `sp.regress(cluster=)` (one-way) | `cluster_meat` |
32
+ | singleton pruning in `sp.fast` | `singleton_mask` |
33
+
34
+ `sp.hdfe_ols` / `sp.Absorber`, `sp.panel` and the pyfixest-backed
35
+ `sp.feols` do not use it.
36
+
37
+ ## Algorithm
38
+
39
+ The fixed-effect projection is solved in the space of FE coefficients,
40
+ as `fixest` does (Bergé 2018; `paper.bib` key `berge2018efficient`):
41
+
42
+ * **Two FEs.** One Gauss-Seidel sweep needs only the group sums of the
43
+ column and the sparse cross-tabulation of the two FEs, so it costs
44
+ `O(nnz)` instead of `O(n)`; on panels where a unit meets few levels of
45
+ the other FE (worker x firm, firm x year) that is a small fraction of
46
+ the rows. The iteration runs on the coefficients of the smaller FE and
47
+ is accelerated with Irons-Tuck at every step.
48
+ * **Three or more FEs.** The two FEs with the most levels form a block
49
+ solved by the two-FE routine (warm-started) inside an outer
50
+ Gauss-Seidel over the others, accelerated with Irons-Tuck. The inner
51
+ tolerance starts loose and follows the outer change down.
52
+ * **Stopping rule.** Stop when the bound on the change of the residual
53
+ between two sweeps is at most `tol_abs + tol_rel * max|x|`, the rule of
54
+ the NumPy reference in `statspai.fast.demean`.
55
+ * **IRLS.** The FE structure is built once per `fepois` call; each
56
+ iteration re-weights it and restarts every column from its FE
57
+ coefficients of the previous iteration.
58
+
59
+ Columns are processed in parallel. The cluster meat sums fixed blocks of
60
+ clusters in parallel and adds the block sums in order, so its result is
61
+ the same at any thread count; the demeaning is per column and therefore
62
+ also independent of the thread count.
63
+
64
+ ## Safety
65
+
66
+ Every entry point validates lengths, contiguity and code ranges before
67
+ running and raises `ValueError` on bad input. The crate is built with
68
+ `panic = "unwind"`, so an unexpected panic surfaces as a Python exception
69
+ instead of aborting the interpreter. The kernels run on their own thread
70
+ pool (never Rayon's global one), which is rebuilt in a forked child.
71
+
72
+ ## Build from source
73
+
74
+ ```bash
75
+ pip install maturin
76
+ cd rust/statspai_hdfe
77
+ maturin develop --release # into the active environment
78
+ maturin build --release # a wheel in target/wheels
79
+ PYO3_PYTHON=python cargo test --release
80
+ ```
81
+
82
+ Wheels are abi3: one per platform serves every CPython >= 3.10. The
83
+ `rust-hdfe` GitHub workflow tests the crate on Linux, macOS and Windows,
84
+ builds wheels on demand, and publishes on an `hdfe-v*` tag.
85
+
86
+ ## Kernel API
87
+
88
+ `statspai_hdfe.API_VERSION` names the interface level; StatsPAI ignores
89
+ an extension older than the level it was written against
90
+ (`statspai._rust_backend.MIN_API_VERSION`) and warns once.
91
+
92
+ | Level | Crate | Change |
93
+ | --- | --- | --- |
94
+ | 1 | 0.1 – 0.7 | data-space alternating projections; inputs not validated |
95
+ | 2 | 0.8 | coefficient-space solver; validated inputs; `fepois_irls` reports `singular` / `fe_converged`; thread-pool control; reproducible `cluster_meat` |
96
+
97
+ ## License
98
+
99
+ MIT.
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.5,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "statspai-hdfe"
7
+ description = "Rust kernels for StatsPAI: HDFE demeaning, Poisson IRLS, cluster-robust meat"
8
+ readme = "README.md"
9
+ license = { text = "MIT" }
10
+ authors = [{ name = "Biaoyue Wang", email = "brycew6m@stanford.edu" }]
11
+ requires-python = ">=3.10"
12
+ dependencies = ["numpy>=1.20"]
13
+ dynamic = ["version"]
14
+ classifiers = [
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Rust",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: Implementation :: CPython",
19
+ "Topic :: Scientific/Engineering :: Mathematics",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/brycewang-stanford/StatsPAI"
24
+ Source = "https://github.com/brycewang-stanford/StatsPAI/tree/main/rust/statspai_hdfe"
25
+
26
+ [tool.maturin]
27
+ module-name = "statspai_hdfe"
28
+ features = ["extension-module"]