decision-methods 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jose Francisco Benavente, Jose Antonio Suarez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: decision_methods
3
+ Version: 0.1.0
4
+ Summary: Library for decision threshold and boundary methods
5
+ Author-email: Jose Francisco Benavente Cuevas <jf.benavente@ciemat.es>, Jose Antonio Suarez Navarro <ja.suarez@ciemat.es>, Victor Manuel Exposito Suarez <VictorManuel.Exposito@ciemat.es>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy>=1.20
11
+ Requires-Dist: matplotlib>=3.5
12
+ Requires-Dist: scipy
13
+ Dynamic: license-file
14
+
15
+ # 🚀 decision_methods
16
+
17
+ **A scientific Python library for computing decision thresholds and limits using numerical methods independent of the analytical expression.**
18
+
19
+ Includes advanced implementations such as:
20
+ - **UD** — Decision Threshold
21
+ - **LID** — Decision Limit
22
+
23
+ ---
24
+
25
+ ## ✨ Key Features
26
+
27
+ - Numerical methods applicable to **any analytical model**
28
+ - Full **uncertainty propagation**
29
+ - Robust **root-finding algorithms**
30
+ - Scientific computation ready for real-world data
31
+ - Visualization tools for LID analysis
32
+
33
+ ---
34
+
35
+ ## ⚙️ Core Function
36
+
37
+ ### 🔹 `compute_F_ud_lid`
38
+
39
+ Computes:
40
+
41
+ - Model value
42
+ - Associated uncertainty
43
+ - Decision Threshold (**UD**)
44
+ - Decision Limit (**LID**)
45
+
46
+ #### **Parameters**
47
+
48
+ - `p` *(array-like)* — Model parameter vector
49
+ - `t_m` *(float)* — Measurement time
50
+ - `t_f` *(float)* — Background time
51
+ - `f` *(callable)* — Multivariable function
52
+ - `pderivative` *(callable)* — Partial derivative function
53
+ - `bisection` *(callable)* — Root-finding method
54
+ - `k` *(float, optional)* — Coverage factor *(default: 1.65)*
55
+ - `LID_a` *(float)* — Lower bound for LID search
56
+ - `LID_b` *(float)* — Upper bound for LID search
57
+ - `cpm_min`*(float)* — Lower bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
58
+ - `cpm_max`*(float)* — Upper bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
59
+ - `cpm_index`*(integer)* — Index of the parameter corresponding to the count rate (CPM) used in the LID search
60
+ - `sample_indices` *(tuple of int, optional)* — Indices of parameters corresponding to sample measurements modeled as Poisson-distributed variables, with expected counts defined as the parameter value scaled by the measurement time t_m *(default: (0,))*
61
+ - `background_indices` *(tuple of int, optional)* — Indices of parameters corresponding to sample measurements modeled as Poisson-distributed variables, with expected counts defined as the parameter value scaled by the measurement time t_f *(default: (1,))*
62
+ - `rel_map` *(tuple of (int, float), optional)* — Relative uncertainty definitions as *(index, relative_error)* pairs
63
+ - `frac_fondo` *(float, optional)* — Relative uncertainty for background when `t_f <= 0`
64
+
65
+ #### **Returns**
66
+
67
+ - `value` *(float)* — Evaluated value of the model function
68
+ - `u_value` *(float)* — Combined uncertainty
69
+ - `UD` *(float)* — Decision threshold
70
+ - `LID` *(float)* — Decision limit
71
+ #### **Returns**
72
+
73
+ - `value` *(float)* — Evaluated value of the model function
74
+ - `u_value` *(float)* — Combined uncertainty
75
+ - `UD` *(float)* — Decision threshold
76
+ - `LID` *(float)* — Decision limit
77
+
78
+ ---
79
+
80
+ ## 📊 Visualization
81
+
82
+ ### 🔹 `plot_lid_function`
83
+
84
+ Generates plots to analyze LID behavior.
85
+
86
+ #### **Parameters**
87
+
88
+ - `LID_a` *(float)* — Lower bound
89
+ - `LID_b` *(float)* — Upper bound
90
+ - `p` *(array-like)* — Parameters
91
+ - `f` *(callable)* — Function
92
+ - `UD` *(float)* — Decision threshold
93
+ - `t_m` *(float)* — Measurement time
94
+ - `t_f` *(float)* — Background time
95
+ - `pderivative` *(callable)* — Partial derivative function
96
+ - `bisection` *(callable)* — Root-finding method
97
+ - `cpm_min`*(float)* — Lower bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
98
+ - `cpm_max`*(float)* — Upper bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
99
+ - `n`*(integer)* — Number of samples or observations used in the LID search
100
+ - `cpm_index`*(integer)* — Index of the parameter corresponding to the count rate (CPM) used in the LID search
101
+ - `k` *(float, optional)* — Coverage factor *(default: 1.65)*
102
+ - `rel_map` *(tuple of (int, float), optional)* — Relative uncertainty definitions as *(index, relative_error)* pairs
103
+
104
+ ---
105
+
106
+ ## 🧪 Activity Functions
107
+
108
+ ### 🔹 `AT` — Total Alpha
109
+
110
+ Computes total alpha activity.
111
+
112
+ $AT(Bq\cdot m^{3})= \frac{CPM_M-CPM_F}{60 \cdot ɛ \cdot V_{Alicuot}}$
113
+
114
+ #### **Parameters**
115
+
116
+ - `CPM_m` *(float)* — Sample counts
117
+ - `CPM_f` *(float)* — Background counts
118
+ - `Eps` *(float)* — Detection efficiency
119
+ - `V_ali` *(float)* — Aliquot volume
120
+
121
+ #### **Returns**
122
+
123
+ - *(float)* — Activity
124
+
125
+ ---
126
+
127
+ ### 🔹 `BT` — Total Beta
128
+
129
+ Computes total beta activity.
130
+
131
+ $BT(Bq\cdot m^{3})= \frac{CPM_{\beta-M}-CPM_{\beta-F} - γ \cdot(CPM_{α-M}-CPM_{α-F})}{60 \cdot Ef_{Sr} \cdot V_{Alicuot}⋅Fa}$
132
+
133
+ #### **Parameters**
134
+
135
+ - `CPM_b_m` *(float)* — Measured beta counts
136
+ - `CPM_b_f` *(float)* — Background beta counts
137
+ - `CPM_a_m` *(float)* — Measured alpha counts
138
+ - `CPM_a_f` *(float)* — Background alpha counts
139
+ - `Spill` *(float)* — Spillover correction factor
140
+ - `EFSr` *(float)* — Detection efficiency factor
141
+ - `Fa` *(float)* — Correction factor
142
+ - `V_ali` *(float)* — Aliquot volume
143
+
144
+ #### **Returns**
145
+
146
+ - *(float)* — Total beta activity
147
+
148
+ ---
149
+
150
+ ## 📦 Installation
151
+
152
+ ```bash
153
+ pip install decision_methods
@@ -0,0 +1,139 @@
1
+ # 🚀 decision_methods
2
+
3
+ **A scientific Python library for computing decision thresholds and limits using numerical methods independent of the analytical expression.**
4
+
5
+ Includes advanced implementations such as:
6
+ - **UD** — Decision Threshold
7
+ - **LID** — Decision Limit
8
+
9
+ ---
10
+
11
+ ## ✨ Key Features
12
+
13
+ - Numerical methods applicable to **any analytical model**
14
+ - Full **uncertainty propagation**
15
+ - Robust **root-finding algorithms**
16
+ - Scientific computation ready for real-world data
17
+ - Visualization tools for LID analysis
18
+
19
+ ---
20
+
21
+ ## ⚙️ Core Function
22
+
23
+ ### 🔹 `compute_F_ud_lid`
24
+
25
+ Computes:
26
+
27
+ - Model value
28
+ - Associated uncertainty
29
+ - Decision Threshold (**UD**)
30
+ - Decision Limit (**LID**)
31
+
32
+ #### **Parameters**
33
+
34
+ - `p` *(array-like)* — Model parameter vector
35
+ - `t_m` *(float)* — Measurement time
36
+ - `t_f` *(float)* — Background time
37
+ - `f` *(callable)* — Multivariable function
38
+ - `pderivative` *(callable)* — Partial derivative function
39
+ - `bisection` *(callable)* — Root-finding method
40
+ - `k` *(float, optional)* — Coverage factor *(default: 1.65)*
41
+ - `LID_a` *(float)* — Lower bound for LID search
42
+ - `LID_b` *(float)* — Upper bound for LID search
43
+ - `cpm_min`*(float)* — Lower bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
44
+ - `cpm_max`*(float)* — Upper bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
45
+ - `cpm_index`*(integer)* — Index of the parameter corresponding to the count rate (CPM) used in the LID search
46
+ - `sample_indices` *(tuple of int, optional)* — Indices of parameters corresponding to sample measurements modeled as Poisson-distributed variables, with expected counts defined as the parameter value scaled by the measurement time t_m *(default: (0,))*
47
+ - `background_indices` *(tuple of int, optional)* — Indices of parameters corresponding to sample measurements modeled as Poisson-distributed variables, with expected counts defined as the parameter value scaled by the measurement time t_f *(default: (1,))*
48
+ - `rel_map` *(tuple of (int, float), optional)* — Relative uncertainty definitions as *(index, relative_error)* pairs
49
+ - `frac_fondo` *(float, optional)* — Relative uncertainty for background when `t_f <= 0`
50
+
51
+ #### **Returns**
52
+
53
+ - `value` *(float)* — Evaluated value of the model function
54
+ - `u_value` *(float)* — Combined uncertainty
55
+ - `UD` *(float)* — Decision threshold
56
+ - `LID` *(float)* — Decision limit
57
+ #### **Returns**
58
+
59
+ - `value` *(float)* — Evaluated value of the model function
60
+ - `u_value` *(float)* — Combined uncertainty
61
+ - `UD` *(float)* — Decision threshold
62
+ - `LID` *(float)* — Decision limit
63
+
64
+ ---
65
+
66
+ ## 📊 Visualization
67
+
68
+ ### 🔹 `plot_lid_function`
69
+
70
+ Generates plots to analyze LID behavior.
71
+
72
+ #### **Parameters**
73
+
74
+ - `LID_a` *(float)* — Lower bound
75
+ - `LID_b` *(float)* — Upper bound
76
+ - `p` *(array-like)* — Parameters
77
+ - `f` *(callable)* — Function
78
+ - `UD` *(float)* — Decision threshold
79
+ - `t_m` *(float)* — Measurement time
80
+ - `t_f` *(float)* — Background time
81
+ - `pderivative` *(callable)* — Partial derivative function
82
+ - `bisection` *(callable)* — Root-finding method
83
+ - `cpm_min`*(float)* — Lower bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
84
+ - `cpm_max`*(float)* — Upper bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
85
+ - `n`*(integer)* — Number of samples or observations used in the LID search
86
+ - `cpm_index`*(integer)* — Index of the parameter corresponding to the count rate (CPM) used in the LID search
87
+ - `k` *(float, optional)* — Coverage factor *(default: 1.65)*
88
+ - `rel_map` *(tuple of (int, float), optional)* — Relative uncertainty definitions as *(index, relative_error)* pairs
89
+
90
+ ---
91
+
92
+ ## 🧪 Activity Functions
93
+
94
+ ### 🔹 `AT` — Total Alpha
95
+
96
+ Computes total alpha activity.
97
+
98
+ $AT(Bq\cdot m^{3})= \frac{CPM_M-CPM_F}{60 \cdot ɛ \cdot V_{Alicuot}}$
99
+
100
+ #### **Parameters**
101
+
102
+ - `CPM_m` *(float)* — Sample counts
103
+ - `CPM_f` *(float)* — Background counts
104
+ - `Eps` *(float)* — Detection efficiency
105
+ - `V_ali` *(float)* — Aliquot volume
106
+
107
+ #### **Returns**
108
+
109
+ - *(float)* — Activity
110
+
111
+ ---
112
+
113
+ ### 🔹 `BT` — Total Beta
114
+
115
+ Computes total beta activity.
116
+
117
+ $BT(Bq\cdot m^{3})= \frac{CPM_{\beta-M}-CPM_{\beta-F} - γ \cdot(CPM_{α-M}-CPM_{α-F})}{60 \cdot Ef_{Sr} \cdot V_{Alicuot}⋅Fa}$
118
+
119
+ #### **Parameters**
120
+
121
+ - `CPM_b_m` *(float)* — Measured beta counts
122
+ - `CPM_b_f` *(float)* — Background beta counts
123
+ - `CPM_a_m` *(float)* — Measured alpha counts
124
+ - `CPM_a_f` *(float)* — Background alpha counts
125
+ - `Spill` *(float)* — Spillover correction factor
126
+ - `EFSr` *(float)* — Detection efficiency factor
127
+ - `Fa` *(float)* — Correction factor
128
+ - `V_ali` *(float)* — Aliquot volume
129
+
130
+ #### **Returns**
131
+
132
+ - *(float)* — Total beta activity
133
+
134
+ ---
135
+
136
+ ## 📦 Installation
137
+
138
+ ```bash
139
+ pip install decision_methods
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "decision_methods"
7
+ version = "0.1.0"
8
+ description = "Library for decision threshold and boundary methods"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+
12
+ authors = [
13
+ { name = "Jose Francisco Benavente Cuevas", email = "jf.benavente@ciemat.es" },
14
+ { name = "Jose Antonio Suarez Navarro", email = "ja.suarez@ciemat.es" },
15
+ { name = "Victor Manuel Exposito Suarez", email = "VictorManuel.Exposito@ciemat.es" }
16
+ ]
17
+
18
+ license = { text = "MIT" }
19
+
20
+ dependencies = [
21
+ "numpy>=1.20",
22
+ "matplotlib>=3.5",
23
+ "scipy"
24
+ ]
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ from .threshold import compute_F_ud_lid, plot_lid_function, pderivative, bisection, AT, BT
2
+
3
+ __all__ = [
4
+ "compute_F_ud_lid",
5
+ "plot_lid_function",
6
+ "pderivative",
7
+ "bisection",
8
+ "AT",
9
+ "BT",
10
+ ]
@@ -0,0 +1,342 @@
1
+
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ def pderivative(f, punto, indice, h=1e-12):
6
+ """
7
+ Calcula la derivada parcial de f en 'punto'
8
+ respecto a la variable indicada por 'indice'
9
+ usando diferencia finita centrada.
10
+
11
+ Parámetros:
12
+ f : función multivariable
13
+ punto : lista o array con el punto donde evaluar
14
+ indice : índice de la variable respecto a la cual derivar
15
+ h : paso de la diferencia finita
16
+
17
+ Devuelve:
18
+ Aproximación de la derivada parcial
19
+ """
20
+ punto = np.array(punto, dtype=float)
21
+
22
+ eps = np.finfo(float).eps
23
+ escala = max(abs(punto[indice]), 1.0)
24
+ h = np.sqrt(eps) * escala
25
+
26
+ punto_mas = punto.copy()
27
+ punto_menos = punto.copy()
28
+
29
+ punto_mas[indice] += h
30
+ punto_menos[indice] -= h
31
+
32
+ return (f(*punto_mas) - f(*punto_menos)) / (2 * h)
33
+
34
+
35
+ def bisection(f, a, b, tol=1e-12, max_iter=100):
36
+ """
37
+ Encuentra una raíz de f en el intervalo [a, b] usando el método de la bisección.
38
+
39
+ Parámetros:
40
+ f : función a evaluar
41
+ a, b : extremos del intervalo (f(a)*f(b) < 0)
42
+ tol : tolerancia para el criterio de parada
43
+ max_iter : máximo número de iteraciones
44
+
45
+ Retorna:
46
+ Aproximación de la raíz
47
+ """
48
+
49
+ if f(a) * f(b) > 0:
50
+ raise ValueError(f"No hay cambio de signo: a={a}, f(a)={fa}, b={b}, f(b)={fb}")
51
+ #raise ValueError("La función debe cambiar de signo en el intervalo [a, b].")
52
+
53
+ for i in range(max_iter):
54
+ c = (a + b) / 2
55
+ fc = f(c)
56
+
57
+ if abs(fc) < tol or (b - a) / 2 < tol:
58
+ return c
59
+
60
+ if f(a) * fc < 0:
61
+ b = c
62
+ else:
63
+ a = c
64
+
65
+ raise RuntimeError("No se alcanzó la convergencia en el número máximo de iteraciones.")
66
+
67
+ def Funcion_Previa_LID(LID, CPM_m, f, p,cpm_index=1):
68
+ return f(CPM_m, *p[cpm_index:]) - LID
69
+
70
+ def eval_f(f, p):
71
+ return f(*p)
72
+
73
+ def Funcion_LID(LID, CPM_m,UD,f, p_base, t_m=None, t_f=None, poisson_map=((0, "t_m"), (1, "t_f")), rel_map=((2, 0.075), (3, 0.01)),k=1.65, pderivative=None, indices_deriv=None,set_CPM_index=0):
74
+ """
75
+ Devuelve: LID - (UD + k * u_comb) donde u_comb = sqrt(sum((w_i*u_i)^2))
76
+ """
77
+ if pderivative is None:
78
+ raise ValueError("Debes pasar la función pderivative(f, p, indice=...).")
79
+
80
+ # Copia de parámetros y fijar CPM_m
81
+ p_eval = np.array(p_base, dtype=float).copy()
82
+ p_eval[set_CPM_index] = CPM_m
83
+
84
+ # Resolver tiempos por nombre
85
+ tiempos = {"t_m": t_m, "t_f": t_f}
86
+ for _, tname in poisson_map:
87
+ if tiempos.get(tname, None) is None:
88
+ raise ValueError(f"Falta el tiempo {tname} (pasa {tname}=...).")
89
+
90
+ # Elegir índices a derivar si no se especifica
91
+ if indices_deriv is None:
92
+ idxs = [i for i, _ in poisson_map] + [i for i, _ in rel_map]
93
+ # quitar duplicados preservando orden
94
+ seen = set()
95
+ indices_deriv = [i for i in idxs if not (i in seen or seen.add(i))]
96
+
97
+ # Construir u_i (incertidumbres estándar) y w_i (sensibilidades)
98
+ u_list = []
99
+ w_list = []
100
+
101
+ # Poisson: sqrt(N*t)/t = sqrt(N*t)/t (tu forma actual)
102
+ for i, tname in poisson_map:
103
+ t = tiempos[tname]
104
+ u_list.append(np.sqrt(p_eval[i] * t) / t)
105
+ w_list.append(pderivative(f, p_eval, indice=i))
106
+
107
+ # Relativas: u = p * frac
108
+ for i, frac in rel_map:
109
+ u_list.append(p_eval[i] * frac)
110
+ w_list.append(pderivative(f, p_eval, indice=i))
111
+
112
+ u = np.array(u_list, dtype=float)
113
+ w = np.array(w_list, dtype=float)
114
+
115
+ u_comb = np.sqrt(np.sum((w**2) * (u**2)))
116
+ return LID - (UD + k * u_comb)
117
+
118
+ def compute_F_ud_lid(
119
+ p, t_m, t_f, f, pderivative, bisection,
120
+ k=1.65,
121
+ LID_a=0.0, LID_b=100.0,
122
+ tol=1e-12, max_iter=100,
123
+ cpm_min_=0.0, cpm_max_=1.0,
124
+ cpm_min=0.0, cpm_max=1.0,
125
+ cpm_index=0,
126
+ sample_indices=(0,), # <- ahora varios
127
+ background_indices=(1,), # <- ahora varios
128
+ rel_map=((2, 0.075), (3, 0.01)),
129
+ frac_fondo=None, # <- NUEVO: error relativo fondo si t_f<=0
130
+ ):
131
+ p = np.array(p, dtype=float)
132
+
133
+ # 1) Actividad
134
+ actividad = eval_f(f, p)
135
+
136
+ # 2) Incertidumbre en p
137
+ idx_list = []
138
+ u_list = []
139
+
140
+ for i in sample_indices:
141
+ idx_list.append(i)
142
+ u_list.append(np.sqrt(p[i] * t_m) / t_m)
143
+
144
+ # Fondo: Poisson si t_f>0, si no -> error relativo fijo frac_fondo
145
+ if t_f is not None and t_f > 0:
146
+ for j in background_indices:
147
+ idx_list.append(j)
148
+ u_list.append(np.sqrt(p[j] * t_f) / t_f)
149
+ else:
150
+ if frac_fondo is None:
151
+ raise ValueError("Si t_f <= 0 debes proporcionar frac_fondo (error relativo del fondo).")
152
+ for j in background_indices:
153
+ idx_list.append(j)
154
+ u_list.append(p[j] * frac_fondo)
155
+
156
+ for i, frac in rel_map:
157
+ idx_list.append(i)
158
+ u_list.append(p[i] * frac)
159
+
160
+ u = np.array(u_list, dtype=float)
161
+ w = np.array([pderivative(f, p, indice=i) for i in idx_list], dtype=float)
162
+
163
+ u_act = np.sqrt(np.sum((w**2) * (u**2)))
164
+
165
+ # 3) UD: raíz de f=0 variando p[0]
166
+ raiz = bisection(
167
+ lambda x: eval_f(f, np.array([x if j == 0 else p[j] for j in range(len(p))], dtype=float)),
168
+ cpm_min_, cpm_max_,tol=1e-12, max_iter=400
169
+ )
170
+
171
+ p_UD = p.copy()
172
+ p_UD[0] = raiz
173
+
174
+ # Recalcular incertidumbre en p_UD (mismo criterio)
175
+ u_list_UD = []
176
+ for i in sample_indices:
177
+ u_list_UD.append(np.sqrt(p_UD[i] * t_m) / t_m)
178
+
179
+ if t_f is not None and t_f > 0:
180
+ for j in background_indices:
181
+ u_list_UD.append(np.sqrt(p_UD[j] * t_f) / t_f)
182
+ else:
183
+ if frac_fondo is None:
184
+ raise ValueError("Si t_f <= 0 debes proporcionar frac_fondo (error relativo del fondo).")
185
+ for j in background_indices:
186
+ u_list_UD.append(p_UD[j] * frac_fondo)
187
+
188
+ for i, frac in rel_map:
189
+ u_list_UD.append(p_UD[i] * frac)
190
+
191
+ u_UD = np.array(u_list_UD, dtype=float)
192
+ w_UD = np.array([pderivative(f, p_UD, indice=i) for i in idx_list], dtype=float)
193
+ u_act_UD = np.sqrt(np.sum((w_UD**2) * (u_UD**2)))
194
+
195
+ UD = u_act_UD * k
196
+
197
+ # 4) LID por bisección
198
+ a, b = float(LID_a), float(LID_b)
199
+
200
+ CPM_a = bisection(lambda x: Funcion_Previa_LID(a, x, f, p, cpm_index=cpm_index), cpm_min, cpm_max)
201
+ CPM_b = bisection(lambda x: Funcion_Previa_LID(b, x, f, p, cpm_index=cpm_index), cpm_min, cpm_max)
202
+ poisson_map = tuple((i, "t_m") for i in sample_indices) + tuple((j, "t_f") for j in background_indices)
203
+
204
+ fa = Funcion_LID(a, CPM_a, UD, f, p, t_m, t_f, pderivative=pderivative,
205
+ k=k, poisson_map=poisson_map,rel_map=rel_map)
206
+ fb = Funcion_LID(b, CPM_b, UD, f, p, t_m, t_f, pderivative=pderivative,
207
+ k=k, poisson_map=poisson_map,rel_map=rel_map)
208
+
209
+ LID = None
210
+ for _ in range(max_iter):
211
+ c = (a + b) / 2.0
212
+ CPM_c = bisection(lambda x: Funcion_Previa_LID(c, x, f, p, cpm_index=cpm_index), cpm_min, cpm_max)
213
+
214
+ fc = Funcion_LID(c, CPM_c, UD, f, p, t_m, t_f, poisson_map= poisson_map,pderivative=pderivative,
215
+ k=k, rel_map=rel_map)
216
+
217
+ if abs(fc) < tol or (b - a) / 2.0 < tol:
218
+ LID = c
219
+ break
220
+
221
+ if fa * fc < 0:
222
+ b = c
223
+ fb = fc
224
+ else:
225
+ a = c
226
+ fa = fc
227
+
228
+ if LID is None:
229
+ LID = (a + b) / 2.0
230
+
231
+ return actividad, u_act, UD, LID
232
+
233
+
234
+ def plot_lid_function(
235
+ LID_a,
236
+ LID_b,
237
+ p,
238
+ f,
239
+ UD,
240
+ t_m,
241
+ t_f,
242
+ pderivative,
243
+ bisection,
244
+ cpm_min,
245
+ cpm_max,
246
+ n=200,
247
+ cpm_index=0,
248
+ k=1.65,
249
+ rel_map=((2, 0.075), (3, 0.01)),
250
+ LID_objetivo=None, # valor de LID para la segunda gráfica
251
+ n_cpm=200 # número de puntos para la gráfica de Funcion_Previa_LID
252
+ ):
253
+ LIDs = np.linspace(LID_a, LID_b, n)
254
+ valores = []
255
+
256
+ for LID in LIDs:
257
+ CPM = bisection(
258
+ lambda x: Funcion_Previa_LID(LID, x, f, p, cpm_index=cpm_index),
259
+ cpm_min,
260
+ cpm_max
261
+ )
262
+
263
+ val = Funcion_LID(
264
+ LID, CPM, UD, f, p, t_m, t_f,
265
+ pderivative=pderivative,
266
+ k=k,
267
+ rel_map=rel_map
268
+ )
269
+
270
+ valores.append(val)
271
+
272
+ valores = np.array(valores)
273
+
274
+ # ---------------------------
275
+ # Gráfica 1: Funcion_LID vs LID
276
+ # ---------------------------
277
+ plt.figure(figsize=(7, 5))
278
+ plt.plot(LIDs, valores, label="Funcion_LID(LID)")
279
+ plt.axhline(0, linestyle="--")
280
+ plt.xlabel("LID")
281
+ plt.ylabel("Funcion_LID")
282
+ plt.title("Curva para encontrar LID")
283
+ plt.grid(True)
284
+ plt.legend()
285
+ plt.show()
286
+
287
+ # -----------------------------------------------
288
+ # Selección del LID para graficar Funcion_Previa_LID
289
+ # -----------------------------------------------
290
+ if LID_objetivo is None:
291
+ # Si no se indica, toma el punto central del intervalo
292
+ LID_objetivo = LIDs[len(LIDs) // 2]
293
+
294
+ # Si el valor no coincide exactamente con la malla, tomamos el más cercano
295
+ idx = np.argmin(np.abs(LIDs - LID_objetivo))
296
+ LID_seleccionado = LIDs[idx]
297
+
298
+ CPMs = np.linspace(cpm_min, cpm_max, n_cpm)
299
+ valores_previos = [
300
+ Funcion_Previa_LID(LID_seleccionado, cpm, f, p, cpm_index=cpm_index)
301
+ for cpm in CPMs
302
+ ]
303
+
304
+ # Intentamos calcular también la raíz por bisección para marcarla
305
+ try:
306
+ CPM_raiz = bisection(
307
+ lambda x: Funcion_Previa_LID(LID_seleccionado, x, f, p, cpm_index=cpm_index),
308
+ cpm_min,
309
+ cpm_max
310
+ )
311
+ valor_raiz = Funcion_Previa_LID(LID_seleccionado, CPM_raiz, f, p, cpm_index=cpm_index)
312
+ except Exception:
313
+ CPM_raiz = None
314
+ valor_raiz = None
315
+
316
+ # ---------------------------
317
+ # Gráfica 2: Funcion_Previa_LID vs CPM
318
+ # ---------------------------
319
+ plt.figure(figsize=(7, 5))
320
+ plt.plot(CPMs, valores_previos, label=f"Funcion_Previa_LID para LID={LID_seleccionado:.6g}")
321
+ plt.axhline(0, linestyle="--")
322
+ if CPM_raiz is not None:
323
+ plt.axvline(CPM_raiz, linestyle=":", label=f"Raíz CPM={CPM_raiz:.6g}")
324
+ plt.plot(CPM_raiz, valor_raiz, "o")
325
+ plt.xlabel("CPM")
326
+ plt.ylabel("Funcion_Previa_LID")
327
+ plt.title(f"Evolución de Funcion_Previa_LID para LID={LID_seleccionado:.6g}")
328
+ plt.grid(True)
329
+ plt.legend()
330
+ plt.show()
331
+
332
+ def AT(CPM_m,CPM_f,Eps,V_ali):
333
+ return (CPM_m-CPM_f)/(60*Eps*V_ali)
334
+
335
+ def BT(CPM_b_m,CPM_b_f,CPM_a_m,CPM_a_f,Spill,EFSr,Fa,V_ali):
336
+ return (CPM_b_m-CPM_b_f-Spill*(CPM_a_m-CPM_a_f))/(60*EFSr*V_ali*Fa)
337
+
338
+ def BR(CPM_b_m,CPM_b_f,CPM_a_m,CPM_a_f,Spill,EFSr,Fa,W_k,V_ali):
339
+ return (CPM_b_m-CPM_b_f-Spill*(CPM_a_m-CPM_a_f)-W_k)/(60*EFSr*V_ali*Fa)
340
+
341
+ def W_k(V_ali,A_e,Eps_k,C_k):
342
+ return 60*V_ali*A_e*Eps_k*C_k*1000
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: decision_methods
3
+ Version: 0.1.0
4
+ Summary: Library for decision threshold and boundary methods
5
+ Author-email: Jose Francisco Benavente Cuevas <jf.benavente@ciemat.es>, Jose Antonio Suarez Navarro <ja.suarez@ciemat.es>, Victor Manuel Exposito Suarez <VictorManuel.Exposito@ciemat.es>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: numpy>=1.20
11
+ Requires-Dist: matplotlib>=3.5
12
+ Requires-Dist: scipy
13
+ Dynamic: license-file
14
+
15
+ # 🚀 decision_methods
16
+
17
+ **A scientific Python library for computing decision thresholds and limits using numerical methods independent of the analytical expression.**
18
+
19
+ Includes advanced implementations such as:
20
+ - **UD** — Decision Threshold
21
+ - **LID** — Decision Limit
22
+
23
+ ---
24
+
25
+ ## ✨ Key Features
26
+
27
+ - Numerical methods applicable to **any analytical model**
28
+ - Full **uncertainty propagation**
29
+ - Robust **root-finding algorithms**
30
+ - Scientific computation ready for real-world data
31
+ - Visualization tools for LID analysis
32
+
33
+ ---
34
+
35
+ ## ⚙️ Core Function
36
+
37
+ ### 🔹 `compute_F_ud_lid`
38
+
39
+ Computes:
40
+
41
+ - Model value
42
+ - Associated uncertainty
43
+ - Decision Threshold (**UD**)
44
+ - Decision Limit (**LID**)
45
+
46
+ #### **Parameters**
47
+
48
+ - `p` *(array-like)* — Model parameter vector
49
+ - `t_m` *(float)* — Measurement time
50
+ - `t_f` *(float)* — Background time
51
+ - `f` *(callable)* — Multivariable function
52
+ - `pderivative` *(callable)* — Partial derivative function
53
+ - `bisection` *(callable)* — Root-finding method
54
+ - `k` *(float, optional)* — Coverage factor *(default: 1.65)*
55
+ - `LID_a` *(float)* — Lower bound for LID search
56
+ - `LID_b` *(float)* — Upper bound for LID search
57
+ - `cpm_min`*(float)* — Lower bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
58
+ - `cpm_max`*(float)* — Upper bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
59
+ - `cpm_index`*(integer)* — Index of the parameter corresponding to the count rate (CPM) used in the LID search
60
+ - `sample_indices` *(tuple of int, optional)* — Indices of parameters corresponding to sample measurements modeled as Poisson-distributed variables, with expected counts defined as the parameter value scaled by the measurement time t_m *(default: (0,))*
61
+ - `background_indices` *(tuple of int, optional)* — Indices of parameters corresponding to sample measurements modeled as Poisson-distributed variables, with expected counts defined as the parameter value scaled by the measurement time t_f *(default: (1,))*
62
+ - `rel_map` *(tuple of (int, float), optional)* — Relative uncertainty definitions as *(index, relative_error)* pairs
63
+ - `frac_fondo` *(float, optional)* — Relative uncertainty for background when `t_f <= 0`
64
+
65
+ #### **Returns**
66
+
67
+ - `value` *(float)* — Evaluated value of the model function
68
+ - `u_value` *(float)* — Combined uncertainty
69
+ - `UD` *(float)* — Decision threshold
70
+ - `LID` *(float)* — Decision limit
71
+ #### **Returns**
72
+
73
+ - `value` *(float)* — Evaluated value of the model function
74
+ - `u_value` *(float)* — Combined uncertainty
75
+ - `UD` *(float)* — Decision threshold
76
+ - `LID` *(float)* — Decision limit
77
+
78
+ ---
79
+
80
+ ## 📊 Visualization
81
+
82
+ ### 🔹 `plot_lid_function`
83
+
84
+ Generates plots to analyze LID behavior.
85
+
86
+ #### **Parameters**
87
+
88
+ - `LID_a` *(float)* — Lower bound
89
+ - `LID_b` *(float)* — Upper bound
90
+ - `p` *(array-like)* — Parameters
91
+ - `f` *(callable)* — Function
92
+ - `UD` *(float)* — Decision threshold
93
+ - `t_m` *(float)* — Measurement time
94
+ - `t_f` *(float)* — Background time
95
+ - `pderivative` *(callable)* — Partial derivative function
96
+ - `bisection` *(callable)* — Root-finding method
97
+ - `cpm_min`*(float)* — Lower bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
98
+ - `cpm_max`*(float)* — Upper bound of the count rate (counts per unit time, e.g., minutes or seconds; CPM) used in the LID search
99
+ - `n`*(integer)* — Number of samples or observations used in the LID search
100
+ - `cpm_index`*(integer)* — Index of the parameter corresponding to the count rate (CPM) used in the LID search
101
+ - `k` *(float, optional)* — Coverage factor *(default: 1.65)*
102
+ - `rel_map` *(tuple of (int, float), optional)* — Relative uncertainty definitions as *(index, relative_error)* pairs
103
+
104
+ ---
105
+
106
+ ## 🧪 Activity Functions
107
+
108
+ ### 🔹 `AT` — Total Alpha
109
+
110
+ Computes total alpha activity.
111
+
112
+ $AT(Bq\cdot m^{3})= \frac{CPM_M-CPM_F}{60 \cdot ɛ \cdot V_{Alicuot}}$
113
+
114
+ #### **Parameters**
115
+
116
+ - `CPM_m` *(float)* — Sample counts
117
+ - `CPM_f` *(float)* — Background counts
118
+ - `Eps` *(float)* — Detection efficiency
119
+ - `V_ali` *(float)* — Aliquot volume
120
+
121
+ #### **Returns**
122
+
123
+ - *(float)* — Activity
124
+
125
+ ---
126
+
127
+ ### 🔹 `BT` — Total Beta
128
+
129
+ Computes total beta activity.
130
+
131
+ $BT(Bq\cdot m^{3})= \frac{CPM_{\beta-M}-CPM_{\beta-F} - γ \cdot(CPM_{α-M}-CPM_{α-F})}{60 \cdot Ef_{Sr} \cdot V_{Alicuot}⋅Fa}$
132
+
133
+ #### **Parameters**
134
+
135
+ - `CPM_b_m` *(float)* — Measured beta counts
136
+ - `CPM_b_f` *(float)* — Background beta counts
137
+ - `CPM_a_m` *(float)* — Measured alpha counts
138
+ - `CPM_a_f` *(float)* — Background alpha counts
139
+ - `Spill` *(float)* — Spillover correction factor
140
+ - `EFSr` *(float)* — Detection efficiency factor
141
+ - `Fa` *(float)* — Correction factor
142
+ - `V_ali` *(float)* — Aliquot volume
143
+
144
+ #### **Returns**
145
+
146
+ - *(float)* — Total beta activity
147
+
148
+ ---
149
+
150
+ ## 📦 Installation
151
+
152
+ ```bash
153
+ pip install decision_methods
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/decision_methods/__init__.py
5
+ src/decision_methods/threshold.py
6
+ src/decision_methods.egg-info/PKG-INFO
7
+ src/decision_methods.egg-info/SOURCES.txt
8
+ src/decision_methods.egg-info/dependency_links.txt
9
+ src/decision_methods.egg-info/requires.txt
10
+ src/decision_methods.egg-info/top_level.txt
11
+ tests/test_compute_lid.py
12
+ tests/test_inputs.py
13
+ tests/test_poisson_model.py
14
+ tests/test_regression.py
@@ -0,0 +1,3 @@
1
+ numpy>=1.20
2
+ matplotlib>=3.5
3
+ scipy
@@ -0,0 +1 @@
1
+ decision_methods
@@ -0,0 +1,30 @@
1
+ import numpy as np
2
+ import decision_methods.threshold as functions
3
+
4
+
5
+ def test_compute_F_ud_lid_basic():
6
+ p_BT = [3.163, 0.608, 0.154, 0.03, 0.32, 0.444, 0.932164125607044, 0.000375]
7
+
8
+ BT_, u_BT, u_BT_UD, LID_BT = functions.compute_F_ud_lid(
9
+ p_BT, 1000, 2000, functions.BT, functions.pderivative, functions.bisection,
10
+ k=1.65,
11
+ LID_a=0.0, LID_b=20.0,
12
+ tol=1e-12, max_iter=50,
13
+ cpm_min_=0.0, cpm_max_=0.65,
14
+ cpm_min=0.0, cpm_max=1.5,
15
+ cpm_index=1,
16
+ sample_indices=(0, 2),
17
+ background_indices=(1, 3),
18
+ rel_map=((4, 0.1), (5, 0.025), (6, 0.04), (7, 0.01)),
19
+ frac_fondo=None,
20
+ )
21
+
22
+ assert np.isfinite(BT_)
23
+ assert np.isfinite(u_BT)
24
+ assert np.isfinite(u_BT_UD)
25
+ assert np.isfinite(LID_BT)
26
+
27
+ assert BT_ >= 0
28
+ assert u_BT >= 0
29
+ assert u_BT_UD >= 0
30
+ assert LID_BT >= 0
@@ -0,0 +1,30 @@
1
+ import numpy as np
2
+ import decision_methods.threshold as functions
3
+
4
+
5
+ def test_compute_F_ud_lid_basic():
6
+ p_BT = [3.163, 0.608, 0.154, 0.03, 0.32, 0.444, 0.932164125607044, 0.000375]
7
+
8
+ BT_, u_BT, u_BT_UD, LID_BT = functions.compute_F_ud_lid(
9
+ p_BT, 1000, 2000, functions.BT, functions.pderivative, functions.bisection,
10
+ k=1.65,
11
+ LID_a=0.0, LID_b=20.0,
12
+ tol=1e-12, max_iter=50,
13
+ cpm_min_=0.0, cpm_max_=0.65,
14
+ cpm_min=0.0, cpm_max=1.5,
15
+ cpm_index=1,
16
+ sample_indices=(0, 2),
17
+ background_indices=(1, 3),
18
+ rel_map=((4, 0.1), (5, 0.025), (6, 0.04), (7, 0.01)),
19
+ frac_fondo=None,
20
+ )
21
+
22
+ assert np.isfinite(BT_)
23
+ assert np.isfinite(u_BT)
24
+ assert np.isfinite(u_BT_UD)
25
+ assert np.isfinite(LID_BT)
26
+
27
+ assert BT_ >= 0
28
+ assert u_BT >= 0
29
+ assert u_BT_UD >= 0
30
+ assert LID_BT >= 0
@@ -0,0 +1,9 @@
1
+ import numpy as np
2
+
3
+ def test_poisson_mean_variance():
4
+ lam = 5.0
5
+ samples = np.random.poisson(lam, size=10000)
6
+
7
+ # En Poisson: media ≈ varianza
8
+ assert np.isclose(np.mean(samples), lam, rtol=0.1)
9
+ assert np.isclose(np.var(samples), lam, rtol=0.1)
@@ -0,0 +1,31 @@
1
+ import numpy as np
2
+ import decision_methods.threshold as functions
3
+
4
+ EXPECTED_BT = 270.1067085367795
5
+ EXPECTED_U_BT = 14.49093
6
+ EXPECTED_U_BT_UD = 5.56013
7
+ EXPECTED_LID_BT = 11.48531
8
+
9
+
10
+ def test_compute_F_ud_lid_regression():
11
+ np.random.seed(0)
12
+ p_BT = [3.163, 0.608, 0.154, 0.03, 0.32, 0.444, 0.932164125607044, 0.000375]
13
+
14
+ BT_, u_BT, u_BT_UD, LID_BT = functions.compute_F_ud_lid(
15
+ p_BT, 1000, 2000, functions.BT, functions.pderivative, functions.bisection,
16
+ k=1.65,
17
+ LID_a=0.0, LID_b=20.0,
18
+ tol=1e-12, max_iter=50,
19
+ cpm_min_=0.0, cpm_max_=0.65,
20
+ cpm_min=0.0, cpm_max=1.5,
21
+ cpm_index=1,
22
+ sample_indices=(0, 2),
23
+ background_indices=(1, 3),
24
+ rel_map=((4, 0.1), (5, 0.025), (6, 0.04), (7, 0.01)),
25
+ frac_fondo=None,
26
+ )
27
+
28
+ assert np.isclose(BT_, EXPECTED_BT, rtol=1e-5, atol=1e-6)
29
+ assert np.isclose(u_BT, EXPECTED_U_BT, rtol=1e-5, atol=1e-6)
30
+ assert np.isclose(u_BT_UD, EXPECTED_U_BT_UD, rtol=1e-5, atol=1e-6)
31
+ assert np.isclose(LID_BT, EXPECTED_LID_BT, rtol=1e-5, atol=1e-6)