rcrpy 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.
Files changed (53) hide show
  1. rcrpy-0.1.0/.gitignore +162 -0
  2. rcrpy-0.1.0/LICENSE +48 -0
  3. rcrpy-0.1.0/PKG-INFO +247 -0
  4. rcrpy-0.1.0/README.md +171 -0
  5. rcrpy-0.1.0/benchmarks/README.md +26 -0
  6. rcrpy-0.1.0/benchmarks/bench_port_vs_oracle.py +46 -0
  7. rcrpy-0.1.0/benchmarks/compare_full_rcr.py +385 -0
  8. rcrpy-0.1.0/benchmarks/diagnostics.py +181 -0
  9. rcrpy-0.1.0/benchmarks/diagnostics_functional.py +171 -0
  10. rcrpy-0.1.0/benchmarks/es_mode_dl_truth_test.py +165 -0
  11. rcrpy-0.1.0/benchmarks/extreme_contamination_test.py +196 -0
  12. rcrpy-0.1.0/benchmarks/optimizer_quality_test.py +172 -0
  13. rcrpy-0.1.0/benchmarks/port_coverage_test.py +363 -0
  14. rcrpy-0.1.0/benchmarks/reflect_parity.py +170 -0
  15. rcrpy-0.1.0/benchmarks/sampling_regime_test.py +102 -0
  16. rcrpy-0.1.0/docs/PUBLISHING.md +109 -0
  17. rcrpy-0.1.0/docs/README.md +33 -0
  18. rcrpy-0.1.0/docs/advanced_functional.md +160 -0
  19. rcrpy-0.1.0/docs/full_rcr_handoff_explainer.txt +281 -0
  20. rcrpy-0.1.0/docs/functional_form.md +138 -0
  21. rcrpy-0.1.0/docs/migration_from_legacy_rcr.md +135 -0
  22. rcrpy-0.1.0/docs/single_value.md +91 -0
  23. rcrpy-0.1.0/full_rcr.py +4009 -0
  24. rcrpy-0.1.0/pyproject.toml +53 -0
  25. rcrpy-0.1.0/scripts/build_single_file.py +192 -0
  26. rcrpy-0.1.0/scripts/extract_unity_tables.py +163 -0
  27. rcrpy-0.1.0/scripts/post_install_check.py +53 -0
  28. rcrpy-0.1.0/scripts/smoke_test_full_rcr.py +80 -0
  29. rcrpy-0.1.0/src/rcrpy/__init__.py +24 -0
  30. rcrpy-0.1.0/src/rcrpy/api.py +169 -0
  31. rcrpy-0.1.0/src/rcrpy/functional.py +1078 -0
  32. rcrpy-0.1.0/src/rcrpy/nonparametric.py +46 -0
  33. rcrpy-0.1.0/src/rcrpy/rejection.py +1714 -0
  34. rcrpy-0.1.0/src/rcrpy/stats.py +650 -0
  35. rcrpy-0.1.0/src/rcrpy/tables.py +382 -0
  36. rcrpy-0.1.0/tests/__init__.py +0 -0
  37. rcrpy-0.1.0/tests/conftest.py +52 -0
  38. rcrpy-0.1.0/tests/test_functional.py +345 -0
  39. rcrpy-0.1.0/tests/test_functional_parity_extended.py +315 -0
  40. rcrpy-0.1.0/tests/test_functional_parity_sweep.py +149 -0
  41. rcrpy-0.1.0/tests/test_functional_rigorous.py +231 -0
  42. rcrpy-0.1.0/tests/test_nonparametric.py +86 -0
  43. rcrpy-0.1.0/tests/test_parity_bulk_all_techs.py +83 -0
  44. rcrpy-0.1.0/tests/test_parity_bulk_lsmode68.py +72 -0
  45. rcrpy-0.1.0/tests/test_parity_bulk_weighted.py +49 -0
  46. rcrpy-0.1.0/tests/test_parity_es_mode_dl.py +94 -0
  47. rcrpy-0.1.0/tests/test_parity_lsmode68.py +83 -0
  48. rcrpy-0.1.0/tests/test_parity_lsmode68_weighted.py +58 -0
  49. rcrpy-0.1.0/tests/test_parity_lsmode_dl.py +98 -0
  50. rcrpy-0.1.0/tests/test_parity_ss_median_dl.py +93 -0
  51. rcrpy-0.1.0/tests/test_smoke.py +44 -0
  52. rcrpy-0.1.0/tests/test_tables.py +77 -0
  53. rcrpy-0.1.0/uv.lock +247 -0
