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 ADDED
File without changes
File without changes
@@ -0,0 +1,113 @@
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
@@ -0,0 +1,157 @@
1
+ import numpy as np
2
+ from bezierv.classes.bezierv import Bezierv
3
+ from scipy.optimize import minimize
4
+ import bezierv.algorithms.utils as utils
5
+
6
+ def objective_function(concatenated: np.array,
7
+ n: int,
8
+ m: int,
9
+ data:np.array,
10
+ bezierv: Bezierv,
11
+ emp_cdf_data: np.array) -> float:
12
+ """
13
+ Compute the objective function value for the given control points.
14
+
15
+ This method calculates the sum of squared errors between the Bezier random variable's CDF
16
+ and the empirical CDF data.
17
+
18
+ Parameters
19
+ ----------
20
+ concatenated : np.array
21
+ A concatenated array containing the control points for z and x coordinates.
22
+ The first n+1 elements are the z control points, and the remaining elements are the x control points.
23
+ n : int
24
+ The number of control points minus one for the Bezier curve.
25
+ m : int
26
+ The number of empirical CDF data points.
27
+ data : np.array
28
+ The sorted data points used to fit the Bezier distribution.
29
+ bezierv : Bezierv
30
+ An instance of the Bezierv class representing the Bezier random variable.
31
+ emp_cdf_data : np.array
32
+ The empirical CDF data points used for fitting.
33
+
34
+ Returns
35
+ -------
36
+ float
37
+ The value of the objective function (MSE).
38
+ """
39
+ x = concatenated[0 : n + 1]
40
+ z = concatenated[n + 1:]
41
+ t = utils.get_t(n, m, data, bezierv, x)
42
+ se = 0
43
+ for j in range(m):
44
+ se += (bezierv.poly_z(t[j], z) - emp_cdf_data[j])**2
45
+ return se / m
46
+
47
+ def objective_function_lagrangian(concatenated: np.array,
48
+ n: int,
49
+ m: int,
50
+ data: np.array,
51
+ bezierv: Bezierv,
52
+ emp_cdf_data: np.array,
53
+ penalty_weight: float=1e3) -> float:
54
+ """
55
+ Compute the objective function value for the given control points.
56
+
57
+ This method calculates the sum of squared errors between the Bezier random variable's CDF
58
+ and the empirical CDF data.
59
+
60
+ Parameters
61
+ ----------
62
+ concatenated : np.array
63
+ A concatenated array containing the control points for z and x coordinates.
64
+ The first n+1 elements are the z control points, and the remaining elements are the x control points.
65
+ n : int
66
+ The number of control points minus one for the Bezier curve.
67
+ m : int
68
+ The number of empirical CDF data points.
69
+ data : np.array
70
+ The sorted data points used to fit the Bezier distribution.
71
+ bezierv : Bezierv
72
+ An instance of the Bezierv class representing the Bezier random variable.
73
+ emp_cdf_data : np.array
74
+ The empirical CDF data points used for fitting.
75
+ penalty_weight : float, optional
76
+ The weight for the penalty term in the objective function (default is 1e3).
77
+
78
+ Returns
79
+ -------
80
+ float
81
+ The value of the objective function + penalty (MSE + penalty).
82
+ """
83
+
84
+ x = concatenated[0 : n + 1]
85
+ z = concatenated[n + 1 : ]
86
+
87
+ try:
88
+ t = utils.get_t(n, m, data, bezierv, x)
89
+ except ValueError as e:
90
+ return np.inf
91
+
92
+ se = 0
93
+ for j in range(m):
94
+ se += (bezierv.poly_z(t[j], z) - emp_cdf_data[j])**2
95
+ mse = se / m
96
+
97
+ penalty = 0.0
98
+ penalty += abs(z[0] - 0.0)
99
+ penalty += abs(z[-1] - 1.0)
100
+ delta_zs = np.diff(z)
101
+ delta_xs = np.diff(x)
102
+ penalty += np.sum(abs(np.minimum(0, delta_zs)))
103
+ penalty += np.sum(abs(np.minimum(0, delta_xs)))
104
+ penalty += abs(x[0] - data[0])
105
+ penalty += abs(data[-1] - x[-1])
106
+
107
+ return mse + penalty_weight * penalty
108
+
109
+ def fit(n: int,
110
+ m: int,
111
+ data: np.array,
112
+ bezierv: Bezierv,
113
+ init_x: np.array,
114
+ init_z: np.array,
115
+ emp_cdf_data: np.array,
116
+ max_iter: int
117
+ ) -> Bezierv:
118
+ """
119
+ Fit the Bezier random variable to the empirical CDF data using the Nelder-Mead optimization algorithm.
120
+
121
+ Parameters
122
+ ----------
123
+ n : int
124
+ The number of control points minus one for the Bezier curve.
125
+ m : int
126
+ The number of empirical CDF data points.
127
+ data : np.array
128
+ The sorted data points used to fit the Bezier distribution.
129
+ bezierv : Bezierv
130
+ An instance of the Bezierv class representing the Bezier random variable.
131
+ init_x : np.array
132
+ Initial guess for the x-coordinates of the control points.
133
+ init_z : np.array
134
+ Initial guess for the z-coordinates of the control points.
135
+ emp_cdf_data : np.array
136
+ The empirical CDF data points used for fitting.
137
+
138
+ Returns
139
+ -------
140
+ Bezierv
141
+ The fitted Bezierv object with updated control points.
142
+ float
143
+ The mean squared error (MSE) of the fit.
144
+ """
145
+ start = np.concatenate((init_x, init_z))
146
+ result = minimize(
147
+ fun=objective_function_lagrangian,
148
+ args=(n, m, data, bezierv, emp_cdf_data),
149
+ x0=start,
150
+ method='Nelder-Mead',
151
+ options={'maxiter': max_iter, 'disp': False})
152
+ sol = result.x
153
+ controls_x = sol[0 : n + 1]
154
+ controls_z = sol[n + 1: ]
155
+ bezierv.update_bezierv(controls_x, controls_z)
156
+ mse = objective_function(sol, n, m, data, bezierv, emp_cdf_data)
157
+ return bezierv, mse
@@ -0,0 +1,167 @@
1
+ from pyexpat import model
2
+ import pyomo.environ as pyo
3
+ import numpy as np
4
+ from bezierv.classes.bezierv import Bezierv
5
+ from pyomo.opt import SolverFactory, SolverStatus, TerminationCondition
6
+
7
+ def fit(n: int,
8
+ m: int,
9
+ data: np.array,
10
+ bezierv: Bezierv,
11
+ init_x: np.array,
12
+ init_z: np.array,
13
+ init_t: np.array,
14
+ emp_cdf_data: np.array,
15
+ solver: str) -> Bezierv:
16
+ """
17
+ Fit a Bézier random variable to the empirical CDF data using a nonlinear optimization solver.
18
+
19
+ Parameters
20
+ ----------
21
+ n : int
22
+ The number of control points minus one for the Bezier curve.
23
+ m : int
24
+ The number of empirical CDF data points.
25
+ data : np.array
26
+ The sorted data points used to fit the Bézier distribution.
27
+ bezierv : Bezierv
28
+ An instance of the Bezierv class representing the Bézier random variable.
29
+ init_x : np.array
30
+ Initial guess for the x-coordinates of the control points.
31
+ init_z : np.array
32
+ Initial guess for the z-coordinates of the control points.
33
+ init_t : np.array
34
+ Initial guess for the Bézier 'time' parameters corresponding to the data points.
35
+ emp_cdf_data : np.array
36
+ The empirical CDF data points used for fitting.
37
+ solver : str, optional
38
+ The name of the solver to use for optimization.
39
+
40
+ Returns
41
+ -------
42
+ Bezierv
43
+ The fitted Bezierv object with updated control points.
44
+ float
45
+ The mean squared error (MSE) of the fit.
46
+
47
+ Raises:
48
+ Exception: If the solver fails to find an optimal solution.
49
+
50
+ Notes:
51
+ - The method uses the IPOPT solver for optimization.
52
+ - The control points are constrained to lie within the range of the data.
53
+ - The method ensures that the control points and the Bézier 'time' parameters are sorted.
54
+ - Convexity constraints are applied to the control points and the Bézier 'time' parameters.
55
+ - The first and last control points are fixed to the minimum and maximum of the data, respectively.
56
+ - The first and last Bézier 'time' parameters are fixed to 0 and 1, respectively.
57
+ """
58
+ # Defining the optimization model
59
+ model = pyo.ConcreteModel()
60
+
61
+ # Sets
62
+ model.N = pyo.Set(initialize=list(range(n + 1))) # N = 0,...,i,...,n
63
+ model.N_n = pyo.Set(initialize=list(range(n))) # N = 0,...,i,...,n-1
64
+ model.M = pyo.Set(initialize=list(range(1, m + 1))) # M = 1,...,j,...,m
65
+ model.M_m = pyo.Set(initialize=list(range(1, m))) # M = 1,...,j,...,m-1
66
+
67
+ # Decision variables
68
+ # Control points. Box constraints.
69
+ X_min = data[0];
70
+ X_max = data[-1];
71
+ # var x{i in 0..n} >=X[1], <=X[m];
72
+ # Initialization:
73
+ def init_x_rule(model, i):
74
+ return float(init_x[i])
75
+ model.x = pyo.Var(model.N, within=pyo.Reals, bounds=(X_min, X_max), initialize=init_x_rule)
76
+ # var z{i in 0..n} >=0, <=1;
77
+ # Initialization:
78
+ def init_z_rule(model, i):
79
+ return float(init_z[i])
80
+ model.z = pyo.Var(model.N, within=pyo.NonNegativeReals, bounds=(0, 1), initialize=init_z_rule)
81
+ # Bezier 'time' parameter t for the j-th sample point.
82
+ # var t{j in 1..m} >=0, <= 1;
83
+ # Initialization:
84
+ def init_t_rule(model, j):
85
+ return float(init_t[j - 1]) # j starts from 1, so we access init_t with j-1
86
+ model.t = pyo.Var(model.M, within=pyo.NonNegativeReals, bounds=(0,1), initialize=init_t_rule )
87
+ # Estimated cdf for the j-th sample point.
88
+ # var F_hat{j in 1..m} >=0, <= 1;
89
+ model.F_hat = pyo.Var(model.M, within=pyo.NonNegativeReals, bounds=(0,1) )
90
+
91
+ # Objective function
92
+ # minimize mean_square_error:
93
+ # 1/m * sum {j in 1..m} ( ( j/m - F_hat[j] )^2);
94
+ def mse_rule(model):
95
+ return (1 / m) * sum((emp_cdf_data[j - 1] - model.F_hat[j])**2 for j in model.M)
96
+ model.mse = pyo.Objective(rule=mse_rule, sense=pyo.minimize )
97
+
98
+ # Constraints
99
+ # subject to F_hat_estimates {j in 1..m}:
100
+ # sum{i in 0..n}( comb[i]*t[j]^i*(1-t[j])^(n-i)*z[i] ) = F_hat[j];
101
+ def F_hat_rule(model, j):
102
+ return sum(bezierv.comb[i] * model.t[j]**i * (1 - model.t[j])**(n - i) * model.z[i] for i in model.N ) == model.F_hat[j]
103
+ model.ctr_F_hat = pyo.Constraint(model.M , rule=F_hat_rule)
104
+
105
+ # subject to data_sample {j in 1..m}:
106
+ # sum{i in 0..n}( comb[i]*t[j]^i*(1-t[j])^(n-i)*x[i] ) = X[j];
107
+ def data_sample_rule(model, j):
108
+ return sum(bezierv.comb[i] * model.t[j]**i * (1 - model.t[j])**(n - i) * model.x[i] for i in model.N ) == data[j-1]
109
+ model.ctr_sample = pyo.Constraint(model.M , rule=data_sample_rule)
110
+
111
+ # subject to convexity_x {i in 0..n-1}:
112
+ # x[i] <= x[i+1];
113
+ def convexity_x_rule(model, i):
114
+ return model.x[i] <= model.x[i + 1]
115
+ model.ctr_convexity_x = pyo.Constraint(model.N_n , rule=convexity_x_rule)
116
+
117
+ # subject to convexity_z {i in 0..n-1}:
118
+ # z[i] <= z[i+1];
119
+ def convexity_z_rule(model, i):
120
+ return model.z[i] <= model.z[i + 1]
121
+ model.ctr_convexity_z = pyo.Constraint(model.N_n , rule=convexity_z_rule)
122
+
123
+ # subject to first_control_x:
124
+ # x[0] = X[1];
125
+ model.first_control_x = pyo.Constraint(expr=model.x[0] <= data[0])
126
+ # subject to first_control_z:
127
+ # z[0] = 0;
128
+ model.first_control_z = pyo.Constraint(expr=model.z[0] == 0)
129
+
130
+ # subject to last_control_x:
131
+ # x[n] = X[m];
132
+ model.last_control_x = pyo.Constraint(expr=model.x[n] >= data[-1])
133
+ # subject to last_control_z:
134
+ # z[n] = 1;
135
+ model.last_control_z = pyo.Constraint(expr=model.z[n] == 1)
136
+
137
+ # subject to first_data_t:
138
+ # t[1] = 0;
139
+ model.first_t = pyo.Constraint(expr=model.t[1] == 0)
140
+ # subject to last_data_t:
141
+ # t[m] = 1;
142
+ model.last_t = pyo.Constraint(expr=model.t[m] == 1)
143
+
144
+ delta_z = 0.0001
145
+ delta_x = 0.5
146
+ # Left end: x1 ~= x0, z1 ~= z0
147
+ #model.end_close_z_left = pyo.Constraint(expr = model.z[1] - model.z[0] <= delta_z)
148
+ #model.end_close_x_left = pyo.Constraint(expr = model.x[1] - model.x[0] <= delta_x)
149
+
150
+ # Right end: xn ~= xn-1, zn ~= zn-1
151
+ #model.end_close_x_right = pyo.Constraint(expr = model.x[n] - model.x[n-1] <= delta_x)
152
+ #model.end_close_z_right = pyo.Constraint(expr = model.z[n] - model.z[n-1] <= delta_z)
153
+
154
+ # Set solver
155
+ pyo_solver = SolverFactory(solver)
156
+
157
+ try:
158
+ results = pyo_solver.solve(model, tee=False, timelimit=60)
159
+ if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
160
+ controls_x = np.array([model.x[i]() for i in model.N])
161
+ controls_z = np.array([model.z[i]() for i in model.N])
162
+ mse = model.mse()
163
+ bezierv.update_bezierv(controls_x, controls_z)
164
+ except Exception as e:
165
+ print("NonLinearSolver [fit]: An exception occurred during model evaluation:", e)
166
+
167
+ return bezierv, mse
@@ -0,0 +1,186 @@
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