rcrpy 0.1.0__py3-none-any.whl

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.
rcrpy/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ """rcrpy: Robust Chauvenet Rejection, Python reimplementation."""
2
+
3
+ from rcrpy.api import (
4
+ RCR,
5
+ RCRResults,
6
+ RejectionTech,
7
+ MuType,
8
+ )
9
+ from rcrpy.nonparametric import NonParametric
10
+ from rcrpy.functional import FunctionalForm, FunctionalFormResults, Priors, PriorType
11
+
12
+ __all__ = [
13
+ "RCR",
14
+ "RCRResults",
15
+ "RejectionTech",
16
+ "MuType",
17
+ "NonParametric",
18
+ "FunctionalForm",
19
+ "FunctionalFormResults",
20
+ "Priors",
21
+ "PriorType",
22
+ ]
23
+
24
+ __version__ = "0.1.0"
rcrpy/api.py ADDED
@@ -0,0 +1,169 @@
1
+ """Public API for rcrpy.
2
+
3
+ Mirrors the surface defined in cpp/src/RCR.h:
4
+ enum RejectionTechs { SS_MEDIAN_DL, LS_MODE_68, LS_MODE_DL, ES_MODE_DL }
5
+ struct RCRResults { mu, sigma, stDev, ..., flags, indices, ... }
6
+ class RCR { performRejection, performBulkRejection, ... }
7
+
8
+ Single-value (non-parametric, non-functional) rejection only in Phase 1.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from enum import Enum
14
+ from typing import Sequence
15
+
16
+ import numpy as np
17
+
18
+
19
+ class RejectionTech(Enum):
20
+ SS_MEDIAN_DL = "SS_MEDIAN_DL"
21
+ LS_MODE_68 = "LS_MODE_68"
22
+ LS_MODE_DL = "LS_MODE_DL"
23
+ ES_MODE_DL = "ES_MODE_DL"
24
+
25
+
26
+ class MuType(Enum):
27
+ """How `mu` is computed each iteration. VALUE is the default (use the
28
+ handled muTech directly on `y`); PARAMETRIC and NONPARAMETRIC delegate
29
+ to a model object that the user attaches via setter methods on RCR.
30
+ Ports of cpp/src/RCR.h::MuTypes."""
31
+ VALUE = "VALUE"
32
+ PARAMETRIC = "PARAMETRIC" # FunctionalForm — not yet wired in rcrpy
33
+ NONPARAMETRIC = "NONPARAMETRIC"
34
+
35
+
36
+ @dataclass
37
+ class RCRResults:
38
+ mu: float = float("nan")
39
+ sigma: float = float("nan")
40
+ sigma_below: float = float("nan")
41
+ sigma_above: float = float("nan")
42
+ st_dev: float = float("nan")
43
+ st_dev_below: float = float("nan")
44
+ st_dev_above: float = float("nan")
45
+ st_dev_total: float = float("nan")
46
+ flags: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=bool))
47
+ indices: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=np.int64))
48
+ clean_y: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=np.float64))
49
+ rejected_y: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=np.float64))
50
+ clean_w: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=np.float64))
51
+ rejected_w: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=np.float64))
52
+ original_y: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=np.float64))
53
+ original_w: np.ndarray = field(default_factory=lambda: np.empty(0, dtype=np.float64))
54
+
55
+
56
+ class RCR:
57
+ """Single-value Robust Chauvenet Rejection.
58
+
59
+ Phase 1: API skeleton. The real iterative + bulk loops are ported in
60
+ rcrpy.rejection and dispatched here.
61
+ """
62
+
63
+ def __init__(self, rejection_tech: RejectionTech = RejectionTech.SS_MEDIAN_DL):
64
+ self.rejection_tech = rejection_tech
65
+ self.result = RCRResults()
66
+ self.mu_type: MuType = MuType.VALUE
67
+ self.non_parametric_model = None # set via set_non_parametric_model
68
+ self.parametric_model = None # set via set_parametric_model
69
+
70
+ def set_rejection_tech(self, tech: RejectionTech) -> None:
71
+ self.rejection_tech = tech
72
+
73
+ def set_mu_type(self, mu_type: MuType) -> None:
74
+ self.mu_type = mu_type
75
+
76
+ def set_non_parametric_model(self, model) -> None:
77
+ """Attach a `rcrpy.NonParametric` subclass instance. The user must
78
+ also call `set_mu_type(MuType.NONPARAMETRIC)`. Port of
79
+ cpp/src/RCR.h::setNonParametricModel."""
80
+ self.non_parametric_model = model
81
+ self.mu_type = MuType.NONPARAMETRIC
82
+
83
+ def set_parametric_model(self, model) -> None:
84
+ """Attach a `rcrpy.FunctionalForm` instance for model-fitting RCR.
85
+ Sets mu_type to PARAMETRIC automatically. Port of
86
+ cpp/src/RCR.h::setParametricModel."""
87
+ self.parametric_model = model
88
+ self.mu_type = MuType.PARAMETRIC
89
+
90
+ def perform_rejection(
91
+ self,
92
+ y: Sequence[float],
93
+ w: Sequence[float] | None = None,
94
+ ) -> None:
95
+ from rcrpy import rejection
96
+ y_arr = np.asarray(y, dtype=np.float64)
97
+ w_arr = None if w is None else np.asarray(w, dtype=np.float64)
98
+ out = rejection.performRejection_LS(
99
+ y_arr, self.rejection_tech.value, w=w_arr,
100
+ non_parametric_model=(self.non_parametric_model
101
+ if self.mu_type is MuType.NONPARAMETRIC else None),
102
+ parametric_model=(self.parametric_model
103
+ if self.mu_type is MuType.PARAMETRIC else None),
104
+ )
105
+ r = self.result
106
+ r.mu = out["mu"]
107
+ r.flags = out["flags"]
108
+ r.indices = out["indices"]
109
+ r.clean_y = out["clean_y"]
110
+ # Each-sigma populates sigma_below/above; the other techs populate
111
+ # sigma/st_dev. setFinalVectors-only fields (rejectedY, originalY,
112
+ # clean_w/rejected_w/original_w) remain at default-empty per
113
+ # performRejection's contract.
114
+ if "sigma" in out:
115
+ r.sigma = out["sigma"]
116
+ r.st_dev = out["st_dev"]
117
+ if "sigma_below" in out:
118
+ r.sigma_below = out["sigma_below"]
119
+ r.sigma_above = out["sigma_above"]
120
+ if "st_dev_above" in out:
121
+ r.st_dev_above = out["st_dev_above"]
122
+ if "st_dev_below" in out:
123
+ r.st_dev_below = out["st_dev_below"]
124
+ if "clean_w" in out:
125
+ r.clean_w = out["clean_w"]
126
+ # NOTE: rejected_y / original_y / clean_w / rejected_w / original_w are
127
+ # NOT set here — the C++ performRejection (non-bulk) leaves them at
128
+ # their default-empty state; only performBulkRejection populates them
129
+ # via setFinalVectors. Match that contract for parity.
130
+
131
+ def perform_bulk_rejection(
132
+ self,
133
+ y: Sequence[float],
134
+ w: Sequence[float] | None = None,
135
+ ) -> None:
136
+ from rcrpy import rejection
137
+ y_arr = np.asarray(y, dtype=np.float64)
138
+ w_arr = None if w is None else np.asarray(w, dtype=np.float64)
139
+ out = rejection.performBulkRejection_LS(
140
+ y_arr, self.rejection_tech.value, w=w_arr,
141
+ non_parametric_model=(self.non_parametric_model
142
+ if self.mu_type is MuType.NONPARAMETRIC else None),
143
+ parametric_model=(self.parametric_model
144
+ if self.mu_type is MuType.PARAMETRIC else None),
145
+ )
146
+ r = self.result
147
+ r.mu = out["mu"]
148
+ r.flags = out["flags"]
149
+ r.indices = out["indices"]
150
+ r.clean_y = out["clean_y"]
151
+ r.rejected_y = out["rejected_y"]
152
+ r.original_y = out["original_y"]
153
+ r.st_dev_total = out["st_dev_total"]
154
+ r.st_dev_above = out["st_dev_above"]
155
+ r.st_dev_below = out["st_dev_below"]
156
+ # Single/Lower set sigma + st_dev; Each sets sigma_below/sigma_above.
157
+ if "sigma" in out:
158
+ r.sigma = out["sigma"]
159
+ if "st_dev" in out:
160
+ r.st_dev = out["st_dev"]
161
+ if "sigma_below" in out:
162
+ r.sigma_below = out["sigma_below"]
163
+ r.sigma_above = out["sigma_above"]
164
+ if "clean_w" in out:
165
+ r.clean_w = out["clean_w"]
166
+ if "rejected_w" in out:
167
+ r.rejected_w = out["rejected_w"]
168
+ if "original_w" in out:
169
+ r.original_w = out["original_w"]