bezierv 1.0.1__py3-none-any.whl → 1.1.1__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.
- bezierv/algorithms/nelder_mead.py +8 -5
- bezierv/algorithms/non_linear.py +39 -32
- bezierv/algorithms/proj_grad.py +9 -8
- bezierv/algorithms/proj_subgrad.py +16 -13
- bezierv/classes/bezierv.py +206 -56
- bezierv/classes/distfit.py +9 -15
- {bezierv-1.0.1.dist-info → bezierv-1.1.1.dist-info}/METADATA +5 -1
- bezierv-1.1.1.dist-info/RECORD +16 -0
- bezierv-1.0.1.dist-info/RECORD +0 -16
- {bezierv-1.0.1.dist-info → bezierv-1.1.1.dist-info}/WHEEL +0 -0
- {bezierv-1.0.1.dist-info → bezierv-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {bezierv-1.0.1.dist-info → bezierv-1.1.1.dist-info}/top_level.txt +0 -0
@@ -114,7 +114,7 @@ def fit(n: int,
|
|
114
114
|
init_z: np.array,
|
115
115
|
emp_cdf_data: np.array,
|
116
116
|
max_iter: int
|
117
|
-
) -> Bezierv:
|
117
|
+
) -> tuple[Bezierv, float]:
|
118
118
|
"""
|
119
119
|
Fit the Bezier random variable to the empirical CDF data using the Nelder-Mead optimization algorithm.
|
120
120
|
|
@@ -134,13 +134,16 @@ def fit(n: int,
|
|
134
134
|
Initial guess for the z-coordinates of the control points.
|
135
135
|
emp_cdf_data : np.array
|
136
136
|
The empirical CDF data points used for fitting.
|
137
|
+
max_iter : int
|
138
|
+
The maximum number of iterations for the optimization algorithm.
|
137
139
|
|
138
140
|
Returns
|
139
141
|
-------
|
140
|
-
Bezierv
|
141
|
-
|
142
|
-
|
143
|
-
|
142
|
+
tuple[Bezierv, float]
|
143
|
+
A tuple containing:
|
144
|
+
- bezierv (Bezierv): The fitted `Bezierv` object with updated control
|
145
|
+
points.
|
146
|
+
- mse (float): The final mean squared error (MSE) of the fit.
|
144
147
|
"""
|
145
148
|
start = np.concatenate((init_x, init_z))
|
146
149
|
result = minimize(
|
bezierv/algorithms/non_linear.py
CHANGED
@@ -13,47 +13,54 @@ def fit(n: int,
|
|
13
13
|
init_t: np.array,
|
14
14
|
emp_cdf_data: np.array,
|
15
15
|
solver: str) -> Bezierv:
|
16
|
-
"""
|
17
|
-
|
18
|
-
|
16
|
+
"""Fits a Bézier random variable to empirical CDF data.
|
17
|
+
|
18
|
+
This method uses a nonlinear optimization solver to find the optimal
|
19
|
+
Bézier curve control points that best represent the empirical cumulative
|
20
|
+
distribution function (CDF) of the provided data.
|
21
|
+
|
19
22
|
Parameters
|
20
23
|
----------
|
21
24
|
n : int
|
22
|
-
|
25
|
+
The degree of the Bézier curve (number of control points - 1).
|
23
26
|
m : int
|
24
|
-
|
25
|
-
data : np.
|
26
|
-
|
27
|
+
The number of empirical CDF data points.
|
28
|
+
data : np.ndarray
|
29
|
+
The sorted data points used to fit the Bézier distribution.
|
27
30
|
bezierv : Bezierv
|
28
|
-
|
29
|
-
init_x : np.
|
30
|
-
|
31
|
-
init_z : np.
|
32
|
-
|
33
|
-
init_t : np.
|
34
|
-
|
35
|
-
emp_cdf_data : np.
|
36
|
-
|
31
|
+
An instance of the `Bezierv` class to be fitted.
|
32
|
+
init_x : np.ndarray
|
33
|
+
Initial guess for the x-coordinates of the control points.
|
34
|
+
init_z : np.ndarray
|
35
|
+
Initial guess for the z-coordinates of the control points.
|
36
|
+
init_t : np.ndarray
|
37
|
+
Initial guess for the Bézier 'time' parameters `t` in [0, 1].
|
38
|
+
emp_cdf_data : np.ndarray
|
39
|
+
The empirical CDF values corresponding to the `data` points.
|
37
40
|
solver : str, optional
|
38
|
-
|
41
|
+
The name of the solver to use for optimization. Defaults to 'ipopt'.
|
39
42
|
|
40
43
|
Returns
|
41
44
|
-------
|
42
|
-
Bezierv
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
45
|
+
tuple[Bezierv, float]
|
46
|
+
A tuple containing:
|
47
|
+
- bezierv (Bezierv): The fitted `Bezierv` object with updated
|
48
|
+
control points.
|
49
|
+
- mse (float): The final mean squared error (MSE) of the fit.
|
50
|
+
|
51
|
+
Raises
|
52
|
+
------
|
53
|
+
Exception
|
54
|
+
If the optimization solver fails to find a solution.
|
55
|
+
|
56
|
+
Notes
|
57
|
+
-----
|
58
|
+
The optimization is subject to several constraints to ensure a valid
|
59
|
+
CDF representation:
|
60
|
+
- The control points and 'time' parameters are kept sorted.
|
61
|
+
- Convexity constraints are applied to the control points.
|
62
|
+
- The first and last control points are fixed to the data's range.
|
63
|
+
- The first and last 'time' parameters are fixed to 0 and 1.
|
57
64
|
"""
|
58
65
|
# Defining the optimization model
|
59
66
|
model = pyo.ConcreteModel()
|
bezierv/algorithms/proj_grad.py
CHANGED
@@ -3,7 +3,7 @@ from bezierv.classes.bezierv import Bezierv
|
|
3
3
|
|
4
4
|
def grad(n: int,
|
5
5
|
m:int,
|
6
|
-
t:
|
6
|
+
t: np.array,
|
7
7
|
bezierv: Bezierv,
|
8
8
|
controls_z: np.array,
|
9
9
|
emp_cdf_data: np.array) -> np.array:
|
@@ -38,7 +38,7 @@ def grad(n: int,
|
|
38
38
|
grad_z += 2 * (bezierv.poly_z(t[j], controls_z) - emp_cdf_data[j]) * inner_sum
|
39
39
|
return grad_z
|
40
40
|
|
41
|
-
def project_z(controls_z: np.array):
|
41
|
+
def project_z(controls_z: np.array) -> np.array:
|
42
42
|
"""
|
43
43
|
Project the z control points onto the feasible set.
|
44
44
|
|
@@ -68,7 +68,7 @@ def fit(n: int,
|
|
68
68
|
bezierv: Bezierv,
|
69
69
|
init_x: np.array,
|
70
70
|
init_z: np.array,
|
71
|
-
t:
|
71
|
+
t: np.array,
|
72
72
|
emp_cdf_data: np.array,
|
73
73
|
step_size: float,
|
74
74
|
max_iter: int,
|
@@ -97,7 +97,7 @@ def fit(n: int,
|
|
97
97
|
Initial guess for the x-coordinates of the control points.
|
98
98
|
init_z : np.array
|
99
99
|
Initial guess for the z-coordinates of the control points.
|
100
|
-
t :
|
100
|
+
t : np.array
|
101
101
|
The parameter values corresponding to the data points. Expected to be an array of shape (m,).
|
102
102
|
emp_cdf_data : np.array
|
103
103
|
The empirical CDF data points used for fitting.
|
@@ -110,10 +110,11 @@ def fit(n: int,
|
|
110
110
|
|
111
111
|
Returns
|
112
112
|
-------
|
113
|
-
Bezierv
|
114
|
-
|
115
|
-
|
116
|
-
|
113
|
+
tuple[Bezierv, float]
|
114
|
+
A tuple containing:
|
115
|
+
- bezierv (Bezierv): The fitted `Bezierv` object with updated control
|
116
|
+
points.
|
117
|
+
- mse (float): The final mean squared error (MSE) of the fit.
|
117
118
|
"""
|
118
119
|
z = init_z
|
119
120
|
for i in range(max_iter):
|
@@ -5,7 +5,7 @@ from bezierv.algorithms import utils as utils
|
|
5
5
|
def subgrad(n: int,
|
6
6
|
m: int,
|
7
7
|
bezierv: Bezierv,
|
8
|
-
t:
|
8
|
+
t: np.array,
|
9
9
|
controls_z: np.array,
|
10
10
|
emp_cdf_data: np.array) -> tuple:
|
11
11
|
"""
|
@@ -28,8 +28,10 @@ def subgrad(n: int,
|
|
28
28
|
|
29
29
|
Returns
|
30
30
|
-------
|
31
|
-
np.
|
32
|
-
A tuple containing
|
31
|
+
tuple[np.ndarray, np.ndarray]
|
32
|
+
A tuple containing:
|
33
|
+
- subgrad_x (np.ndarray): Subgradient w.r.t. the x-coordinates.
|
34
|
+
- grad_z (np.ndarray): Gradient w.r.t. the z-coordinates.
|
33
35
|
"""
|
34
36
|
grad_z = np.zeros(n + 1)
|
35
37
|
subgrad_x = np.zeros(n + 1)
|
@@ -52,7 +54,7 @@ def subgrad(n: int,
|
|
52
54
|
return subgrad_x, grad_z
|
53
55
|
|
54
56
|
def project_x(data:np.array,
|
55
|
-
controls_x: np.array):
|
57
|
+
controls_x: np.array) -> np.array:
|
56
58
|
"""
|
57
59
|
Project the x control points onto the feasible set.
|
58
60
|
|
@@ -76,7 +78,7 @@ def project_x(data:np.array,
|
|
76
78
|
x_prime[-1] = data[-1]
|
77
79
|
return x_prime
|
78
80
|
|
79
|
-
def project_z(controls_z: np.array):
|
81
|
+
def project_z(controls_z: np.array) -> np.array:
|
80
82
|
"""
|
81
83
|
Project the z control points onto the feasible set.
|
82
84
|
|
@@ -104,7 +106,7 @@ def objective_function(m: int,
|
|
104
106
|
bezierv: Bezierv,
|
105
107
|
emp_cdf_data: np.array,
|
106
108
|
z: np.array,
|
107
|
-
t: np.array):
|
109
|
+
t: np.array) -> float:
|
108
110
|
"""
|
109
111
|
Compute the objective function value for the given z control points.
|
110
112
|
|
@@ -134,10 +136,10 @@ def fit(n: int,
|
|
134
136
|
bezierv: Bezierv,
|
135
137
|
init_x: np.array,
|
136
138
|
init_z: np.array,
|
137
|
-
init_t:
|
139
|
+
init_t: np.array,
|
138
140
|
emp_cdf_data: np.array,
|
139
141
|
step_size: float,
|
140
|
-
max_iter: int):
|
142
|
+
max_iter: int) -> tuple[Bezierv, float]:
|
141
143
|
"""
|
142
144
|
Fit the Bezier random variable to the empirical CDF data using projected gradient descent.
|
143
145
|
|
@@ -162,7 +164,7 @@ def fit(n: int,
|
|
162
164
|
Initial control points for the x-coordinates of the Bezier curve.
|
163
165
|
init_z : np.array
|
164
166
|
Initial control points for the z-coordinates of the Bezier curve.
|
165
|
-
init_t :
|
167
|
+
init_t : np.array
|
166
168
|
Initial parameter values corresponding to the data points.
|
167
169
|
emp_cdf_data : np.array
|
168
170
|
The empirical cumulative distribution function (CDF) data derived from the empirical data.
|
@@ -173,10 +175,11 @@ def fit(n: int,
|
|
173
175
|
|
174
176
|
Returns
|
175
177
|
-------
|
176
|
-
Bezierv
|
177
|
-
|
178
|
-
|
179
|
-
|
178
|
+
tuple[Bezierv, float]
|
179
|
+
A tuple containing:
|
180
|
+
- bezierv (Bezierv): The fitted `Bezierv` object with updated control
|
181
|
+
points.
|
182
|
+
- mse (float): The final mean squared error (MSE) of the fit.
|
180
183
|
"""
|
181
184
|
f_best = np.inf
|
182
185
|
x_best = None
|
bezierv/classes/bezierv.py
CHANGED
@@ -6,13 +6,14 @@ from scipy.optimize import brentq, bisect
|
|
6
6
|
from scipy.integrate import quad
|
7
7
|
from statsmodels.distributions.empirical_distribution import ECDF
|
8
8
|
|
9
|
-
|
9
|
+
# import arrays for type hinting
|
10
|
+
from numpy.typing import ArrayLike
|
10
11
|
|
11
12
|
class Bezierv:
|
12
13
|
def __init__(self,
|
13
14
|
n: int,
|
14
|
-
controls_x=None,
|
15
|
-
controls_z=None):
|
15
|
+
controls_x: ArrayLike=None,
|
16
|
+
controls_z: ArrayLike=None):
|
16
17
|
"""
|
17
18
|
Initialize a Bezierv instance representing a Bezier random variable.
|
18
19
|
|
@@ -117,14 +118,6 @@ class Bezierv:
|
|
117
118
|
def combinations(self):
|
118
119
|
"""
|
119
120
|
Compute and store binomial coefficients.
|
120
|
-
|
121
|
-
Parameters
|
122
|
-
----------
|
123
|
-
None
|
124
|
-
|
125
|
-
Returns
|
126
|
-
-------
|
127
|
-
None
|
128
121
|
"""
|
129
122
|
n = self.n
|
130
123
|
for i in range(0, n + 1):
|
@@ -135,21 +128,13 @@ class Bezierv:
|
|
135
128
|
def deltas(self):
|
136
129
|
"""
|
137
130
|
Compute the differences between consecutive control points.
|
138
|
-
|
139
|
-
Parameters
|
140
|
-
----------
|
141
|
-
None
|
142
|
-
|
143
|
-
Returns
|
144
|
-
-------
|
145
|
-
None
|
146
131
|
"""
|
147
132
|
n = self.n
|
148
133
|
for i in range(n):
|
149
134
|
self.deltas_x[i] = self.controls_x[i + 1] - self.controls_x[i]
|
150
135
|
self.deltas_z[i] = self.controls_z[i + 1] - self.controls_z[i]
|
151
136
|
|
152
|
-
def bernstein(self, t, i, combinations, n):
|
137
|
+
def bernstein(self, t: float, i: int, combinations: np.array, n: int) -> float:
|
153
138
|
"""
|
154
139
|
Compute the Bernstein basis polynomial value.
|
155
140
|
|
@@ -159,7 +144,7 @@ class Bezierv:
|
|
159
144
|
The parameter value (in the interval [0, 1]).
|
160
145
|
i : int
|
161
146
|
The index of the Bernstein basis polynomial.
|
162
|
-
combinations : array
|
147
|
+
combinations : np.array
|
163
148
|
An array of binomial coefficients to use in the computation.
|
164
149
|
n : int
|
165
150
|
The degree for the Bernstein polynomial.
|
@@ -171,7 +156,7 @@ class Bezierv:
|
|
171
156
|
"""
|
172
157
|
return combinations[i] * t**i * (1 - t)**(n - i)
|
173
158
|
|
174
|
-
def poly_x(self, t, controls_x = None):
|
159
|
+
def poly_x(self, t: float, controls_x: np.array = None) -> float:
|
175
160
|
"""
|
176
161
|
Evaluate the x-coordinate at a given t value.
|
177
162
|
|
@@ -195,8 +180,8 @@ class Bezierv:
|
|
195
180
|
for i in range(n + 1):
|
196
181
|
p_x += self.bernstein(t, i, self.comb, self.n) * controls_x[i]
|
197
182
|
return p_x
|
198
|
-
|
199
|
-
def poly_z(self, t, controls_z = None):
|
183
|
+
|
184
|
+
def poly_z(self, t: float, controls_z: np.array = None) -> float:
|
200
185
|
"""
|
201
186
|
Evaluate the z-coordinate at a given t value.
|
202
187
|
|
@@ -204,7 +189,7 @@ class Bezierv:
|
|
204
189
|
----------
|
205
190
|
t : float
|
206
191
|
The parameter value at which to evaluate the curve (typically in [0, 1]).
|
207
|
-
controls_z : array
|
192
|
+
controls_z : np.array, optional
|
208
193
|
An array of control points for the z-coordinate. Defaults to self.controls_z.
|
209
194
|
|
210
195
|
Returns
|
@@ -220,8 +205,8 @@ class Bezierv:
|
|
220
205
|
for i in range(n + 1):
|
221
206
|
p_z += self.bernstein(t, i, self.comb, self.n) * controls_z[i]
|
222
207
|
return p_z
|
223
|
-
|
224
|
-
def root_find(self, x, method='brentq'):
|
208
|
+
|
209
|
+
def root_find(self, x: float, method: str = 'brentq') -> float:
|
225
210
|
"""
|
226
211
|
Find t such that the Bezier curve's x-coordinate equals a given value.
|
227
212
|
|
@@ -249,7 +234,7 @@ class Bezierv:
|
|
249
234
|
t = bisect(poly_x_zero, 0, 1, args=(x,))
|
250
235
|
return t
|
251
236
|
|
252
|
-
def eval_t(self, t):
|
237
|
+
def eval_t(self, t: float) -> tuple[float, float]:
|
253
238
|
"""
|
254
239
|
Evaluate the CDF of the Bezier random variable at a given parameter value t.
|
255
240
|
|
@@ -260,8 +245,8 @@ class Bezierv:
|
|
260
245
|
|
261
246
|
Returns
|
262
247
|
-------
|
263
|
-
tuple
|
264
|
-
A tuple (
|
248
|
+
tuple[float, float]
|
249
|
+
A tuple containing the (x, z) coordinates of the point on the curve w.r.t. t.
|
265
250
|
"""
|
266
251
|
self._ensure_initialized()
|
267
252
|
n = self.n
|
@@ -271,8 +256,8 @@ class Bezierv:
|
|
271
256
|
p_x += self.comb[i] * t**i * (1 - t)**(n - i) * self.controls_x[i]
|
272
257
|
p_z += self.comb[i] * t**i * (1 - t)**(n - i) * self.controls_z[i]
|
273
258
|
return p_x, p_z
|
274
|
-
|
275
|
-
def eval_x(self, x):
|
259
|
+
|
260
|
+
def eval_x(self, x: float) -> tuple[float, float]:
|
276
261
|
"""
|
277
262
|
Evaluate the CDF of the Bezier random variable at a given x-coordinate.
|
278
263
|
|
@@ -283,14 +268,14 @@ class Bezierv:
|
|
283
268
|
|
284
269
|
Returns
|
285
270
|
-------
|
286
|
-
tuple
|
287
|
-
A tuple
|
271
|
+
tuple[float, float]
|
272
|
+
A tuple containing the (x, z) coordinates of the point on the curve w.r.t. x.
|
288
273
|
"""
|
289
274
|
self._ensure_initialized()
|
290
275
|
t = self.root_find(x)
|
291
276
|
return self.eval_t(t)
|
292
|
-
|
293
|
-
def cdf_x(self, x):
|
277
|
+
|
278
|
+
def cdf_x(self, x: float) -> float:
|
294
279
|
"""
|
295
280
|
Compute the cumulative distribution function (CDF) at a given x-coordinate.
|
296
281
|
|
@@ -312,7 +297,7 @@ class Bezierv:
|
|
312
297
|
_, p_z = self.eval_x(x)
|
313
298
|
return p_z
|
314
299
|
|
315
|
-
def quantile(self, alpha, method='brentq'):
|
300
|
+
def quantile(self, alpha: float, method: str = 'brentq') -> float:
|
316
301
|
"""
|
317
302
|
Compute the quantile function (inverse CDF) for a given probability level alpha.
|
318
303
|
|
@@ -335,8 +320,8 @@ class Bezierv:
|
|
335
320
|
elif method == 'bisect':
|
336
321
|
t = bisect(cdf_t, 0, 1, args=(alpha,))
|
337
322
|
return self.poly_x(t)
|
338
|
-
|
339
|
-
def pdf_t(self, t):
|
323
|
+
|
324
|
+
def pdf_t(self, t: float) -> float:
|
340
325
|
"""
|
341
326
|
Compute the probability density function (PDF) of the Bezier random variable with respect to t.
|
342
327
|
|
@@ -359,7 +344,7 @@ class Bezierv:
|
|
359
344
|
pdf_denom_x += self.bernstein(t, i, self.comb_minus, n - 1) * self.deltas_x[i]
|
360
345
|
return pdf_num_z/pdf_denom_x
|
361
346
|
|
362
|
-
def pdf_x(self, x):
|
347
|
+
def pdf_x(self, x: float) -> float:
|
363
348
|
"""
|
364
349
|
Compute the probability density function (PDF) of the Bezier random variable at a given x.
|
365
350
|
|
@@ -376,8 +361,8 @@ class Bezierv:
|
|
376
361
|
self._ensure_initialized()
|
377
362
|
t = self.root_find(x)
|
378
363
|
return self.pdf_t(t)
|
379
|
-
|
380
|
-
def pdf_numerator_t(self, t):
|
364
|
+
|
365
|
+
def pdf_numerator_t(self, t: float) -> float:
|
381
366
|
"""
|
382
367
|
Compute the numerator part of the PDF for the Bezier random variable with respect to t.
|
383
368
|
|
@@ -397,7 +382,7 @@ class Bezierv:
|
|
397
382
|
pdf_num_z += self.bernstein(t, i, self.comb_minus, self.n - 1) * self.deltas_z[i]
|
398
383
|
return pdf_num_z
|
399
384
|
|
400
|
-
def get_mean(self, closed_form: bool=True):
|
385
|
+
def get_mean(self, closed_form: bool=True) -> float:
|
401
386
|
"""
|
402
387
|
Compute and return the mean of the distribution.
|
403
388
|
|
@@ -427,7 +412,12 @@ class Bezierv:
|
|
427
412
|
self.mean, _ = quad(lambda x: x * self.pdf_x(x), a, b)
|
428
413
|
return self.mean
|
429
414
|
|
430
|
-
def get_variance(self):
|
415
|
+
def get_variance(self) -> float:
|
416
|
+
"""Compute and return the variance of the distribution.
|
417
|
+
|
418
|
+
Returns:
|
419
|
+
float: The variance value of the distribution.
|
420
|
+
"""
|
431
421
|
self._ensure_initialized()
|
432
422
|
a, b = self.bounds
|
433
423
|
E_x2, _ = quad(lambda x: (x)**2 * self.pdf_x(x), a, b)
|
@@ -437,6 +427,8 @@ class Bezierv:
|
|
437
427
|
self.variance = E_x2 - self.mean**2
|
438
428
|
return self.variance
|
439
429
|
|
430
|
+
#TODO: implement skewness and kurtosis
|
431
|
+
|
440
432
|
def random(self,
|
441
433
|
n_sims: int,
|
442
434
|
*,
|
@@ -471,8 +463,8 @@ class Bezierv:
|
|
471
463
|
samples[i] = self.quantile(u[i])
|
472
464
|
return samples
|
473
465
|
|
474
|
-
|
475
|
-
def plot_cdf(self, data=None, num_points=100, ax=None):
|
466
|
+
|
467
|
+
def plot_cdf(self, data: np.array=None, num_points: int=100, ax: plt.Axes=None):
|
476
468
|
"""
|
477
469
|
Plot the cumulative distribution function (CDF) of the Bezier random variable alongside
|
478
470
|
the empirical CDF (if data is provided).
|
@@ -485,10 +477,6 @@ class Bezierv:
|
|
485
477
|
The number of points to use in the linspace when data is not provided (default is 100).
|
486
478
|
ax : matplotlib.axes.Axes, optional
|
487
479
|
The axes on which to plot the CDF. If None, the current axes are used.
|
488
|
-
|
489
|
-
Returns
|
490
|
-
-------
|
491
|
-
None
|
492
480
|
"""
|
493
481
|
self._ensure_initialized()
|
494
482
|
data_bool = True
|
@@ -520,7 +508,7 @@ class Bezierv:
|
|
520
508
|
if show:
|
521
509
|
plt.show()
|
522
510
|
|
523
|
-
def plot_pdf(self, data=None, num_points=100, ax=None):
|
511
|
+
def plot_pdf(self, data: np.array=None, num_points: int=100, ax: plt.Axes=None):
|
524
512
|
"""
|
525
513
|
Plot the probability density function (PDF) of the Bezier random variable.
|
526
514
|
|
@@ -532,10 +520,6 @@ class Bezierv:
|
|
532
520
|
The number of points to use in the linspace when data is not provided (default is 100).
|
533
521
|
ax : matplotlib.axes.Axes, optional
|
534
522
|
The axes on which to plot the PDF. If None, the current axes are used.
|
535
|
-
|
536
|
-
Returns
|
537
|
-
-------
|
538
|
-
None
|
539
523
|
"""
|
540
524
|
self._ensure_initialized()
|
541
525
|
if data is None:
|
@@ -576,4 +560,170 @@ class Bezierv:
|
|
576
560
|
raise RuntimeError(
|
577
561
|
"Bezier controls are all zeros (placeholder). "
|
578
562
|
"Provide valid controls in the constructor or call update_bezierv()."
|
579
|
-
)
|
563
|
+
)
|
564
|
+
|
565
|
+
from bokeh.plotting import figure, curdoc
|
566
|
+
from bokeh.models import (ColumnDataSource, PointDrawTool, Button, CustomJS,
|
567
|
+
DataTable, TableColumn, NumberFormatter)
|
568
|
+
from bokeh.layouts import column, row
|
569
|
+
|
570
|
+
class InteractiveBezierv:
|
571
|
+
"""Manages an interactive Bezier distribution in a Bokeh plot."""
|
572
|
+
|
573
|
+
def __init__(self, controls_x, controls_z):
|
574
|
+
self._is_updating = False
|
575
|
+
|
576
|
+
n = len(controls_x) - 1
|
577
|
+
self.bezier = Bezierv(n=n, controls_x=controls_x, controls_z=controls_z)
|
578
|
+
|
579
|
+
self.controls_source = ColumnDataSource(data=self._get_controls_data())
|
580
|
+
self.curve_source = ColumnDataSource(data=self._get_curve_data())
|
581
|
+
|
582
|
+
self.plot = figure(
|
583
|
+
height=600, width=900,
|
584
|
+
title="Interactive Bézier CDF Editor",
|
585
|
+
x_axis_label="x", y_axis_label="CDF",
|
586
|
+
y_range=(-0.05, 1.05)
|
587
|
+
)
|
588
|
+
|
589
|
+
self.plot.line(x='x', y='y', source=self.curve_source, line_width=3, legend_label="Bézier CDF", color="navy")
|
590
|
+
self.plot.line(x='x', y='y', source=self.controls_source, line_dash="dashed", color="gray")
|
591
|
+
|
592
|
+
controls_renderer = self.plot.scatter(
|
593
|
+
x='x', y='y', source=self.controls_source, size=12,
|
594
|
+
legend_label="Control Points", color="firebrick"
|
595
|
+
)
|
596
|
+
self.plot.legend.location = "top_left"
|
597
|
+
|
598
|
+
draw_tool = PointDrawTool(renderers=[controls_renderer])
|
599
|
+
self.plot.add_tools(draw_tool)
|
600
|
+
self.plot.toolbar.active_tap = draw_tool
|
601
|
+
|
602
|
+
formatter = NumberFormatter(format="0.000")
|
603
|
+
columns = [
|
604
|
+
TableColumn(field="x", title="X", formatter=formatter),
|
605
|
+
TableColumn(field="y", title="Z", formatter=formatter)
|
606
|
+
]
|
607
|
+
|
608
|
+
self.data_table = DataTable(
|
609
|
+
source=self.controls_source,
|
610
|
+
columns=columns,
|
611
|
+
width=250,
|
612
|
+
height=600,
|
613
|
+
editable=True
|
614
|
+
)
|
615
|
+
|
616
|
+
self.download_button = Button(
|
617
|
+
label="Download Control Points as CSV",
|
618
|
+
button_type="success",
|
619
|
+
width=250
|
620
|
+
)
|
621
|
+
|
622
|
+
callback = CustomJS(args=dict(source=self.controls_source), code="""
|
623
|
+
const data = source.data;
|
624
|
+
const file_name = 'control_points.csv';
|
625
|
+
let csv_content = 'X,Z_CDF\\n'; // CSV Header
|
626
|
+
|
627
|
+
// Iterate over the data and build the CSV string
|
628
|
+
for (let i = 0; i < data.x.length; i++) {
|
629
|
+
const row = [data.x[i], data.y[i]];
|
630
|
+
csv_content += row.join(',') + '\\n';
|
631
|
+
}
|
632
|
+
|
633
|
+
// Create a Blob and trigger the download
|
634
|
+
const blob = new Blob([csv_content], { type: 'text/csv;charset=utf-8;' });
|
635
|
+
const link = document.createElement('a');
|
636
|
+
link.href = URL.createObjectURL(blob);
|
637
|
+
link.download = file_name;
|
638
|
+
document.body.appendChild(link);
|
639
|
+
link.click();
|
640
|
+
document.body.removeChild(link);
|
641
|
+
""")
|
642
|
+
|
643
|
+
self.download_button.js_on_click(callback)
|
644
|
+
|
645
|
+
self.controls_source.on_change('data', self._update_callback)
|
646
|
+
|
647
|
+
widgets_layout = column(self.plot, self.download_button)
|
648
|
+
self.layout = row(widgets_layout, self.data_table)
|
649
|
+
|
650
|
+
def _get_controls_data(self):
|
651
|
+
"""Returns the current control points from the Bezierv instance."""
|
652
|
+
return {'x': self.bezier.controls_x, 'y': self.bezier.controls_z}
|
653
|
+
|
654
|
+
def _get_curve_data(self, num_points=200):
|
655
|
+
"""Calculates and returns the CDF curve points."""
|
656
|
+
t = np.linspace(0, 1, num_points)
|
657
|
+
curve_x = [self.bezier.poly_x(ti) for ti in t]
|
658
|
+
curve_z = [self.bezier.poly_z(ti) for ti in t]
|
659
|
+
return {'x': curve_x, 'y': curve_z}
|
660
|
+
|
661
|
+
def _update_callback(self, attr, old, new):
|
662
|
+
"""Handles moving, adding, and deleting control points."""
|
663
|
+
|
664
|
+
if self._is_updating:
|
665
|
+
return
|
666
|
+
|
667
|
+
try:
|
668
|
+
self._is_updating = True
|
669
|
+
|
670
|
+
new_x = new['x']
|
671
|
+
new_z = new['y']
|
672
|
+
|
673
|
+
is_point_added = len(new_x) > len(old['x'])
|
674
|
+
|
675
|
+
if is_point_added:
|
676
|
+
old_points_sorted = sorted(zip(old['x'], old['y']))
|
677
|
+
|
678
|
+
if len(old_points_sorted) < 2:
|
679
|
+
raise ValueError("Cannot add a point, need at least 2 existing points.")
|
680
|
+
|
681
|
+
mid_index = len(old_points_sorted) // 2
|
682
|
+
|
683
|
+
p_before = old_points_sorted[mid_index - 1]
|
684
|
+
p_after = old_points_sorted[mid_index]
|
685
|
+
|
686
|
+
x_new = (p_before[0] + p_after[0]) / 2.0
|
687
|
+
y_new = (p_before[1] + p_after[1]) / 2.0
|
688
|
+
|
689
|
+
final_points = old_points_sorted
|
690
|
+
final_points.insert(mid_index, (x_new, y_new))
|
691
|
+
|
692
|
+
final_x, final_z = zip(*final_points)
|
693
|
+
final_x, final_z = list(final_x), list(final_z)
|
694
|
+
|
695
|
+
else:
|
696
|
+
sorted_points = sorted(zip(new_x, new_z))
|
697
|
+
if not sorted_points:
|
698
|
+
final_x, final_z = [], []
|
699
|
+
else:
|
700
|
+
final_x, final_z = zip(*sorted_points)
|
701
|
+
final_x, final_z = list(final_x), list(final_z)
|
702
|
+
|
703
|
+
if final_z != sorted(final_z):
|
704
|
+
raise ValueError("Control points' y-values must be in non-decreasing order.")
|
705
|
+
|
706
|
+
if len(final_x) < 2:
|
707
|
+
raise ValueError("At least two control points are required.")
|
708
|
+
|
709
|
+
final_z[0] = 0.0
|
710
|
+
final_z[-1] = 1.0
|
711
|
+
|
712
|
+
new_n = len(final_x) - 1
|
713
|
+
if new_n != self.bezier.n:
|
714
|
+
self.bezier = Bezierv(n=new_n, controls_x=final_x, controls_z=final_z)
|
715
|
+
else:
|
716
|
+
self.bezier.update_bezierv(np.array(final_x), np.array(final_z))
|
717
|
+
|
718
|
+
self.controls_source.data = {
|
719
|
+
'x': list(self.bezier.controls_x),
|
720
|
+
'y': list(self.bezier.controls_z)
|
721
|
+
}
|
722
|
+
self.curve_source.data = self._get_curve_data()
|
723
|
+
|
724
|
+
except Exception as e:
|
725
|
+
print(f"An unexpected error occurred: {e}. Reverting.")
|
726
|
+
self.controls_source.data = dict(old)
|
727
|
+
|
728
|
+
finally:
|
729
|
+
self._is_updating = False
|
bezierv/classes/distfit.py
CHANGED
@@ -94,7 +94,7 @@ class DistFit:
|
|
94
94
|
def fit(self,
|
95
95
|
method: str='projgrad',
|
96
96
|
step_size_PG: float=0.001,
|
97
|
-
max_iter_PG:
|
97
|
+
max_iter_PG: int=1000,
|
98
98
|
threshold_PG: float=1e-3,
|
99
99
|
step_size_PS: float=0.001,
|
100
100
|
max_iter_PS: int=1000,
|
@@ -114,8 +114,14 @@ class DistFit:
|
|
114
114
|
The maximum number of iterations for the projected gradient descent method (default is 1000).
|
115
115
|
threshold_PG : float, optional
|
116
116
|
The convergence threshold for the projected gradient descent method (default is 1e-3).
|
117
|
+
step_size_PS : float, optional
|
118
|
+
The step size for the projected subgradient method (default is 0.001).
|
119
|
+
max_iter_PS : int, optional
|
120
|
+
The maximum number of iterations for the projected subgradient method (default is 1000).
|
117
121
|
solver_NL : str, optional
|
118
122
|
The solver to use for the nonlinear fitting method (default is 'ipopt').
|
123
|
+
max_iter_NM : int, optional
|
124
|
+
The maximum number of iterations for the Nelder-Mead optimization method (default is 1000).
|
119
125
|
|
120
126
|
Returns
|
121
127
|
-------
|
@@ -173,18 +179,6 @@ class DistFit:
|
|
173
179
|
def get_controls_z(self) -> np.array:
|
174
180
|
"""
|
175
181
|
Compute the control points for the z-coordinates of the Bezier curve.
|
176
|
-
|
177
|
-
'quantile' method is used to determine the control points based on the data quantiles.
|
178
|
-
|
179
|
-
Parameters
|
180
|
-
----------
|
181
|
-
data : np.array
|
182
|
-
The sorted data points.
|
183
|
-
|
184
|
-
Returns
|
185
|
-
-------
|
186
|
-
np.array
|
187
|
-
The control points for the z-coordinates of the Bezier curve.
|
188
182
|
"""
|
189
183
|
controls_z = np.linspace(0, 1, self.n + 1)
|
190
184
|
return controls_z
|
@@ -197,8 +191,8 @@ class DistFit:
|
|
197
191
|
|
198
192
|
Parameters
|
199
193
|
----------
|
200
|
-
|
201
|
-
The
|
194
|
+
method : str
|
195
|
+
The method to use for initializing the x-coordinates of the control points.
|
202
196
|
|
203
197
|
Returns
|
204
198
|
-------
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bezierv
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.1.1
|
4
4
|
Summary: This package serves as a computational framework for Bézier distributions.
|
5
5
|
Author-email: Esteban Leiva <e.leivam@uniandes.edu.co>, "Andrés L. Medaglia" <amedagli@uniandes.edu.co>
|
6
6
|
Maintainer-email: Esteban Leiva <e.leivam@uniandes.edu.co>
|
@@ -42,21 +42,25 @@ Requires-Dist: scipy<1.16,>=1.13; python_version == "3.10"
|
|
42
42
|
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.10"
|
43
43
|
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.10"
|
44
44
|
Requires-Dist: pyomo<7,>=6.8; python_version == "3.10"
|
45
|
+
Requires-Dist: bokeh<4,>=3.4; python_version == "3.10"
|
45
46
|
Requires-Dist: numpy<2.3,>=1.26; python_version == "3.11"
|
46
47
|
Requires-Dist: scipy<1.17,>=1.13; python_version == "3.11"
|
47
48
|
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.11"
|
48
49
|
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.11"
|
49
50
|
Requires-Dist: pyomo<7,>=6.8; python_version == "3.11"
|
51
|
+
Requires-Dist: bokeh<4,>=3.4; python_version == "3.11"
|
50
52
|
Requires-Dist: numpy<2.3,>=2.1; python_version == "3.12"
|
51
53
|
Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.12"
|
52
54
|
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.12"
|
53
55
|
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.12"
|
54
56
|
Requires-Dist: pyomo<7,>=6.8; python_version == "3.12"
|
57
|
+
Requires-Dist: bokeh<4,>=3.4; python_version == "3.12"
|
55
58
|
Requires-Dist: numpy<2.4,>=2.2; python_version == "3.13"
|
56
59
|
Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.13"
|
57
60
|
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.13"
|
58
61
|
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.13"
|
59
62
|
Requires-Dist: pyomo<7,>=6.8; python_version == "3.13"
|
63
|
+
Requires-Dist: bokeh<4,>=3.4; python_version == "3.13"
|
60
64
|
Dynamic: license-file
|
61
65
|
|
62
66
|
<p align="center">
|
@@ -0,0 +1,16 @@
|
|
1
|
+
bezierv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
bezierv/algorithms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
bezierv/algorithms/nelder_mead.py,sha256=tMKnCPF36qAAOTRV7MBuHxSuvNBgYOzwgsI2sLtmAQQ,5673
|
4
|
+
bezierv/algorithms/non_linear.py,sha256=nNAHbLK1FEdN53nmOg8Np8Fb0PKX7tiHs2JqVPn_VF0,7684
|
5
|
+
bezierv/algorithms/proj_grad.py,sha256=6Ve8vVaJSyN3495r3cPsXheL3Uyho9C5qGDr5MXPfbQ,4716
|
6
|
+
bezierv/algorithms/proj_subgrad.py,sha256=l9CNZuxJMde44Im3dbbobdKK5LVqD1Ev73Pbgy8LeLM,7158
|
7
|
+
bezierv/algorithms/utils.py,sha256=QQecNlGdxMWhDcHPfGEJ_V1nuOIdJdR-2UziKh6N6tw,2396
|
8
|
+
bezierv/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
bezierv/classes/bezierv.py,sha256=zv-Y-gpo2baA33JqP09Qlbqt0P-HxE_PrfVvc2PKqKA,26323
|
10
|
+
bezierv/classes/convolver.py,sha256=h-NewJ__VlL881UQSAUFSamscMEgPBwUxvpb6bHpFD0,2850
|
11
|
+
bezierv/classes/distfit.py,sha256=gw7nye1GDkGrvF1aHAoItCCV-45xUKLdm5Dyev0RFRY,8984
|
12
|
+
bezierv-1.1.1.dist-info/licenses/LICENSE,sha256=VfWiefIi6eo_kZleNp0Plg8A2eUX4D8fDbKtfj4VCp4,1115
|
13
|
+
bezierv-1.1.1.dist-info/METADATA,sha256=FcrW6EzdJvpaJr4k9B-cfCwfdk5f4FTrgSs_939XAOc,4850
|
14
|
+
bezierv-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
bezierv-1.1.1.dist-info/top_level.txt,sha256=AC8zK0YmUeXyPIHtt0EKuHkLrvFBFeHwzp6bjzYnqJI,8
|
16
|
+
bezierv-1.1.1.dist-info/RECORD,,
|
bezierv-1.0.1.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
bezierv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
bezierv/algorithms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
bezierv/algorithms/nelder_mead.py,sha256=uqH3jJS-CG6wblOKP83Rt8-XFJrXZSVJLHaixRKCflw,5478
|
4
|
-
bezierv/algorithms/non_linear.py,sha256=dgwGGe__JJGXQXidmAYjSkDBqSjdr-1HilnUUuPEs8c,7523
|
5
|
-
bezierv/algorithms/proj_grad.py,sha256=BhBAKtvW0_FZrNwinN9VCq27taYshM8zZymu5zX46w0,4622
|
6
|
-
bezierv/algorithms/proj_subgrad.py,sha256=gtk4FXpKziykHHfyfhaRhBhRbdDQErpTDPYqrjo15e0,6962
|
7
|
-
bezierv/algorithms/utils.py,sha256=QQecNlGdxMWhDcHPfGEJ_V1nuOIdJdR-2UziKh6N6tw,2396
|
8
|
-
bezierv/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
bezierv/classes/bezierv.py,sha256=UT-iPn-gKfKi9qMG8lrPi3VRmnV2sjSKQCGazoaUSAc,19696
|
10
|
-
bezierv/classes/convolver.py,sha256=h-NewJ__VlL881UQSAUFSamscMEgPBwUxvpb6bHpFD0,2850
|
11
|
-
bezierv/classes/distfit.py,sha256=EBN_knZ7vinkNjZQf6KSyGfMl0nx0rVqTJR7xUumIDY,8863
|
12
|
-
bezierv-1.0.1.dist-info/licenses/LICENSE,sha256=VfWiefIi6eo_kZleNp0Plg8A2eUX4D8fDbKtfj4VCp4,1115
|
13
|
-
bezierv-1.0.1.dist-info/METADATA,sha256=x3-0fmrohRnqUBTQMN0xfq02YDLM21KGAgudhLgx_Zk,4626
|
14
|
-
bezierv-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
-
bezierv-1.0.1.dist-info/top_level.txt,sha256=AC8zK0YmUeXyPIHtt0EKuHkLrvFBFeHwzp6bjzYnqJI,8
|
16
|
-
bezierv-1.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|