rcrpy-0.1.0/.gitignore ADDED
@@ -0,0 +1,162 @@
1
+ # ============================================================================
2
+ # Python
3
+ # ============================================================================
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ *.pyo
8
+ *.pyd
9
+
10
+ # Distribution / packaging
11
+ build/
12
+ dist/
13
+ wheels/
14
+ *.egg
15
+ *.egg-info/
16
+ *.whl
17
+ MANIFEST
18
+
19
+ # Installer logs
20
+ pip-log.txt
21
+ pip-delete-this-directory.txt
22
+
23
+ # Unit test / coverage
24
+ .pytest_cache/
25
+ .tox/
26
+ .nox/
27
+ .coverage
28
+ .coverage.*
29
+ htmlcov/
30
+ .cache
31
+ nosetests.xml
32
+ coverage.xml
33
+ *.cover
34
+ .hypothesis/
35
+
36
+ # Type checkers / linters
37
+ .mypy_cache/
38
+ .dmypy.json
39
+ dmypy.json
40
+ .pyre/
41
+ .ruff_cache/
42
+ .pytype/
43
+
44
+ # Jupyter
45
+ .ipynb_checkpoints/
46
+ *.ipynb_checkpoints
47
+
48
+ # pyenv
49
+ .python-version
50
+
51
+ # Sphinx documentation
52
+ docs/_build/
53
+
54
+ # ============================================================================
55
+ # Virtual environments (project-specific names + the standard ones)
56
+ # ============================================================================
57
+ .venv/
58
+ .venv-*/
59
+ venv/
60
+ env/
61
+ ENV/
62
+ env.bak/
63
+ venv.bak/
64
+
65
+ # ============================================================================
66
+ # C++ build artifacts
67
+ # ============================================================================
68
+ *.o
69
+ *.obj
70
+ *.a
71
+ *.lib
72
+ *.so
73
+ *.so.*
74
+ *.dylib
75
+ *.dll
76
+ *.exe
77
+ *.out
78
+ *.app
79
+
80
+ # CMake
81
+ CMakeCache.txt
82
+ CMakeFiles/
83
+ CMakeScripts/
84
+ cmake_install.cmake
85
+ install_manifest.txt
86
+ CTestTestfile.cmake
87
+ _deps/
88
+
89
+ # Build directories
90
+ build/
91
+ build-*/
92
+ cmake-build-*/
93
+
94
+ # ============================================================================
95
+ # IDE / editor state
96
+ # ============================================================================
97
+ .vscode/
98
+ .idea/
99
+ *.swp
100
+ *.swo
101
+ *~
102
+ .project
103
+ .settings/
104
+ .classpath
105
+
106
+ # JetBrains
107
+ *.iml
108
+ .idea_modules/
109
+
110
+ # ============================================================================
111
+ # OS junk
112
+ # ============================================================================
113
+ .DS_Store
114
+ .DS_Store?
115
+ ._*
116
+ .Spotlight-V100
117
+ .Trashes
118
+ ehthumbs.db
119
+ Thumbs.db
120
+ desktop.ini
121
+ $RECYCLE.BIN/
122
+
123
+ # ============================================================================
124
+ # Claude / agent local state (conversation transcripts live here on the user's
125
+ # machine; do not commit even by accident if a .claude/ ends up in the repo).
126
+ # ============================================================================
127
+ .claude/
128
+ .aider*
129
+ CLAUDE.md.local
130
+
131
+ # ============================================================================
132
+ # Logs and temporary files
133
+ # ============================================================================
134
+ *.log
135
+ *.tmp
136
+ *.bak
137
+ *.orig
138
+ *.rej
139
+
140
+ # ============================================================================
141
+ # Local secrets / credentials (defense in depth — nothing should ever be here)
142
+ # ============================================================================
143
+ .env
144
+ .env.local
145
+ .env.*.local
146
+ *.pem
147
+ *.key
148
+ secrets/
149
+
150
+ # ============================================================================
151
+ # Project-specific
152
+ # ============================================================================
153
+ # Compiled rcr extension lands here after pip install -e cpp/
154
+ cpp/rcr.*.pyd
155
+ cpp/rcr.*.so
156
+ cpp/build/
157
+
158
+ # Background-task transcripts (Claude Code writes these locally during sessions)
159
+ *.output
160
+
161
+ # Editor swap files in nested dirs
162
+ **/*~
rcrpy-0.1.0/LICENSE ADDED
@@ -0,0 +1,48 @@
1
+ COPYRIGHT AND PERMISSION NOTICE
2
+ UNC Software "Robust Chauvenet Outlier Rejection" (Ref No. 17-0086)
3
+ Copyright (C) 2020 The University of North Carolina at Chapel Hill
4
+ All rights reserved.
5
+
6
+ The University of North Carolina at Chapel Hill ("UNC") and the developers ("Developers") of Robust
7
+ Chauvenet Outlier Rejection software ("Software") give recipient ("Recipient") and Recipient's Institution
8
+ ("Institution") permission to use, copy and redistribute the software in source and binary forms, with or without
9
+ modification for non-commercial purposes only provided that the following conditions are met:
10
+
11
+ 1) All copies and redistributions of Software in binary form and/or source code, related documentation
12
+ and/or other materials provided with the Software must reproduce and retain the above copyright notice,
13
+ this list of conditions and the following disclaimer.
14
+
15
+ 2) Recipient will provide the Developers with feedback on the use of the Software in their research. The
16
+ Developers and UNC are permitted to use any information Recipient provides in making changes to the
17
+ Software. All feedback, bug reports and technical questions shall be sent to: Prof. Daniel Reichart
18
+ (reichart@email.unc.edu).
19
+
20
+ 3) Recipient acknowledges that the Developers, UNC and its licensees may develop modifications to
21
+ Software that may be substantially similar to Recipient's modifications of Software, and that the
22
+ Developers, UNC and its licensees shall not be constrained in any way by Recipient in UNC's or its
23
+ licensees' use or management of such modifications. Recipient acknowledges the right of the
24
+ Developers and UNC to prepare and publish modifications to Software that may be substantially similar
25
+ or functionally equivalent to your modifications and improvements, and if Recipient or Institution
26
+ obtains patent protection for any modification or improvement to Software, Recipient and Institution
27
+ agree not to allege or enjoin infringement of their patent by the Developers, UNC or any of UNC's
28
+ licensees obtaining modifications or improvements to Software from the UNC or the Developers.
29
+
30
+ 4) Recipient and Developer will acknowledge in their respective publications the contributions made to
31
+ each other's research involving or based on the Software. The current citations for Software are:
32
+ M. P. Maples, D. E. Reichart, N. C. Konz, T. A. Berger, A. S. Trotter, J. R. Martin, D. A. Dutton, M. L.
33
+ Paggen, R. E. Joyner, C. P. Salemi, 2018 ApJS, 238.1, p. 2
34
+
35
+ 5) Any party desiring a license to use the Software for commercial purposes shall contact UNC's Office of
36
+ Commercialization and Economic Development at 919-966-3929 or oced@unc.edu.
37
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, CONTRIBUTORS, AND THE
38
+ UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL "AS IS" AND ANY EXPRESS OR IMPLIED
39
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
40
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
41
+ EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY OF NORTH
42
+ CAROLINA AT CHAPEL HILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
43
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
44
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
45
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
46
+ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
47
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
48
+ POSSIBILITY OF SUCH DAMAGE.
rcrpy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: rcrpy
3
+ Version: 0.1.0
4
+ Summary: Robust Chauvenet Rejection (RCR), Python reimplementation
5
+ Project-URL: Homepage, https://github.com/nickk124/RCR
6
+ Project-URL: Repository, https://github.com/nickk124/RCR
7
+ Project-URL: Issues, https://github.com/nickk124/RCR/issues
8
+ Project-URL: Paper, https://arxiv.org/abs/1807.05276
9
+ Author-email: Haislip <haislip@physics.unc.edu>
10
+ Maintainer-email: UNC Physics RCR successors <haislip@physics.unc.edu>
11
+ License: COPYRIGHT AND PERMISSION NOTICE
12
+ UNC Software "Robust Chauvenet Outlier Rejection" (Ref No. 17-0086)
13
+ Copyright (C) 2020 The University of North Carolina at Chapel Hill
14
+ All rights reserved.
15
+
16
+ The University of North Carolina at Chapel Hill ("UNC") and the developers ("Developers") of Robust
17
+ Chauvenet Outlier Rejection software ("Software") give recipient ("Recipient") and Recipient's Institution
18
+ ("Institution") permission to use, copy and redistribute the software in source and binary forms, with or without
19
+ modification for non-commercial purposes only provided that the following conditions are met:
20
+
21
+ 1) All copies and redistributions of Software in binary form and/or source code, related documentation
22
+ and/or other materials provided with the Software must reproduce and retain the above copyright notice,
23
+ this list of conditions and the following disclaimer.
24
+
25
+ 2) Recipient will provide the Developers with feedback on the use of the Software in their research. The
26
+ Developers and UNC are permitted to use any information Recipient provides in making changes to the
27
+ Software. All feedback, bug reports and technical questions shall be sent to: Prof. Daniel Reichart
28
+ (reichart@email.unc.edu).
29
+
30
+ 3) Recipient acknowledges that the Developers, UNC and its licensees may develop modifications to
31
+ Software that may be substantially similar to Recipient's modifications of Software, and that the
32
+ Developers, UNC and its licensees shall not be constrained in any way by Recipient in UNC's or its
33
+ licensees' use or management of such modifications. Recipient acknowledges the right of the
34
+ Developers and UNC to prepare and publish modifications to Software that may be substantially similar
35
+ or functionally equivalent to your modifications and improvements, and if Recipient or Institution
36
+ obtains patent protection for any modification or improvement to Software, Recipient and Institution
37
+ agree not to allege or enjoin infringement of their patent by the Developers, UNC or any of UNC's
38
+ licensees obtaining modifications or improvements to Software from the UNC or the Developers.
39
+
40
+ 4) Recipient and Developer will acknowledge in their respective publications the contributions made to
41
+ each other's research involving or based on the Software. The current citations for Software are:
42
+ M. P. Maples, D. E. Reichart, N. C. Konz, T. A. Berger, A. S. Trotter, J. R. Martin, D. A. Dutton, M. L.
43
+ Paggen, R. E. Joyner, C. P. Salemi, 2018 ApJS, 238.1, p. 2
44
+
45
+ 5) Any party desiring a license to use the Software for commercial purposes shall contact UNC's Office of
46
+ Commercialization and Economic Development at 919-966-3929 or oced@unc.edu.
47
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, CONTRIBUTORS, AND THE
48
+ UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL "AS IS" AND ANY EXPRESS OR IMPLIED
49
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
50
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
51
+ EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY OF NORTH
52
+ CAROLINA AT CHAPEL HILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
53
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
54
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
55
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
56
+ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
57
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
58
+ POSSIBILITY OF SUCH DAMAGE.
59
+ License-File: LICENSE
60
+ Keywords: astronomy,chauvenet,outlier-rejection,robust-statistics,statistics
61
+ Classifier: Development Status :: 4 - Beta
62
+ Classifier: Intended Audience :: Science/Research
63
+ Classifier: License :: Other/Proprietary License
64
+ Classifier: Operating System :: OS Independent
65
+ Classifier: Programming Language :: Python :: 3
66
+ Classifier: Programming Language :: Python :: 3.11
67
+ Classifier: Programming Language :: Python :: 3.12
68
+ Classifier: Programming Language :: Python :: 3.13
69
+ Classifier: Topic :: Scientific/Engineering
70
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
71
+ Classifier: Topic :: Scientific/Engineering :: Physics
72
+ Requires-Python: >=3.11
73
+ Requires-Dist: numpy>=1.26
74
+ Requires-Dist: scipy>=1.11
75
+ Description-Content-Type: text/markdown
76
+
77
+ # rcrpy
78
+
79
+ Python reimplementation of Robust Chauvenet Rejection (RCR) — statistical
80
+ outlier rejection that stays accurate on samples that are >85% contaminated.
81
+
82
+ Based on the algorithm in
83
+ [Maples et al. 2018, ApJS 238.1](https://arxiv.org/abs/1807.05276).
84
+ This package is a numpy / scipy port of the original C++/pybind11
85
+ implementation at [`../cpp/`](../cpp/), preserving algorithmic parity
86
+ while running pure Python — no compiler toolchain required.
87
+
88
+ ## Install
89
+
90
+ ```bash
91
+ pip install rcrpy
92
+ ```
93
+
94
+ Runtime deps: `numpy>=1.26`, `scipy>=1.11`. Python ≥3.11.
95
+
96
+ ## Quick start
97
+
98
+ ### Single-value RCR
99
+
100
+ Reject outliers from a 1-D dataset and recover the underlying central
101
+ value and width.
102
+
103
+ ```python
104
+ import rcrpy
105
+ import numpy as np
106
+
107
+ # Heavily contaminated data: mu=0, sigma=1, plus one-sided outliers
108
+ rng = np.random.default_rng(42)
109
+ y = np.concatenate([
110
+ rng.normal(0, 1, size=150), # clean
111
+ np.abs(rng.normal(0, 10, size=850)), # 85% contamination
112
+ ])
113
+
114
+ r = rcrpy.RCR(rcrpy.RejectionTech.LS_MODE_68)
115
+ r.perform_rejection(y.tolist())
116
+
117
+ print(f"Recovered mu = {r.result.mu:.3f} (truth: 0)")
118
+ print(f"Recovered sigma = {r.result.sigma:.3f} (truth: 1)")
119
+ print(f"Points kept = {r.result.flags.sum()} / {len(y)}")
120
+ ```
121
+
122
+ ### Functional-form RCR (linear model fit with outlier rejection)
123
+
124
+ Fit a parametric model to (x, y) data while rejecting outliers.
125
+
126
+ ```python
127
+ import rcrpy
128
+ import numpy as np
129
+
130
+ # Linear data with outliers
131
+ rng = np.random.default_rng(0)
132
+ x = np.linspace(-5, 5, 100)
133
+ y = 2.0 + 1.5 * x + rng.normal(0, 0.3, size=x.size)
134
+ # Inject 20 outliers
135
+ out = rng.choice(x.size, size=20, replace=False)
136
+ y[out] += rng.normal(15, 5, size=20)
137
+
138
+ def linear(x, params):
139
+ return params[0] + params[1] * x
140
+
141
+ def d_linear_b(x, params):
142
+ return 1.0
143
+
144
+ def d_linear_m(x, params):
145
+ return x
146
+
147
+ model = rcrpy.FunctionalForm(
148
+ linear, x, y, [d_linear_b, d_linear_m], guess=[0.0, 0.0],
149
+ )
150
+ r = rcrpy.RCR(rcrpy.RejectionTech.LS_MODE_68)
151
+ r.set_parametric_model(model)
152
+ r.perform_rejection(y.tolist())
153
+
154
+ b, m = model.result.parameters
155
+ print(f"Recovered b = {b:.3f} (truth: 2.0)")
156
+ print(f"Recovered m = {m:.3f} (truth: 1.5)")
157
+ print(f"Kept {int(r.result.flags.sum())} / {len(y)} points")
158
+ ```
159
+
160
+ ## Rejection techniques
161
+
162
+ | `RejectionTech.*` | Use case |
163
+ |---|---|
164
+ | `LS_MODE_68` | Symmetric uncontaminated distribution, one-sided contaminants |
165
+ | `LS_MODE_DL` | Mixed one-sided + two-sided contaminants |
166
+ | `SS_MEDIAN_DL` | Symmetric uncontaminated distribution, two-sided contaminants |
167
+ | `ES_MODE_DL` | Mildly asymmetric uncontaminated distribution and/or very small N |
168
+
169
+ > ⚠️ **Known algorithm bug**: `ES_MODE_DL` combined with `FunctionalForm`
170
+ > (parametric models) exhibits a runaway-rejection cascade that drops
171
+ > ~75% of inliers even on perfectly clean data. **This is an algorithm bug
172
+ > inherited from the C++ source** — the C++ oracle exhibits the same
173
+ > behavior; it is not a port deficiency. Empirical characterization is in
174
+ > [`benchmarks/es_mode_dl_truth_test.py`](benchmarks/es_mode_dl_truth_test.py).
175
+ > All other combinations of rejection technique × model type work
176
+ > correctly. **For functional-form fits, use one of `LS_MODE_68`,
177
+ > `LS_MODE_DL`, or `SS_MEDIAN_DL`.** `ES_MODE_DL` remains correct for
178
+ > single-value (non-parametric) rejection.
179
+
180
+ See [agents/python_vs_rust_plan.md](../agents/python_vs_rust_plan.md) and
181
+ [Maples et al. 2018](https://arxiv.org/abs/1807.05276) for the science.
182
+
183
+ ## More invocations
184
+
185
+ | Want… | Call |
186
+ |---|---|
187
+ | Bulk rejection (faster on large N) | `r.perform_bulk_rejection(y)` |
188
+ | Weighted RCR | `r.perform_rejection(y, w=weights)` |
189
+ | User-defined "candidate" mu | subclass `rcrpy.NonParametric`, `r.set_non_parametric_model(model)` |
190
+ | Add Gaussian / bounded priors | `rcrpy.Priors(prior_type=..., gaussian_params=...)` → pass via `priors=` to `FunctionalForm` |
191
+ | Pivot search for power-law / exponential fits | pass `pivot_function=...` and `pivot_guess=...` to `FunctionalForm` |
192
+ | ND independent variable | pass `xdata` as `(N, D)` array; user functions take vector `x` |
193
+
194
+ ## Status
195
+
196
+ | Slice | Status |
197
+ |---|---|
198
+ | Phase 1 single-value RCR (4 techs × iter/bulk × weighted/unweighted) | ✅ 100%; bit-identical to C++ oracle at rtol=1e-12 (worst case 1.7e-15 = 8 ULPs) |
199
+ | Phase 2 functional form (1D + ND, priors, pivot search, paramuncertainty) | ✅ 99%; parity with C++ at rtol=5% on 6 sweep scenarios (RNG-limited) |
200
+ | Packaging / CI / docs | 🚧 0.1.0 release; collecting real-user feedback before 1.0 |
201
+
202
+ 85 tests passing (+ 1 documented xfail). Reproduce parity with
203
+ [`benchmarks/reflect_parity.py`](benchmarks/reflect_parity.py); benchmark
204
+ with [`benchmarks/diagnostics.py`](benchmarks/diagnostics.py) and
205
+ [`benchmarks/diagnostics_functional.py`](benchmarks/diagnostics_functional.py).
206
+
207
+ ## Layout
208
+
209
+ ```
210
+ python/
211
+ ├── pyproject.toml
212
+ ├── README.md (you are here)
213
+ ├── LICENSE (mirrors ../cpp/LICENSE — academic / non-commercial)
214
+ ├── src/rcrpy/
215
+ │ ├── __init__.py public API re-exports
216
+ │ ├── api.py RCR class, RCRResults, RejectionTech, MuType
217
+ │ ├── stats.py median, halfSampleMode, robust-sigma helpers
218
+ │ ├── rejection.py iterative + bulk loops (lower/single/each sigma)
219
+ │ ├── tables.py unity tables ported verbatim from cpp/src/RCR.cpp
220
+ │ ├── nonparametric.py NonParametric base class
221
+ │ └── functional.py FunctionalForm + Priors for parametric fits
222
+ ├── tests/ pytest suite (85 + 1 xfailed)
223
+ ├── docs/ markdown tutorials + PUBLISHING checklist
224
+ ├── benchmarks/ parity + perf scripts (not part of the wheel)
225
+ └── scripts/ one-off codegen + post-install smoke check
226
+ ```
227
+
228
+ ## Citing
229
+
230
+ If you use `rcrpy` in academic work, please cite the original paper:
231
+
232
+ ```bibtex
233
+ @article{maples2018robust,
234
+ title = {Robust Chauvenet Outlier Rejection},
235
+ author = {Maples, M.P. and Reichart, D.E. and Konz, N.C. and Berger, T.A.
236
+ and Trotter, A.S. and Martin, J.R. and Dutton, D.A.
237
+ and Paggen, M.L. and Joyner, R.E. and Salemi, C.P.},
238
+ journal = {The Astrophysical Journal Supplement Series},
239
+ volume = {238}, number = {1}, pages = {2}, year = {2018},
240
+ }
241
+ ```
242
+
243
+ ## License
244
+
245
+ See [LICENSE](LICENSE) — mirrors the upstream C++ license. Free for
246
+ academic and non-commercial use; contact UNC's Office of
247
+ Commercialization and Economic Development for commercial licensing.
rcrpy-0.1.0/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # rcrpy
2
+
3
+ Python reimplementation of Robust Chauvenet Rejection (RCR) — statistical
4
+ outlier rejection that stays accurate on samples that are >85% contaminated.
5
+
6
+ Based on the algorithm in
7
+ [Maples et al. 2018, ApJS 238.1](https://arxiv.org/abs/1807.05276).
8
+ This package is a numpy / scipy port of the original C++/pybind11
9
+ implementation at [`../cpp/`](../cpp/), preserving algorithmic parity
10
+ while running pure Python — no compiler toolchain required.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install rcrpy
16
+ ```
17
+
18
+ Runtime deps: `numpy>=1.26`, `scipy>=1.11`. Python ≥3.11.
19
+
20
+ ## Quick start
21
+
22
+ ### Single-value RCR
23
+
24
+ Reject outliers from a 1-D dataset and recover the underlying central
25
+ value and width.
26
+
27
+ ```python
28
+ import rcrpy
29
+ import numpy as np
30
+
31
+ # Heavily contaminated data: mu=0, sigma=1, plus one-sided outliers
32
+ rng = np.random.default_rng(42)
33
+ y = np.concatenate([
34
+ rng.normal(0, 1, size=150), # clean
35
+ np.abs(rng.normal(0, 10, size=850)), # 85% contamination
36
+ ])
37
+
38
+ r = rcrpy.RCR(rcrpy.RejectionTech.LS_MODE_68)
39
+ r.perform_rejection(y.tolist())
40
+
41
+ print(f"Recovered mu = {r.result.mu:.3f} (truth: 0)")
42
+ print(f"Recovered sigma = {r.result.sigma:.3f} (truth: 1)")
43
+ print(f"Points kept = {r.result.flags.sum()} / {len(y)}")
44
+ ```
45
+
46
+ ### Functional-form RCR (linear model fit with outlier rejection)
47
+
48
+ Fit a parametric model to (x, y) data while rejecting outliers.
49
+
50
+ ```python
51
+ import rcrpy
52
+ import numpy as np
53
+
54
+ # Linear data with outliers
55
+ rng = np.random.default_rng(0)
56
+ x = np.linspace(-5, 5, 100)
57
+ y = 2.0 + 1.5 * x + rng.normal(0, 0.3, size=x.size)
58
+ # Inject 20 outliers
59
+ out = rng.choice(x.size, size=20, replace=False)
60
+ y[out] += rng.normal(15, 5, size=20)
61
+
62
+ def linear(x, params):
63
+ return params[0] + params[1] * x
64
+
65
+ def d_linear_b(x, params):
66
+ return 1.0
67
+
68
+ def d_linear_m(x, params):
69
+ return x
70
+
71
+ model = rcrpy.FunctionalForm(
72
+ linear, x, y, [d_linear_b, d_linear_m], guess=[0.0, 0.0],
73
+ )
74
+ r = rcrpy.RCR(rcrpy.RejectionTech.LS_MODE_68)
75
+ r.set_parametric_model(model)
76
+ r.perform_rejection(y.tolist())
77
+
78
+ b, m = model.result.parameters
79
+ print(f"Recovered b = {b:.3f} (truth: 2.0)")
80
+ print(f"Recovered m = {m:.3f} (truth: 1.5)")
81
+ print(f"Kept {int(r.result.flags.sum())} / {len(y)} points")
82
+ ```
83
+
84
+ ## Rejection techniques
85
+
86
+ | `RejectionTech.*` | Use case |
87
+ |---|---|
88
+ | `LS_MODE_68` | Symmetric uncontaminated distribution, one-sided contaminants |
89
+ | `LS_MODE_DL` | Mixed one-sided + two-sided contaminants |
90
+ | `SS_MEDIAN_DL` | Symmetric uncontaminated distribution, two-sided contaminants |
91
+ | `ES_MODE_DL` | Mildly asymmetric uncontaminated distribution and/or very small N |
92
+
93
+ > ⚠️ **Known algorithm bug**: `ES_MODE_DL` combined with `FunctionalForm`
94
+ > (parametric models) exhibits a runaway-rejection cascade that drops
95
+ > ~75% of inliers even on perfectly clean data. **This is an algorithm bug
96
+ > inherited from the C++ source** — the C++ oracle exhibits the same
97
+ > behavior; it is not a port deficiency. Empirical characterization is in
98
+ > [`benchmarks/es_mode_dl_truth_test.py`](benchmarks/es_mode_dl_truth_test.py).
99
+ > All other combinations of rejection technique × model type work
100
+ > correctly. **For functional-form fits, use one of `LS_MODE_68`,
101
+ > `LS_MODE_DL`, or `SS_MEDIAN_DL`.** `ES_MODE_DL` remains correct for
102
+ > single-value (non-parametric) rejection.
103
+
104
+ See [agents/python_vs_rust_plan.md](../agents/python_vs_rust_plan.md) and
105
+ [Maples et al. 2018](https://arxiv.org/abs/1807.05276) for the science.
106
+
107
+ ## More invocations
108
+
109
+ | Want… | Call |
110
+ |---|---|
111
+ | Bulk rejection (faster on large N) | `r.perform_bulk_rejection(y)` |
112
+ | Weighted RCR | `r.perform_rejection(y, w=weights)` |
113
+ | User-defined "candidate" mu | subclass `rcrpy.NonParametric`, `r.set_non_parametric_model(model)` |
114
+ | Add Gaussian / bounded priors | `rcrpy.Priors(prior_type=..., gaussian_params=...)` → pass via `priors=` to `FunctionalForm` |
115
+ | Pivot search for power-law / exponential fits | pass `pivot_function=...` and `pivot_guess=...` to `FunctionalForm` |
116
+ | ND independent variable | pass `xdata` as `(N, D)` array; user functions take vector `x` |
117
+
118
+ ## Status
119
+
120
+ | Slice | Status |
121
+ |---|---|
122
+ | Phase 1 single-value RCR (4 techs × iter/bulk × weighted/unweighted) | ✅ 100%; bit-identical to C++ oracle at rtol=1e-12 (worst case 1.7e-15 = 8 ULPs) |
123
+ | Phase 2 functional form (1D + ND, priors, pivot search, paramuncertainty) | ✅ 99%; parity with C++ at rtol=5% on 6 sweep scenarios (RNG-limited) |
124
+ | Packaging / CI / docs | 🚧 0.1.0 release; collecting real-user feedback before 1.0 |
125
+
126
+ 85 tests passing (+ 1 documented xfail). Reproduce parity with
127
+ [`benchmarks/reflect_parity.py`](benchmarks/reflect_parity.py); benchmark
128
+ with [`benchmarks/diagnostics.py`](benchmarks/diagnostics.py) and
129
+ [`benchmarks/diagnostics_functional.py`](benchmarks/diagnostics_functional.py).
130
+
131
+ ## Layout
132
+
133
+ ```
134
+ python/
135
+ ├── pyproject.toml
136
+ ├── README.md (you are here)
137
+ ├── LICENSE (mirrors ../cpp/LICENSE — academic / non-commercial)
138
+ ├── src/rcrpy/
139
+ │ ├── __init__.py public API re-exports
140
+ │ ├── api.py RCR class, RCRResults, RejectionTech, MuType
141
+ │ ├── stats.py median, halfSampleMode, robust-sigma helpers
142
+ │ ├── rejection.py iterative + bulk loops (lower/single/each sigma)
143
+ │ ├── tables.py unity tables ported verbatim from cpp/src/RCR.cpp
144
+ │ ├── nonparametric.py NonParametric base class
145
+ │ └── functional.py FunctionalForm + Priors for parametric fits
146
+ ├── tests/ pytest suite (85 + 1 xfailed)
147
+ ├── docs/ markdown tutorials + PUBLISHING checklist
148
+ ├── benchmarks/ parity + perf scripts (not part of the wheel)
149
+ └── scripts/ one-off codegen + post-install smoke check
150
+ ```
151
+
152
+ ## Citing
153
+
154
+ If you use `rcrpy` in academic work, please cite the original paper:
155
+
156
+ ```bibtex
157
+ @article{maples2018robust,
158
+ title = {Robust Chauvenet Outlier Rejection},
159
+ author = {Maples, M.P. and Reichart, D.E. and Konz, N.C. and Berger, T.A.
160
+ and Trotter, A.S. and Martin, J.R. and Dutton, D.A.
161
+ and Paggen, M.L. and Joyner, R.E. and Salemi, C.P.},
162
+ journal = {The Astrophysical Journal Supplement Series},
163
+ volume = {238}, number = {1}, pages = {2}, year = {2018},
164
+ }
165
+ ```
166
+
167
+ ## License
168
+
169
+ See [LICENSE](LICENSE) — mirrors the upstream C++ license. Free for
170
+ academic and non-commercial use; contact UNC's Office of
171
+ Commercialization and Economic Development for commercial licensing.
@@ -0,0 +1,26 @@
1
+ # `rcrpy` benchmarks
2
+
3
+ Standalone scripts for measuring `rcrpy` correctness and performance.
4
+ None of these are part of the installed package — they're maintainer
5
+ tools that live alongside the source.
6
+
7
+ | Script | What it does |
8
+ |---|---|
9
+ | [`reflect_parity.py`](reflect_parity.py) | Sweep all 20 Phase 1 parity scenarios (single-value RCR × every tech × iterative+bulk × weighted+unweighted). Reports max relative scalar disagreement vs the C++ oracle. Worst case: 1.7×10⁻¹⁵. |
10
+ | [`diagnostics.py`](diagnostics.py) | Per-scenario wall-clock + precision sweep for single-value RCR (port vs. C++ oracle). |
11
+ | [`diagnostics_functional.py`](diagnostics_functional.py) | Same for functional-form RCR. Demonstrates the port is FASTER than the oracle on this workload (80×–6000× depending on N). |
12
+ | [`bench_port_vs_oracle.py`](bench_port_vs_oracle.py) | One-shot bench on `data_singlevalue.csv` for quick perf checks. |
13
+
14
+ ## Running
15
+
16
+ Each script is self-contained. From the repo root:
17
+
18
+ ```bash
19
+ python rcrpy/benchmarks/reflect_parity.py
20
+ python rcrpy/benchmarks/diagnostics.py
21
+ python rcrpy/benchmarks/diagnostics_functional.py
22
+ ```
23
+
24
+ All four require the legacy `rcr` C++ module (the oracle) plus `rcrpy`
25
+ itself installed in the active environment — i.e., `pip install -e
26
+ ./python[dev]` covers everything.