bezierv 1.0.0__py3-none-any.whl → 1.0.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-1.0.0.dist-info → bezierv-1.0.1.dist-info}/METADATA +3 -4
- bezierv-1.0.1.dist-info/RECORD +16 -0
- bezierv/algorithms/conv_bezierv.py +0 -113
- bezierv/algorithms/non_linear_solver.py +0 -186
- 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 +0 -54
- bezierv/tests/test_algorithms/test_proj_grad.py +0 -63
- bezierv/tests/test_algorithms/test_proj_subgrad.py +0 -65
- bezierv/tests/test_algorithms/test_utils.py +0 -42
- bezierv/tests/test_classes/conftest.py +0 -42
- bezierv/tests/test_classes/test_bezierv.py +0 -0
- bezierv/tests/test_classes/test_convolver.py +0 -36
- bezierv/tests/test_classes/test_distfit.py +0 -34
- bezierv-1.0.0.dist-info/RECORD +0 -29
- {bezierv-1.0.0.dist-info → bezierv-1.0.1.dist-info}/WHEEL +0 -0
- {bezierv-1.0.0.dist-info → bezierv-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {bezierv-1.0.0.dist-info → bezierv-1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bezierv
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.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>
|
@@ -26,9 +26,8 @@ License: MIT License
|
|
26
26
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
27
27
|
SOFTWARE.
|
28
28
|
|
29
|
-
Project-URL:
|
30
|
-
Project-URL:
|
31
|
-
Project-URL: Source, https://github.com/you/your-package
|
29
|
+
Project-URL: Documentation, https://estebanleiva.github.io/bezierv/
|
30
|
+
Project-URL: Source, https://github.com/EstebanLeiva/bezierv
|
32
31
|
Keywords: bezier,distribution,optimization
|
33
32
|
Classifier: Programming Language :: Python :: 3 :: Only
|
34
33
|
Classifier: Programming Language :: Python :: 3.10
|
@@ -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=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,,
|
@@ -1,113 +0,0 @@
|
|
1
|
-
import numpy as np
|
2
|
-
import matplotlib.pyplot as plt
|
3
|
-
from scipy.integrate import quad
|
4
|
-
|
5
|
-
from bezierv.classes.bezierv import Bezierv
|
6
|
-
from bezierv.algorithms.proj_grad import ProjGrad
|
7
|
-
|
8
|
-
class ConvBezier:
|
9
|
-
def __init__(self, bezierv_x: Bezierv, bezierv_y: Bezierv, m: int):
|
10
|
-
"""
|
11
|
-
Initialize a ConvBezier instance for convolving two Bezier curves.
|
12
|
-
|
13
|
-
This constructor sets up the convolution object by storing the provided Bezierv
|
14
|
-
random variables, and creates a new Bezierv instance to hold the convolution
|
15
|
-
result. It also initializes the number of data points to be used in the numerical
|
16
|
-
convolution process.
|
17
|
-
|
18
|
-
Parameters
|
19
|
-
----------
|
20
|
-
bezierv_x : Bezierv
|
21
|
-
A Bezierv instance representing the first Bezier random variable.
|
22
|
-
bezierv_y : Bezierv
|
23
|
-
A Bezierv instance representing the second Bezier random variable.
|
24
|
-
m : int
|
25
|
-
The number of data points to generate for the convolution (more data points
|
26
|
-
induce a better approximation).
|
27
|
-
|
28
|
-
Attributes
|
29
|
-
----------
|
30
|
-
bezierv_x : Bezierv
|
31
|
-
The Bezierv instance representing the first random variable.
|
32
|
-
bezierv_y : Bezierv
|
33
|
-
The Bezierv instance representing the second random variable.
|
34
|
-
bezierv_conv : Bezierv
|
35
|
-
A Bezierv instance that will store the convolution result. Its number of control
|
36
|
-
points is set to the maximum of control points between bezierv_x and bezierv_y.
|
37
|
-
m : int
|
38
|
-
The number of data points used in the convolution.
|
39
|
-
"""
|
40
|
-
self.bezierv_x = bezierv_x
|
41
|
-
self.bezierv_y = bezierv_y
|
42
|
-
self.bezierv_conv = Bezierv(max(bezierv_x.n, bezierv_y.n))
|
43
|
-
self.m = m
|
44
|
-
|
45
|
-
def cdf_z (self, z: float):
|
46
|
-
"""
|
47
|
-
Numerically compute the cumulative distribution function (CDF) at a given z-value for the
|
48
|
-
sum of Bezier random variables.
|
49
|
-
|
50
|
-
This method evaluates the CDF at the specified value z by integrating over the
|
51
|
-
parameter t of the sum of Bezier random variables.
|
52
|
-
|
53
|
-
Parameters
|
54
|
-
----------
|
55
|
-
z : float
|
56
|
-
The value at which to evaluate the cumulative distribution function.
|
57
|
-
|
58
|
-
Returns
|
59
|
-
-------
|
60
|
-
float
|
61
|
-
The computed CDF value at z.
|
62
|
-
"""
|
63
|
-
def integrand(t: float):
|
64
|
-
y_val = z - self.bezierv_x.poly_x(t)
|
65
|
-
if y_val < self.bezierv_y.controls_x[0]:
|
66
|
-
cumul_z = 0
|
67
|
-
elif y_val > self.bezierv_y.controls_x[-1]:
|
68
|
-
cumul_z = self.bezierv_x.pdf_numerator(t)
|
69
|
-
else:
|
70
|
-
y_inv = self.bezierv_y.root_find(y_val)
|
71
|
-
cumul_z = self.bezierv_x.pdf_numerator(t) * self.bezierv_y.eval_t(y_inv)[1]
|
72
|
-
return cumul_z
|
73
|
-
|
74
|
-
result, _ = quad(integrand, 0, 1)
|
75
|
-
return self.bezierv_x.n * result
|
76
|
-
|
77
|
-
def conv(self, step=0.001):
|
78
|
-
"""
|
79
|
-
Numerically compute the convolution of two Bezier random variables.
|
80
|
-
|
81
|
-
This method performs the convolution by:
|
82
|
-
- Defining the lower and upper bounds for the convolution as the sum of the
|
83
|
-
smallest and largest control points of bezierv_x and bezierv_y, respectively.
|
84
|
-
- Generating a set of data points over these bounds.
|
85
|
-
- Evaluating the cumulative distribution function (CDF) at each data point using the
|
86
|
-
cdf_z method.
|
87
|
-
- Initializing a ProjGrad instance with the computed data and empirical CDF values.
|
88
|
-
- Fitting the convolution Bezier random variable via projected gradient descent and updating
|
89
|
-
bezierv_conv.
|
90
|
-
|
91
|
-
Returns
|
92
|
-
-------
|
93
|
-
Bezierv
|
94
|
-
The updated Bezierv instance representing the convolution of the two input Bezier
|
95
|
-
random variables.
|
96
|
-
"""
|
97
|
-
low_bound = self.bezierv_x.controls_x[0] + self.bezierv_y.controls_x[0]
|
98
|
-
up_bound = self.bezierv_x.controls_x[-1] + self.bezierv_y.controls_x[-1]
|
99
|
-
data = np.linspace(low_bound, up_bound, self.m)
|
100
|
-
|
101
|
-
emp_cdf_data = np.zeros(self.m)
|
102
|
-
for i in range(self.m):
|
103
|
-
emp_cdf_data[i] = self.cdf_z(data[i])
|
104
|
-
|
105
|
-
if self.bezierv_x.n == self.bezierv_y.n:
|
106
|
-
controls_x = self.bezierv_x.controls_x + self.bezierv_y.controls_x
|
107
|
-
else:
|
108
|
-
#TODO: Implement the case where the number of control points is different with quantiles
|
109
|
-
raise NotImplementedError
|
110
|
-
proj_grad = ProjGrad(self.bezierv_conv, data, controls_x, emp_cdf_data)
|
111
|
-
controls_z = np.linspace(0, 1, self.bezierv_conv.n + 1)
|
112
|
-
self.bezierv_conv = proj_grad.fit(controls_z, step=step)
|
113
|
-
return self.bezierv_conv
|
@@ -1,186 +0,0 @@
|
|
1
|
-
import pyomo.environ as pyo
|
2
|
-
import numpy as np
|
3
|
-
|
4
|
-
from statsmodels.distributions.empirical_distribution import ECDF
|
5
|
-
from pyomo.opt import SolverFactory, SolverStatus, TerminationCondition
|
6
|
-
|
7
|
-
from bezierv.classes.bezierv import Bezierv
|
8
|
-
|
9
|
-
class NonLinearSolver:
|
10
|
-
def __init__(self, bezierv: Bezierv, data: np.array, emp_cdf_data: np.array=None):
|
11
|
-
"""
|
12
|
-
Initialize the NonLinearSolver instance with a Bezierv object and data to fit.
|
13
|
-
|
14
|
-
Parameters
|
15
|
-
----------
|
16
|
-
bezierv : Bezierv
|
17
|
-
An instance of the Bezierv class representing the Bezier random variable.
|
18
|
-
data : np.array
|
19
|
-
The data points to be fitted; they will be sorted.
|
20
|
-
emp_cdf_data : np.array, optional
|
21
|
-
The empirical CDF values corresponding to the data. If not provided, they
|
22
|
-
are computed using ECDF from the sorted data.
|
23
|
-
|
24
|
-
Attributes
|
25
|
-
----------
|
26
|
-
bezierv : Bezierv
|
27
|
-
The Bezierv object.
|
28
|
-
n : int
|
29
|
-
The degree of the Bezier random variable (inferred from bezierv.n).
|
30
|
-
data : np.array
|
31
|
-
The sorted data points.
|
32
|
-
m : int
|
33
|
-
The number of data points.
|
34
|
-
t_data : np.array
|
35
|
-
The values of t corresponding to the data points on the Bezier random variable.
|
36
|
-
fit_error : float
|
37
|
-
The fitting error, initialized to infinity.
|
38
|
-
emp_cdf_data : np.array
|
39
|
-
The empirical CDF values for the data points.
|
40
|
-
"""
|
41
|
-
self.bezierv = bezierv
|
42
|
-
self.n = bezierv.n
|
43
|
-
self.data = np.sort(data)
|
44
|
-
self.m = len(data)
|
45
|
-
self.mse = np.inf
|
46
|
-
|
47
|
-
if emp_cdf_data is None:
|
48
|
-
emp_cdf = ECDF(data)
|
49
|
-
self.emp_cdf_data = emp_cdf(data)
|
50
|
-
else:
|
51
|
-
self.emp_cdf_data = emp_cdf_data
|
52
|
-
|
53
|
-
def fit(self, solver='ipopt'):
|
54
|
-
"""
|
55
|
-
Fits a Bézier distribution to the given data using n control points.
|
56
|
-
This method sorts the input data, computes the empirical cumulative
|
57
|
-
distribution function (CDF), and sets up an optimization model to
|
58
|
-
fit a Bézier distribution. The control points and the empirical CDF
|
59
|
-
are automatically saved. The method returns the mean square error (MSE)
|
60
|
-
of the fit.
|
61
|
-
|
62
|
-
Parameters:
|
63
|
-
data (list): A list of data points to fit the Bézier distribution.
|
64
|
-
n (int): The number of control points for the Bézier distribution.
|
65
|
-
solver (str): The SolverFactory str in pyomo. Values: "ipopt","knitroampl"
|
66
|
-
|
67
|
-
Returns:
|
68
|
-
float: The mean squared error (MSE) of the fit.
|
69
|
-
|
70
|
-
Raises:
|
71
|
-
Exception: If the solver fails to find an optimal solution.
|
72
|
-
|
73
|
-
Notes:
|
74
|
-
- The method uses the IPOPT solver for optimization.
|
75
|
-
- The control points are constrained to lie within the range of the data.
|
76
|
-
- The method ensures that the control points and the Bézier 'time' parameters are sorted.
|
77
|
-
- Convexity constraints are applied to the control points and the Bézier 'time' parameters.
|
78
|
-
- The first and last control points are fixed to the minimum and maximum of the data, respectively.
|
79
|
-
- The first and last Bézier 'time' parameters are fixed to 0 and 1, respectively.
|
80
|
-
"""
|
81
|
-
# Defining the optimization model
|
82
|
-
model = pyo.ConcreteModel()
|
83
|
-
|
84
|
-
# Sets
|
85
|
-
model.N = pyo.Set(initialize=list(range(self.n + 1))) # N = 0,...,i,...,n
|
86
|
-
model.N_n = pyo.Set(initialize=list(range(self.n))) # N = 0,...,i,...,n-1
|
87
|
-
model.M = pyo.Set(initialize=list(range(1, self.m + 1))) # M = 1,...,j,...,m
|
88
|
-
model.M_m = pyo.Set(initialize=list(range(1, self.m))) # M = 1,...,j,...,m-1
|
89
|
-
|
90
|
-
# Decision variables
|
91
|
-
# Control points. Box constraints.
|
92
|
-
X_min = self.data[0];
|
93
|
-
X_max = self.data[self.m - 1];
|
94
|
-
# var x{i in 0..n} >=X[1], <=X[m];
|
95
|
-
# Initialization: let {i in 1..n-1} x[i] := X[1]+i*(X[m]-X[1])/n;
|
96
|
-
def init_x_rule(model, i):
|
97
|
-
return X_min + i*(X_max - X_min) / self.n
|
98
|
-
model.x = pyo.Var(model.N, within=pyo.Reals, bounds=(X_min, X_max), initialize=init_x_rule)
|
99
|
-
# var z{i in 0..n} >=0, <=1;
|
100
|
-
# Initialization: let {i in 1..n-1} z[i] := i*(1/n);
|
101
|
-
def init_z_rule(model, i):
|
102
|
-
return i*(1 / self.n)
|
103
|
-
model.z = pyo.Var(model.N, within=pyo.NonNegativeReals, bounds=(0, 1), initialize=init_z_rule)
|
104
|
-
# Bezier 'time' parameter t for the j-th sample point.
|
105
|
-
# var t{j in 1..m} >=0, <= 1;
|
106
|
-
# Initialization: let {j in 2..m-1} t[j] := j*(1/m);
|
107
|
-
def init_t_rule(model, j):
|
108
|
-
return j*(1 / self.m)
|
109
|
-
model.t = pyo.Var(model.M, within=pyo.NonNegativeReals, bounds=(0,1), initialize=init_t_rule )
|
110
|
-
# Estimated cdf for the j-th sample point.
|
111
|
-
# var F_hat{j in 1..m} >=0, <= 1;
|
112
|
-
model.F_hat = pyo.Var(model.M, within=pyo.NonNegativeReals, bounds=(0,1) )
|
113
|
-
|
114
|
-
# Objective function
|
115
|
-
# minimize mean_square_error:
|
116
|
-
# 1/m * sum {j in 1..m} ( ( j/m - F_hat[j] )^2);
|
117
|
-
def mse_rule(model):
|
118
|
-
return (1 / self.m) * sum(((j / self.m) - model.F_hat[j])**2 for j in model.M)
|
119
|
-
model.mse = pyo.Objective(rule=mse_rule, sense=pyo.minimize )
|
120
|
-
|
121
|
-
# Constraints
|
122
|
-
# subject to F_hat_estimates {j in 1..m}:
|
123
|
-
# sum{i in 0..n}( comb[i]*t[j]^i*(1-t[j])^(n-i)*z[i] ) = F_hat[j];
|
124
|
-
def F_hat_rule(model, j):
|
125
|
-
return sum(self.bezierv.comb[i] * model.t[j]**i * (1 - model.t[j])**(self.n - i) * model.z[i] for i in model.N ) == model.F_hat[j]
|
126
|
-
model.ctr_F_hat = pyo.Constraint(model.M , rule=F_hat_rule)
|
127
|
-
|
128
|
-
# subject to data_sample {j in 1..m}:
|
129
|
-
# sum{i in 0..n}( comb[i]*t[j]^i*(1-t[j])^(n-i)*x[i] ) = X[j];
|
130
|
-
def data_sample_rule(model, j):
|
131
|
-
return sum(self.bezierv.comb[i] * model.t[j]**i * (1 - model.t[j])**(self.n - i) * model.x[i] for i in model.N ) == self.data[j-1]
|
132
|
-
model.ctr_sample = pyo.Constraint(model.M , rule=data_sample_rule)
|
133
|
-
|
134
|
-
# subject to sorted_t{j in 1..m-1}:
|
135
|
-
# t[j] <= t[j+1];
|
136
|
-
#def sorted_t_rule(model, j):
|
137
|
-
# return model.t[j] <= model.t[j + 1]
|
138
|
-
#model.ctr_sorted_t = pyo.Constraint(model.M_m , rule=sorted_t_rule)
|
139
|
-
|
140
|
-
# subject to convexity_x {i in 0..n-1}:
|
141
|
-
# x[i] <= x[i+1];
|
142
|
-
def convexity_x_rule(model, i):
|
143
|
-
return model.x[i] <= model.x[i + 1]
|
144
|
-
model.ctr_convexity_x = pyo.Constraint(model.N_n , rule=convexity_x_rule)
|
145
|
-
|
146
|
-
# subject to convexity_z {i in 0..n-1}:
|
147
|
-
# z[i] <= z[i+1];
|
148
|
-
def convexity_z_rule(model, i):
|
149
|
-
return model.z[i] <= model.z[i + 1]
|
150
|
-
model.ctr_convexity_z = pyo.Constraint(model.N_n , rule=convexity_z_rule)
|
151
|
-
|
152
|
-
# subject to first_control_x:
|
153
|
-
# x[0] = X[1];
|
154
|
-
model.first_control_x = pyo.Constraint(expr=model.x[0] <= self.data[0]) #==
|
155
|
-
# subject to first_control_z:
|
156
|
-
# z[0] = 0;
|
157
|
-
model.first_control_z = pyo.Constraint(expr=model.z[0] == 0)
|
158
|
-
|
159
|
-
# subject to last_control_x:
|
160
|
-
# x[n] = X[m];
|
161
|
-
model.last_control_x = pyo.Constraint(expr=model.x[self.n] >= self.data[self.m - 1]) # ==
|
162
|
-
# subject to last_control_z:
|
163
|
-
# z[n] = 1;
|
164
|
-
model.last_control_z = pyo.Constraint(expr=model.z[self.n] == 1)
|
165
|
-
|
166
|
-
# subject to first_data_t:
|
167
|
-
# t[1] = 0;
|
168
|
-
model.first_t = pyo.Constraint(expr=model.t[1] == 0)
|
169
|
-
# subject to last_data_t:
|
170
|
-
# t[m] = 1;
|
171
|
-
model.last_t = pyo.Constraint(expr=model.t[self.m] == 1)
|
172
|
-
|
173
|
-
# Set solver
|
174
|
-
pyo_solver = SolverFactory(solver)
|
175
|
-
|
176
|
-
try:
|
177
|
-
results = pyo_solver.solve(model, tee=False, timelimit=60)
|
178
|
-
if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
|
179
|
-
controls_x = np.array([model.x[i]() for i in model.N])
|
180
|
-
controls_z = np.array([model.z[i]() for i in model.N])
|
181
|
-
self.mse = model.mse()
|
182
|
-
self.bezierv.update_bezierv(controls_x, controls_z, (self.data[0], self.data[-1]))
|
183
|
-
except Exception as e:
|
184
|
-
print("NonLinearSolver [fit]: An exception occurred during model evaluation:", e)
|
185
|
-
|
186
|
-
return self.bezierv
|
bezierv/tests/__init__.py
DELETED
File without changes
|
bezierv/tests/conf_test.py
DELETED
File without changes
|
File without changes
|
@@ -1,54 +0,0 @@
|
|
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)
|
@@ -1,63 +0,0 @@
|
|
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
|
-
|
@@ -1,65 +0,0 @@
|
|
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)
|
@@ -1,42 +0,0 @@
|
|
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)
|
@@ -1,42 +0,0 @@
|
|
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
|
@@ -1,36 +0,0 @@
|
|
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
|
@@ -1,34 +0,0 @@
|
|
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")
|
bezierv-1.0.0.dist-info/RECORD
DELETED
@@ -1,29 +0,0 @@
|
|
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-1.0.0.dist-info/licenses/LICENSE,sha256=VfWiefIi6eo_kZleNp0Plg8A2eUX4D8fDbKtfj4VCp4,1115
|
26
|
-
bezierv-1.0.0.dist-info/METADATA,sha256=hzqB1McgWf-yUlKqHtBTsY3gpUjv6PN-JQ7_KNQCQG0,4679
|
27
|
-
bezierv-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
28
|
-
bezierv-1.0.0.dist-info/top_level.txt,sha256=AC8zK0YmUeXyPIHtt0EKuHkLrvFBFeHwzp6bjzYnqJI,8
|
29
|
-
bezierv-1.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|