bezierv 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.
- bezierv/__init__.py +0 -0
- bezierv/algorithms/__init__.py +0 -0
- bezierv/algorithms/conv_bezierv.py +113 -0
- bezierv/algorithms/nelder_mead.py +157 -0
- bezierv/algorithms/non_linear.py +167 -0
- bezierv/algorithms/non_linear_solver.py +186 -0
- bezierv/algorithms/proj_grad.py +132 -0
- bezierv/algorithms/proj_subgrad.py +203 -0
- bezierv/algorithms/utils.py +67 -0
- bezierv/classes/__init__.py +0 -0
- bezierv/classes/bezierv.py +579 -0
- bezierv/classes/convolver.py +74 -0
- bezierv/classes/distfit.py +216 -0
- bezierv/tests/__init__.py +0 -0
- bezierv/tests/conf_test.py +0 -0
- bezierv/tests/test_algorithms/test_conv_bezierv.py +0 -0
- bezierv/tests/test_algorithms/test_nelder_mead.py +54 -0
- bezierv/tests/test_algorithms/test_proj_grad.py +63 -0
- bezierv/tests/test_algorithms/test_proj_subgrad.py +65 -0
- bezierv/tests/test_algorithms/test_utils.py +42 -0
- bezierv/tests/test_classes/conftest.py +42 -0
- bezierv/tests/test_classes/test_bezierv.py +0 -0
- bezierv/tests/test_classes/test_convolver.py +36 -0
- bezierv/tests/test_classes/test_distfit.py +34 -0
- bezierv-0.1.0.dist-info/METADATA +32 -0
- bezierv-0.1.0.dist-info/RECORD +29 -0
- bezierv-0.1.0.dist-info/WHEEL +5 -0
- bezierv-0.1.0.dist-info/licenses/LICENSE +21 -0
- bezierv-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,216 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import copy
|
3
|
+
from bezierv.classes.bezierv import Bezierv
|
4
|
+
from typing import List
|
5
|
+
from statsmodels.distributions.empirical_distribution import ECDF
|
6
|
+
from bezierv.algorithms import proj_grad as pg
|
7
|
+
from bezierv.algorithms import non_linear as nl
|
8
|
+
from bezierv.algorithms import nelder_mead as nm
|
9
|
+
from bezierv.algorithms import proj_subgrad as ps
|
10
|
+
from bezierv.algorithms import utils as utils
|
11
|
+
|
12
|
+
|
13
|
+
class DistFit:
|
14
|
+
"""
|
15
|
+
A class to fit a Bezier random variable to empirical data.
|
16
|
+
|
17
|
+
Attributes
|
18
|
+
----------
|
19
|
+
data : List
|
20
|
+
The empirical data to fit the Bezier random variable to.
|
21
|
+
n : int
|
22
|
+
The number of control points minus one for the Bezier curve.
|
23
|
+
init_x : np.array
|
24
|
+
Initial control points for the x-coordinates of the Bezier curve.
|
25
|
+
init_z : np.array
|
26
|
+
Initial control points for the z-coordinates of the Bezier curve.
|
27
|
+
init_t : np.array
|
28
|
+
Initial parameter values.
|
29
|
+
emp_cdf_data : np.array
|
30
|
+
The empirical cumulative distribution function (CDF) data derived from the empirical data.
|
31
|
+
bezierv : Bezierv
|
32
|
+
An instance of the Bezierv class representing the Bezier random variable.
|
33
|
+
m : int
|
34
|
+
The number of empirical data points.
|
35
|
+
mse : float
|
36
|
+
The mean squared error of the fit, initialized to infinity.
|
37
|
+
"""
|
38
|
+
def __init__(self,
|
39
|
+
data: List,
|
40
|
+
n: int=5,
|
41
|
+
init_x: np.array=None,
|
42
|
+
init_z: np.array=None,
|
43
|
+
init_t: np.array=None,
|
44
|
+
emp_cdf_data: np.array=None,
|
45
|
+
method_init_x: str='quantile'
|
46
|
+
):
|
47
|
+
"""
|
48
|
+
Initialize the DistFit class with empirical data and parameters for fitting a Bezier random variable.
|
49
|
+
|
50
|
+
Parameters
|
51
|
+
----------
|
52
|
+
data : List
|
53
|
+
The empirical data to fit the Bezier random variable to.
|
54
|
+
n : int, optional
|
55
|
+
The number of control points minus one for the Bezier curve (default is 5).
|
56
|
+
init_x : np.array, optional
|
57
|
+
Initial control points for the x-coordinates of the Bezier curve (default is None).
|
58
|
+
init_z : np.array, optional
|
59
|
+
Initial control points for the z-coordinates of the Bezier curve (default is None).
|
60
|
+
emp_cdf_data : np.array, optional
|
61
|
+
The empirical cumulative distribution function (CDF) data derived from the empirical data (default is None).
|
62
|
+
method_init_x : str, optional
|
63
|
+
Method to initialize the x-coordinates of the control points (default is 'quantile').
|
64
|
+
"""
|
65
|
+
self.data = np.sort(data)
|
66
|
+
self.n = n
|
67
|
+
self.bezierv = Bezierv(n)
|
68
|
+
self.m = len(data)
|
69
|
+
self.mse = np.inf
|
70
|
+
|
71
|
+
if init_x is None:
|
72
|
+
self.init_x = self.get_controls_x(method_init_x)
|
73
|
+
else:
|
74
|
+
init_x = np.asarray(init_x, dtype=float)
|
75
|
+
self.init_x = init_x
|
76
|
+
|
77
|
+
if init_t is None:
|
78
|
+
self.init_t = utils.get_t(self.n, self.m, self.data, self.bezierv, self.init_x)
|
79
|
+
else:
|
80
|
+
self.init_t = init_t
|
81
|
+
|
82
|
+
if init_z is None:
|
83
|
+
self.init_z = self.get_controls_z()
|
84
|
+
else:
|
85
|
+
init_z = np.asarray(init_z, dtype=float)
|
86
|
+
self.init_z = init_z
|
87
|
+
|
88
|
+
if emp_cdf_data is None:
|
89
|
+
emp_cdf = ECDF(self.data)
|
90
|
+
self.emp_cdf_data = emp_cdf(self.data)
|
91
|
+
else:
|
92
|
+
self.emp_cdf_data = emp_cdf_data
|
93
|
+
|
94
|
+
def fit(self,
|
95
|
+
method: str='projgrad',
|
96
|
+
step_size_PG: float=0.001,
|
97
|
+
max_iter_PG: float=1000,
|
98
|
+
threshold_PG: float=1e-3,
|
99
|
+
step_size_PS: float=0.001,
|
100
|
+
max_iter_PS: int=1000,
|
101
|
+
solver_NL: str='ipopt',
|
102
|
+
max_iter_NM: int=1000) -> Bezierv:
|
103
|
+
"""
|
104
|
+
Fit the bezierv distribution to the data.
|
105
|
+
|
106
|
+
Parameters
|
107
|
+
----------
|
108
|
+
method : str, optional
|
109
|
+
The fitting method to use. Options are 'projgrad', 'nonlinear', 'projsubgrad', or 'neldermead'.
|
110
|
+
Default is 'projgrad'.
|
111
|
+
step_size_PG : float, optional
|
112
|
+
The step size for the projected gradient descent method (default is 0.001).
|
113
|
+
max_iter_PG : int, optional
|
114
|
+
The maximum number of iterations for the projected gradient descent method (default is 1000).
|
115
|
+
threshold_PG : float, optional
|
116
|
+
The convergence threshold for the projected gradient descent method (default is 1e-3).
|
117
|
+
solver_NL : str, optional
|
118
|
+
The solver to use for the nonlinear fitting method (default is 'ipopt').
|
119
|
+
|
120
|
+
Returns
|
121
|
+
-------
|
122
|
+
Bezierv
|
123
|
+
The fitted Bezierv instance with updated control points.
|
124
|
+
"""
|
125
|
+
if method == 'projgrad':
|
126
|
+
self.bezierv, self.mse = pg.fit(self.n,
|
127
|
+
self.m,
|
128
|
+
self.data,
|
129
|
+
self.bezierv,
|
130
|
+
self.init_x,
|
131
|
+
self.init_z,
|
132
|
+
self.init_t,
|
133
|
+
self.emp_cdf_data,
|
134
|
+
step_size_PG,
|
135
|
+
max_iter_PG,
|
136
|
+
threshold_PG)
|
137
|
+
elif method == 'nonlinear':
|
138
|
+
self.bezierv, self.mse = nl.fit(self.n,
|
139
|
+
self.m,
|
140
|
+
self.data,
|
141
|
+
self.bezierv,
|
142
|
+
self.init_x,
|
143
|
+
self.init_z,
|
144
|
+
self.init_t,
|
145
|
+
self.emp_cdf_data,
|
146
|
+
solver_NL)
|
147
|
+
|
148
|
+
elif method == 'neldermead':
|
149
|
+
self.bezierv, self.mse = nm.fit(self.n,
|
150
|
+
self.m,
|
151
|
+
self.data,
|
152
|
+
self.bezierv,
|
153
|
+
self.init_x,
|
154
|
+
self.init_z,
|
155
|
+
self.emp_cdf_data,
|
156
|
+
max_iter_NM)
|
157
|
+
elif method == 'projsubgrad':
|
158
|
+
self.bezierv, self.mse = ps.fit(self.n,
|
159
|
+
self.m,
|
160
|
+
self.data,
|
161
|
+
self.bezierv,
|
162
|
+
self.init_x,
|
163
|
+
self.init_z,
|
164
|
+
self.init_t,
|
165
|
+
self.emp_cdf_data,
|
166
|
+
step_size_PS,
|
167
|
+
max_iter_PS)
|
168
|
+
else:
|
169
|
+
raise ValueError("Method not recognized. Use 'projgrad', 'nonlinear', or 'neldermead'.")
|
170
|
+
|
171
|
+
return copy.copy(self.bezierv), copy.copy(self.mse)
|
172
|
+
|
173
|
+
def get_controls_z(self) -> np.array:
|
174
|
+
"""
|
175
|
+
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
|
+
"""
|
189
|
+
controls_z = np.linspace(0, 1, self.n + 1)
|
190
|
+
return controls_z
|
191
|
+
|
192
|
+
def get_controls_x(self, method: str) -> np.array:
|
193
|
+
"""
|
194
|
+
Compute the control points for the x-coordinates of the Bezier curve.
|
195
|
+
|
196
|
+
'quantile' method is used to determine the control points based on the data quantiles.
|
197
|
+
|
198
|
+
Parameters
|
199
|
+
----------
|
200
|
+
data : np.array
|
201
|
+
The sorted data points.
|
202
|
+
|
203
|
+
Returns
|
204
|
+
-------
|
205
|
+
np.array
|
206
|
+
The control points for the x-coordinates of the Bezier curve.
|
207
|
+
"""
|
208
|
+
if method == 'quantile':
|
209
|
+
controls_x = np.zeros(self.n + 1)
|
210
|
+
for i in range(self.n + 1):
|
211
|
+
controls_x[i] = np.quantile(self.data, i/self.n)
|
212
|
+
elif method == 'uniform':
|
213
|
+
controls_x = np.linspace(np.min(self.data), np.max(self.data), self.n + 1)
|
214
|
+
else:
|
215
|
+
raise ValueError("Method not recognized. Use 'quantile' or 'uniform'.")
|
216
|
+
return controls_x
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from bezierv.algorithms import nelder_mead as nm
|
3
|
+
from bezierv.classes.bezierv import Bezierv
|
4
|
+
|
5
|
+
|
6
|
+
def test_objective_function_zero_for_perfect_fit():
|
7
|
+
n = 1
|
8
|
+
m = 3
|
9
|
+
data = np.array([0.0, 0.5, 1.0])
|
10
|
+
x = np.array([0.0, 1.0])
|
11
|
+
z = np.array([0.0, 1.0])
|
12
|
+
emp_cdf = data.copy()
|
13
|
+
bez = Bezierv(n)
|
14
|
+
|
15
|
+
concat = np.concatenate((x, z))
|
16
|
+
val = nm.objective_function(concat, n, m, data, bez, emp_cdf)
|
17
|
+
assert val < 1e-12, "Perfect fit should give ~0 MSE"
|
18
|
+
|
19
|
+
def test_objective_function_lagrangian_adds_penalty():
|
20
|
+
n = 1
|
21
|
+
m = 3
|
22
|
+
data = np.array([0.0, 0.5, 1.0])
|
23
|
+
x_bad = np.array([1.0, -0.5])
|
24
|
+
z_bad = np.array([0.2, 0.2])
|
25
|
+
emp_cdf = data.copy()
|
26
|
+
bez = Bezierv(n)
|
27
|
+
good = np.concatenate((np.array([0.0, 1.0]), np.array([0.0, 1.0])))
|
28
|
+
bad = np.concatenate((x_bad, z_bad))
|
29
|
+
mse_good = nm.objective_function_lagrangian(good, n, m, data, bez, emp_cdf)
|
30
|
+
mse_bad = nm.objective_function_lagrangian(bad, n, m, data, bez, emp_cdf)
|
31
|
+
assert mse_bad > mse_good + 1.0, "Violating constraints should increase objective markedly"
|
32
|
+
|
33
|
+
def test_fit_returns_low_mse_and_updates_bezier():
|
34
|
+
n = 1
|
35
|
+
m = 3
|
36
|
+
data = np.array([0.0, 0.5, 1.0])
|
37
|
+
init_x = np.array([-0.05, 1.05])
|
38
|
+
init_z = np.array([0, 1])
|
39
|
+
emp_cdf = data.copy()
|
40
|
+
bez = Bezierv(n)
|
41
|
+
|
42
|
+
fitted_bez, mse = nm.fit(
|
43
|
+
n=n,
|
44
|
+
m=m,
|
45
|
+
data=data,
|
46
|
+
bezierv=bez,
|
47
|
+
init_x=init_x,
|
48
|
+
init_z=init_z,
|
49
|
+
emp_cdf_data=emp_cdf
|
50
|
+
)
|
51
|
+
|
52
|
+
assert mse < 1e-5, "Expected near-perfect fit with dummy minimise"
|
53
|
+
np.testing.assert_allclose(fitted_bez.controls_x, np.array([0.0, 1.0]), atol=1e-2)
|
54
|
+
np.testing.assert_allclose(fitted_bez.controls_z, np.array([0.0, 1.0]), atol=1e-2)
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from bezierv.algorithms import proj_grad as pg
|
3
|
+
from bezierv.algorithms import utils as utils
|
4
|
+
from bezierv.classes.bezierv import Bezierv
|
5
|
+
|
6
|
+
|
7
|
+
def test_grad_zero_error_returns_zero_gradient():
|
8
|
+
"""When the Bézier curve matches the empirical CDF exactly, the gradient is 0."""
|
9
|
+
n = 1 # linear curve: z(t) = t
|
10
|
+
t = np.array([0.0, 0.5, 1.0])
|
11
|
+
m = t.size
|
12
|
+
emp_cdf = t.copy() # perfect fit
|
13
|
+
controls_z = np.array([0.0, 1.0])
|
14
|
+
bez = Bezierv(n)
|
15
|
+
|
16
|
+
g = pg.grad(n, m, t, bez, controls_z, emp_cdf)
|
17
|
+
np.testing.assert_allclose(g, np.zeros_like(g), atol=1e-12)
|
18
|
+
|
19
|
+
def test_project_z_clips_sorts_and_enforces_bounds():
|
20
|
+
"""
|
21
|
+
Test the project_z function to ensure it clips, sorts, and enforces bounds correctly.
|
22
|
+
"""
|
23
|
+
raw = np.array([-0.2, 0.9, 0.7, 1.2])
|
24
|
+
projected = pg.project_z(raw.copy())
|
25
|
+
expected = np.array([0.0, 0.7, 0.9, 1.0])
|
26
|
+
np.testing.assert_allclose(projected, expected)
|
27
|
+
np.testing.assert_allclose(pg.project_z(projected.copy()), expected)
|
28
|
+
|
29
|
+
def test_fit_converges_and_returns_low_mse():
|
30
|
+
"""
|
31
|
+
On a toy linear CDF the projected-gradient solver should converge
|
32
|
+
to z = [0,1] and yield (almost) zero MSE.
|
33
|
+
"""
|
34
|
+
n = 1
|
35
|
+
t = np.array([0.0, 0.5, 1.0])
|
36
|
+
m = t.size
|
37
|
+
data = t.copy()
|
38
|
+
emp_cdf = t.copy()
|
39
|
+
init_x = np.array([0.0, 1.0])
|
40
|
+
init_z = np.array([0.2, 0.8])
|
41
|
+
bez = Bezierv(n)
|
42
|
+
|
43
|
+
bezierv , mse = pg.fit(
|
44
|
+
n=n,
|
45
|
+
m=m,
|
46
|
+
data=data,
|
47
|
+
bezierv=bez,
|
48
|
+
init_x=init_x,
|
49
|
+
init_z=init_z,
|
50
|
+
t=t,
|
51
|
+
emp_cdf_data=emp_cdf,
|
52
|
+
step_size=0.2,
|
53
|
+
max_iter=1_000,
|
54
|
+
threshold=1e-6,
|
55
|
+
)
|
56
|
+
|
57
|
+
assert isinstance(bezierv, Bezierv), "Expected Bezierv instance as output"
|
58
|
+
np.testing.assert_allclose(bezierv.controls_z, np.array([0., 1.])), "Expected z control points to be [0, 1]"
|
59
|
+
np.testing.assert_allclose(bezierv.controls_x, np.array([0., 1.])), "Expected x control points to be [0, 1]"
|
60
|
+
assert mse < 1e-6, "Solver failed to reach a near-perfect fit"
|
61
|
+
|
62
|
+
|
63
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from bezierv.algorithms import proj_subgrad as ps
|
3
|
+
from bezierv.algorithms import utils as utils
|
4
|
+
from bezierv.classes.bezierv import Bezierv
|
5
|
+
|
6
|
+
def test_subgrad_zero_when_perfect_fit():
|
7
|
+
n = 1
|
8
|
+
t = np.array([0.0, 0.5, 1.0])
|
9
|
+
m = t.size
|
10
|
+
emp_cdf = t.copy()
|
11
|
+
controls_z = np.array([0.0, 1.0])
|
12
|
+
bez = Bezierv(n)
|
13
|
+
g_x, g_z = ps.subgrad(n, m, bez, t, controls_z, emp_cdf)
|
14
|
+
np.testing.assert_allclose(g_x, 0.0)
|
15
|
+
np.testing.assert_allclose(g_z, 0.0)
|
16
|
+
|
17
|
+
|
18
|
+
def test_project_x_respects_bounds_and_order():
|
19
|
+
data = np.array([2.0, 5.0, 10.0])
|
20
|
+
raw_x = np.array([12.0, 3.0, -7.0])
|
21
|
+
projected = ps.project_x(data, raw_x.copy())
|
22
|
+
expected = np.array([2.0, 3.0, 10.0])
|
23
|
+
np.testing.assert_allclose(projected, expected)
|
24
|
+
np.testing.assert_allclose(ps.project_x(data, projected.copy()), expected)
|
25
|
+
|
26
|
+
|
27
|
+
def test_project_z_behaviour():
|
28
|
+
z_raw = np.array([1.3, -0.1, 0.6])
|
29
|
+
res = ps.project_z(z_raw.copy())
|
30
|
+
np.testing.assert_allclose(res, np.array([0.0, 0.6, 1.0]))
|
31
|
+
np.testing.assert_allclose(ps.project_z(res.copy()), res)
|
32
|
+
|
33
|
+
|
34
|
+
def test_fit_converges_to_linear_solution():
|
35
|
+
"""
|
36
|
+
The full projected-subgradient solver should find the perfect fit on a
|
37
|
+
toy linear CDF — i.e. MSE near 0 and final z ≈ [0,1].
|
38
|
+
"""
|
39
|
+
n = 1
|
40
|
+
t_init = np.array([0.0, 0.5, 1.0])
|
41
|
+
m = t_init.size
|
42
|
+
data = t_init.copy()
|
43
|
+
emp_cdf = t_init.copy()
|
44
|
+
init_x = np.array([2.0, 8.0])
|
45
|
+
init_z = np.array([0.4, 0.7])
|
46
|
+
bez = Bezierv(n)
|
47
|
+
|
48
|
+
_, mse = ps.fit(
|
49
|
+
n=n,
|
50
|
+
m=m,
|
51
|
+
data=data,
|
52
|
+
bezierv=bez,
|
53
|
+
init_x=init_x,
|
54
|
+
init_z=init_z,
|
55
|
+
init_t=t_init,
|
56
|
+
emp_cdf_data=emp_cdf,
|
57
|
+
step_size_x=0.5,
|
58
|
+
step_size_z=0.5,
|
59
|
+
max_iter=200,
|
60
|
+
)
|
61
|
+
|
62
|
+
assert mse < 1e-6, "Expected near-perfect fit"
|
63
|
+
# check the Bézier object got updated to the best values
|
64
|
+
np.testing.assert_allclose(bez.controls_z, np.array([0.0, 1.0]), atol=1e-3)
|
65
|
+
np.testing.assert_allclose(bez.controls_x, data[[0, -1]], atol=1e-3)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pytest
|
3
|
+
from bezierv.classes.bezierv import Bezierv
|
4
|
+
from bezierv.algorithms import utils as utils
|
5
|
+
|
6
|
+
def test_root_find_linear_case():
|
7
|
+
"""
|
8
|
+
For the linear Bézier curve x(t) = t, root_find should return t = data_point.
|
9
|
+
"""
|
10
|
+
n = 1
|
11
|
+
bez = Bezierv(n)
|
12
|
+
controls_x = np.array([0.0, 1.0])
|
13
|
+
for dp in [0.0, 0.25, 0.9, 1.0]:
|
14
|
+
t = utils.root_find(n, bez, controls_x, dp)
|
15
|
+
assert abs(t - dp) < 1e-12, f"Expected {dp}, got {t}"
|
16
|
+
|
17
|
+
|
18
|
+
def test_get_t_returns_vector_of_roots():
|
19
|
+
"""
|
20
|
+
get_t should call root_find for each data point and return the same values
|
21
|
+
when the mapping is linear.
|
22
|
+
"""
|
23
|
+
n = 1
|
24
|
+
m = 5
|
25
|
+
data = np.linspace(0, 1, m)
|
26
|
+
controls_x = np.array([0.0, 1.0])
|
27
|
+
bez = Bezierv(n)
|
28
|
+
|
29
|
+
t_vals = utils.get_t(n, m, data, bez, controls_x)
|
30
|
+
np.testing.assert_allclose(t_vals, data, atol=1e-12)
|
31
|
+
|
32
|
+
|
33
|
+
def test_root_find_errors_outside_interval():
|
34
|
+
"""
|
35
|
+
Raises ValueError when the data point is outside [0,1].
|
36
|
+
root_find should propagate that error.
|
37
|
+
"""
|
38
|
+
n = 1
|
39
|
+
bez = Bezierv(n)
|
40
|
+
controls_x = np.array([0.0, 1.0])
|
41
|
+
with pytest.raises(ValueError):
|
42
|
+
_ = utils.root_find(n, bez, controls_x, 1.5)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import pytest
|
2
|
+
import numpy as np
|
3
|
+
from bezierv.classes.bezierv import Bezierv
|
4
|
+
|
5
|
+
@pytest.fixture
|
6
|
+
def normal_data(scope='package') -> np.array:
|
7
|
+
"""
|
8
|
+
Fixture to create a sample data instance for testing.
|
9
|
+
|
10
|
+
Returns
|
11
|
+
-------
|
12
|
+
np.array
|
13
|
+
A numpy array of sample data points.
|
14
|
+
"""
|
15
|
+
np.random.seed(111)
|
16
|
+
return np.random.normal(loc=0, scale=1, size=100)
|
17
|
+
|
18
|
+
@pytest.fixture
|
19
|
+
def linear_bezierv() -> Bezierv:
|
20
|
+
"""
|
21
|
+
Fixture to create a linear Bezier instance for testing.
|
22
|
+
|
23
|
+
Returns
|
24
|
+
-------
|
25
|
+
Bezierv
|
26
|
+
An instance of the Bezierv class with linear controls.
|
27
|
+
"""
|
28
|
+
return Bezierv(n=1, controls_x=np.array([0.0, 1.0]), controls_z=np.array([0.0, 1.0]))
|
29
|
+
|
30
|
+
@pytest.fixture
|
31
|
+
def two_uniform_bezierv() -> tuple:
|
32
|
+
"""
|
33
|
+
Fixture to create two uniform Bezier random variables for testing convolution.
|
34
|
+
|
35
|
+
Returns
|
36
|
+
-------
|
37
|
+
tuple
|
38
|
+
"""
|
39
|
+
return (
|
40
|
+
Bezierv(n=1, controls_x=np.array([0.0, 1.0]), controls_z=np.array([0.0, 1.0])),
|
41
|
+
Bezierv(n=1, controls_x=np.array([0.0, 1.0]), controls_z=np.array([0.0, 1.0]))
|
42
|
+
)
|
File without changes
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pytest
|
3
|
+
from bezierv.classes.convolver import Convolver
|
4
|
+
from bezierv.classes.bezierv import Bezierv
|
5
|
+
|
6
|
+
def triangular_cdf(z):
|
7
|
+
"""
|
8
|
+
CDF of Z = X+Y with X,Y ~ U(0,1):
|
9
|
+
F_Z(z) = 0 (z ≤ 0)
|
10
|
+
z² / 2 (0 < z < 1)
|
11
|
+
1 - (2 - z)² / 2 (1 ≤ z < 2)
|
12
|
+
1 (z ≥ 2)
|
13
|
+
"""
|
14
|
+
if z <= 0:
|
15
|
+
return 0.0
|
16
|
+
if z < 1:
|
17
|
+
return 0.5 * z * z
|
18
|
+
if z < 2:
|
19
|
+
return 1 - 0.5 * (2 - z) ** 2
|
20
|
+
return 1.0
|
21
|
+
|
22
|
+
def test_cdf_z_matches_triangle(two_uniform_bezierv):
|
23
|
+
bx, by = two_uniform_bezierv
|
24
|
+
conv = Convolver(bx, by, grid=50)
|
25
|
+
|
26
|
+
for z in [0.0, 0.2, 0.8, 1.0, 1.4, 2.0]:
|
27
|
+
val = conv.cdf_z(z)
|
28
|
+
expected = triangular_cdf(z)
|
29
|
+
assert val == pytest.approx(expected, abs=5e-3)
|
30
|
+
|
31
|
+
def test_conv_calls_distfit_and_returns(two_uniform_bezierv):
|
32
|
+
bx, by = two_uniform_bezierv
|
33
|
+
conv = Convolver(bx, by, grid=20)
|
34
|
+
bez_out, mse = conv.conv(method="projgrad")
|
35
|
+
assert isinstance(bez_out, Bezierv)
|
36
|
+
assert bez_out.check_ordering() is True
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pytest
|
3
|
+
from bezierv.classes.distfit import DistFit
|
4
|
+
|
5
|
+
def test_quantile_initial_x(normal_data):
|
6
|
+
d = DistFit(normal_data, n=4)
|
7
|
+
expected = np.quantile(normal_data, np.linspace(0, 1, 5))
|
8
|
+
np.testing.assert_allclose(d.init_x, expected)
|
9
|
+
|
10
|
+
def test_uniform_initial_x(normal_data):
|
11
|
+
d = DistFit(normal_data, n=3, method_init_x="uniform")
|
12
|
+
expected = np.linspace(np.min(normal_data), np.max(normal_data), 4)
|
13
|
+
np.testing.assert_allclose(d.init_x, expected)
|
14
|
+
|
15
|
+
@pytest.mark.parametrize(
|
16
|
+
"method, target_mse",
|
17
|
+
[
|
18
|
+
("projgrad", 1e-2),
|
19
|
+
("nonlinear", 1e-2),
|
20
|
+
("neldermead", 1e-2),
|
21
|
+
("projsubgrad", 1e-2),
|
22
|
+
],
|
23
|
+
)
|
24
|
+
|
25
|
+
def test_fit_dispatch_and_mse(normal_data, method, target_mse):
|
26
|
+
df = DistFit(normal_data, n=3)
|
27
|
+
bez, mse = df.fit(method=method, max_iter_PS = 100, max_iter_PG=100)
|
28
|
+
assert mse <= target_mse
|
29
|
+
assert bez.check_ordering() is True
|
30
|
+
|
31
|
+
def test_bad_method_raises(normal_data):
|
32
|
+
df = DistFit(normal_data)
|
33
|
+
with pytest.raises(ValueError):
|
34
|
+
df.fit(method="does-not-exist")
|
@@ -0,0 +1,32 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: bezierv
|
3
|
+
Version: 0.1.0
|
4
|
+
Author-email: Esteban Leiva <e.leivam@uniandes.edu.co>, "Andrés L. Medaglia" <amedagli@uniandes.edu.co>
|
5
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
6
|
+
Classifier: Programming Language :: Python :: 3.10
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
10
|
+
Requires-Python: <3.14,>=3.10
|
11
|
+
License-File: LICENSE
|
12
|
+
Requires-Dist: numpy<2,>=1.26; python_version == "3.10"
|
13
|
+
Requires-Dist: scipy<1.16,>=1.13; python_version == "3.10"
|
14
|
+
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.10"
|
15
|
+
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.10"
|
16
|
+
Requires-Dist: pyomo<7,>=6.8; python_version == "3.10"
|
17
|
+
Requires-Dist: numpy<2.3,>=1.26; python_version == "3.11"
|
18
|
+
Requires-Dist: scipy<1.17,>=1.13; python_version == "3.11"
|
19
|
+
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.11"
|
20
|
+
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.11"
|
21
|
+
Requires-Dist: pyomo<7,>=6.8; python_version == "3.11"
|
22
|
+
Requires-Dist: numpy<2.3,>=2.1; python_version == "3.12"
|
23
|
+
Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.12"
|
24
|
+
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.12"
|
25
|
+
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.12"
|
26
|
+
Requires-Dist: pyomo<7,>=6.8; python_version == "3.12"
|
27
|
+
Requires-Dist: numpy<2.4,>=2.2; python_version == "3.13"
|
28
|
+
Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.13"
|
29
|
+
Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.13"
|
30
|
+
Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.13"
|
31
|
+
Requires-Dist: pyomo<7,>=6.8; python_version == "3.13"
|
32
|
+
Dynamic: license-file
|
@@ -0,0 +1,29 @@
|
|
1
|
+
bezierv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
bezierv/algorithms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
bezierv/algorithms/conv_bezierv.py,sha256=t74CKomBli98WpVyFWp3oueu7mBuJ4jVYXNj55jmg_Q,4795
|
4
|
+
bezierv/algorithms/nelder_mead.py,sha256=uqH3jJS-CG6wblOKP83Rt8-XFJrXZSVJLHaixRKCflw,5478
|
5
|
+
bezierv/algorithms/non_linear.py,sha256=dgwGGe__JJGXQXidmAYjSkDBqSjdr-1HilnUUuPEs8c,7523
|
6
|
+
bezierv/algorithms/non_linear_solver.py,sha256=03QaD0XJLfRdnhYay_WaspxbRZtDcURjqcl2p-DnOVI,8480
|
7
|
+
bezierv/algorithms/proj_grad.py,sha256=BhBAKtvW0_FZrNwinN9VCq27taYshM8zZymu5zX46w0,4622
|
8
|
+
bezierv/algorithms/proj_subgrad.py,sha256=gtk4FXpKziykHHfyfhaRhBhRbdDQErpTDPYqrjo15e0,6962
|
9
|
+
bezierv/algorithms/utils.py,sha256=QQecNlGdxMWhDcHPfGEJ_V1nuOIdJdR-2UziKh6N6tw,2396
|
10
|
+
bezierv/classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
bezierv/classes/bezierv.py,sha256=UT-iPn-gKfKi9qMG8lrPi3VRmnV2sjSKQCGazoaUSAc,19696
|
12
|
+
bezierv/classes/convolver.py,sha256=h-NewJ__VlL881UQSAUFSamscMEgPBwUxvpb6bHpFD0,2850
|
13
|
+
bezierv/classes/distfit.py,sha256=EBN_knZ7vinkNjZQf6KSyGfMl0nx0rVqTJR7xUumIDY,8863
|
14
|
+
bezierv/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
bezierv/tests/conf_test.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
bezierv/tests/test_algorithms/test_conv_bezierv.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
bezierv/tests/test_algorithms/test_nelder_mead.py,sha256=METAE2eWnfm4bBdGMKtrFEoP_8FpFU4O3uFuiqEsBG0,1788
|
18
|
+
bezierv/tests/test_algorithms/test_proj_grad.py,sha256=eRdwQFn5rfGH1Tu5812hW_7BrPWGO88L6HipexGPW9s,2109
|
19
|
+
bezierv/tests/test_algorithms/test_proj_subgrad.py,sha256=On4-E1A4fS7szXxfokv4BZKY5GIVgbl2YVAtVztwS1A,2077
|
20
|
+
bezierv/tests/test_algorithms/test_utils.py,sha256=JBb9uukGfBsrFOgSngX6FUkPdW2KrY7hJ3DkY_TQz7I,1236
|
21
|
+
bezierv/tests/test_classes/conftest.py,sha256=X94OWG8puTTjueuxjBBQR-fU4f8nN-4QT5NlDFZgTgs,1124
|
22
|
+
bezierv/tests/test_classes/test_bezierv.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
+
bezierv/tests/test_classes/test_convolver.py,sha256=3Z6G0em_owso1gNcGWadHkiHxx65ktFJV_u9Gvop_W8,1118
|
24
|
+
bezierv/tests/test_classes/test_distfit.py,sha256=B-xgIocMzWAfuTxhMTvD2S1QvE-MHXSw8JkoVcziQHA,1103
|
25
|
+
bezierv-0.1.0.dist-info/licenses/LICENSE,sha256=VfWiefIi6eo_kZleNp0Plg8A2eUX4D8fDbKtfj4VCp4,1115
|
26
|
+
bezierv-0.1.0.dist-info/METADATA,sha256=VGF8QhZgQrWtyMz1P_pId6ZZm37P7SgaS17Po2NO8ow,1730
|
27
|
+
bezierv-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
28
|
+
bezierv-0.1.0.dist-info/top_level.txt,sha256=AC8zK0YmUeXyPIHtt0EKuHkLrvFBFeHwzp6bjzYnqJI,8
|
29
|
+
bezierv-0.1.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Esteban Leiva and Andrés L. Medaglia
|
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 @@
|
|
1
|
+
bezierv
|