bezierv 1.0.0__tar.gz → 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {bezierv-1.0.0 → bezierv-1.1.1}/.gitignore +1 -1
  2. {bezierv-1.0.0 → bezierv-1.1.1}/PKG-INFO +7 -4
  3. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/algorithms/nelder_mead.py +8 -5
  4. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/algorithms/non_linear.py +39 -32
  5. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/algorithms/proj_grad.py +9 -8
  6. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/algorithms/proj_subgrad.py +16 -13
  7. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/classes/bezierv.py +206 -56
  8. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/classes/distfit.py +9 -15
  9. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv.egg-info/PKG-INFO +7 -4
  10. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv.egg-info/SOURCES.txt +1 -0
  11. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv.egg-info/requires.txt +4 -0
  12. {bezierv-1.0.0 → bezierv-1.1.1}/docs/index.md +26 -0
  13. {bezierv-1.0.0 → bezierv-1.1.1}/docs/reference.md +8 -0
  14. {bezierv-1.0.0 → bezierv-1.1.1}/noxfile.py +4 -0
  15. {bezierv-1.0.0 → bezierv-1.1.1}/pyproject.toml +7 -4
  16. bezierv-1.1.1/scripts/app.py +16 -0
  17. {bezierv-1.0.0 → bezierv-1.1.1}/.github/workflows/ci.yml +0 -0
  18. {bezierv-1.0.0 → bezierv-1.1.1}/.github/workflows/documentation.yml +0 -0
  19. {bezierv-1.0.0 → bezierv-1.1.1}/LICENSE +0 -0
  20. {bezierv-1.0.0 → bezierv-1.1.1}/README.md +0 -0
  21. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/__init__.py +0 -0
  22. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/algorithms/__init__.py +0 -0
  23. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/algorithms/utils.py +0 -0
  24. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/classes/__init__.py +0 -0
  25. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv/classes/convolver.py +0 -0
  26. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv.egg-info/dependency_links.txt +0 -0
  27. {bezierv-1.0.0 → bezierv-1.1.1}/bezierv.egg-info/top_level.txt +0 -0
  28. {bezierv-1.0.0 → bezierv-1.1.1}/docs/assets/logo.png +0 -0
  29. {bezierv-1.0.0 → bezierv-1.1.1}/mkdocs.yml +0 -0
  30. {bezierv-1.0.0 → bezierv-1.1.1}/setup.cfg +0 -0
  31. {bezierv-1.0.0 → bezierv-1.1.1}/tests/__init__.py +0 -0
  32. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_algorithms/test_nelder_mead.py +0 -0
  33. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_algorithms/test_proj_grad.py +0 -0
  34. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_algorithms/test_proj_subgrad.py +0 -0
  35. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_algorithms/test_utils.py +0 -0
  36. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_classes/conftest.py +0 -0
  37. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_classes/test_bezierv.py +0 -0
  38. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_classes/test_convolver.py +0 -0
  39. {bezierv-1.0.0 → bezierv-1.1.1}/tests/test_classes/test_distfit.py +0 -0
@@ -168,4 +168,4 @@ cython_debug/
168
168
  #.idea/
169
169
 
170
170
  # PyPI configuration file
171
- .pypirc
171
+ .pypirc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bezierv
3
- Version: 1.0.0
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>
@@ -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: Homepage, https://github.com/you/your-package
30
- Project-URL: Documentation, https://your-package.readthedocs.io/
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
@@ -43,21 +42,25 @@ Requires-Dist: scipy<1.16,>=1.13; python_version == "3.10"
43
42
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.10"
44
43
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.10"
45
44
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.10"
45
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.10"
46
46
  Requires-Dist: numpy<2.3,>=1.26; python_version == "3.11"
47
47
  Requires-Dist: scipy<1.17,>=1.13; python_version == "3.11"
48
48
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.11"
49
49
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.11"
50
50
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.11"
51
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.11"
51
52
  Requires-Dist: numpy<2.3,>=2.1; python_version == "3.12"
52
53
  Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.12"
53
54
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.12"
54
55
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.12"
55
56
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.12"
57
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.12"
56
58
  Requires-Dist: numpy<2.4,>=2.2; python_version == "3.13"
57
59
  Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.13"
58
60
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.13"
59
61
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.13"
60
62
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.13"
63
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.13"
61
64
  Dynamic: license-file
62
65
 
63
66
  <p align="center">
@@ -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
- The fitted Bezierv object with updated control points.
142
- float
143
- The mean squared error (MSE) of the fit.
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(
@@ -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
- Fit a Bézier random variable to the empirical CDF data using a nonlinear optimization solver.
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
- The number of control points minus one for the Bezier curve.
25
+ The degree of the Bézier curve (number of control points - 1).
23
26
  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
+ 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
- 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.
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
- The name of the solver to use for optimization.
41
+ The name of the solver to use for optimization. Defaults to 'ipopt'.
39
42
 
40
43
  Returns
41
44
  -------
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.
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()
@@ -3,7 +3,7 @@ from bezierv.classes.bezierv import Bezierv
3
3
 
4
4
  def grad(n: int,
5
5
  m:int,
6
- t: float,
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: float,
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 : float or np.array
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
- The updated Bezierv instance with fitted control points.
115
- float
116
- The mean squared error (MSE) of the fit.
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: float,
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.array, np.array
32
- A tuple containing the subgradient with respect to the x control points and the gradient with respect to the z control points.
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: float,
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 : float
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
- The updated Bezierv instance with fitted control points.
178
- float
179
- The mean squared error (MSE) of the fit.
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
@@ -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
- from numpy import tanh, arctanh
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-like
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-like, optional
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 of floats
264
- A tuple (p_x, p_z) where p_x is the evaluated x-coordinate and p_z is the evaluated z-coordinate.
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 of floats
287
- A tuple (p_x, p_z) where p_x is the x-coordinate and p_z is the z-coordinate of the curve at x.
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
@@ -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: float=1000,
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
- data : np.array
201
- The sorted data points.
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.0.0
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>
@@ -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: Homepage, https://github.com/you/your-package
30
- Project-URL: Documentation, https://your-package.readthedocs.io/
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
@@ -43,21 +42,25 @@ Requires-Dist: scipy<1.16,>=1.13; python_version == "3.10"
43
42
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.10"
44
43
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.10"
45
44
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.10"
45
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.10"
46
46
  Requires-Dist: numpy<2.3,>=1.26; python_version == "3.11"
47
47
  Requires-Dist: scipy<1.17,>=1.13; python_version == "3.11"
48
48
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.11"
49
49
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.11"
50
50
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.11"
51
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.11"
51
52
  Requires-Dist: numpy<2.3,>=2.1; python_version == "3.12"
52
53
  Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.12"
53
54
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.12"
54
55
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.12"
55
56
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.12"
57
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.12"
56
58
  Requires-Dist: numpy<2.4,>=2.2; python_version == "3.13"
57
59
  Requires-Dist: scipy<1.17,>=1.14.1; python_version == "3.13"
58
60
  Requires-Dist: matplotlib<3.11,>=3.9; python_version == "3.13"
59
61
  Requires-Dist: statsmodels<0.15,>=0.14.2; python_version == "3.13"
60
62
  Requires-Dist: pyomo<7,>=6.8; python_version == "3.13"
63
+ Requires-Dist: bokeh<4,>=3.4; python_version == "3.13"
61
64
  Dynamic: license-file
62
65
 
63
66
  <p align="center">
@@ -25,6 +25,7 @@ bezierv/classes/distfit.py
25
25
  docs/index.md
26
26
  docs/reference.md
27
27
  docs/assets/logo.png
28
+ scripts/app.py
28
29
  tests/__init__.py
29
30
  tests/test_algorithms/test_nelder_mead.py
30
31
  tests/test_algorithms/test_proj_grad.py
@@ -5,6 +5,7 @@ scipy<1.16,>=1.13
5
5
  matplotlib<3.11,>=3.9
6
6
  statsmodels<0.15,>=0.14.2
7
7
  pyomo<7,>=6.8
8
+ bokeh<4,>=3.4
8
9
 
9
10
  [:python_version == "3.11"]
10
11
  numpy<2.3,>=1.26
@@ -12,6 +13,7 @@ scipy<1.17,>=1.13
12
13
  matplotlib<3.11,>=3.9
13
14
  statsmodels<0.15,>=0.14.2
14
15
  pyomo<7,>=6.8
16
+ bokeh<4,>=3.4
15
17
 
16
18
  [:python_version == "3.12"]
17
19
  numpy<2.3,>=2.1
@@ -19,6 +21,7 @@ scipy<1.17,>=1.14.1
19
21
  matplotlib<3.11,>=3.9
20
22
  statsmodels<0.15,>=0.14.2
21
23
  pyomo<7,>=6.8
24
+ bokeh<4,>=3.4
22
25
 
23
26
  [:python_version == "3.13"]
24
27
  numpy<2.4,>=2.2
@@ -26,3 +29,4 @@ scipy<1.17,>=1.14.1
26
29
  matplotlib<3.11,>=3.9
27
30
  statsmodels<0.15,>=0.14.2
28
31
  pyomo<7,>=6.8
32
+ bokeh<4,>=3.4
@@ -75,6 +75,32 @@ bz_sum.plot_cdf(data) # overlays ECDF and Bézier CDF
75
75
 
76
76
  ---
77
77
 
78
+ ## Interactive Tool
79
+ The `bezierv` package includes an interactive tool for visualizing and editing a Bézier CDF curve. This tool allows you to manipulate the curve's control points in real-time and see how the distribution's shape changes.
80
+
81
+ ```python
82
+ from bezierv.classes.bezierv import InteractiveBezierv
83
+ from bokeh.plotting import curdoc
84
+
85
+ # Define the initial control points for the single curve
86
+ initial_controls_x = [0.0, 0.25, 0.75, 1.0]
87
+ initial_controls_z = [0.0, 0.1, 0.9, 1.0]
88
+
89
+ # Create the manager instance with the initial curve
90
+ manager = InteractiveBezierv(
91
+ controls_x=initial_controls_x,
92
+ controls_z=initial_controls_z
93
+ )
94
+
95
+ # Add the plot layout to the document
96
+ curdoc().add_root(manager.layout)
97
+ curdoc().title = "Interactive Bézier Tool"
98
+ ```
99
+
100
+ Then, run the app from your terminal using the Bokeh server:
101
+ ```python
102
+ bokeh serve --show app.py
103
+ ```
78
104
  ## Next steps
79
105
 
80
106
  - Browse the **[API reference](reference.md)** for the full class and function docs.
@@ -11,6 +11,14 @@
11
11
  show_source: false
12
12
  members_order: source
13
13
 
14
+ ::: bezierv.classes.bezierv.InteractiveBezierv
15
+ handler: python
16
+ options:
17
+ heading_level: 4
18
+ show_root_heading: true
19
+ show_source: false
20
+ members_order: source
21
+
14
22
  ### Distribution fitting
15
23
  ::: bezierv.classes.distfit.DistFit
16
24
  handler: python
@@ -13,6 +13,7 @@ PINNED = {
13
13
  "matplotlib>=3.9,<3.11",
14
14
  "statsmodels>=0.14.2,<0.15",
15
15
  "pyomo>=6.8,<7",
16
+ "bokeh>=3.4,<4",
16
17
  ],
17
18
  "3.11": [
18
19
  "numpy>=1.26,<2.3",
@@ -20,6 +21,7 @@ PINNED = {
20
21
  "matplotlib>=3.9,<3.11",
21
22
  "statsmodels>=0.14.2,<0.15",
22
23
  "pyomo>=6.8,<7",
24
+ "bokeh>=3.4,<4",
23
25
  ],
24
26
  "3.12": [
25
27
  "numpy>=2.1,<2.3",
@@ -27,6 +29,7 @@ PINNED = {
27
29
  "matplotlib>=3.9,<3.11",
28
30
  "statsmodels>=0.14.2,<0.15",
29
31
  "pyomo>=6.8,<7",
32
+ "bokeh>=3.4,<4",
30
33
  ],
31
34
  "3.13": [
32
35
  "numpy>=2.2,<2.4",
@@ -34,6 +37,7 @@ PINNED = {
34
37
  "matplotlib>=3.9,<3.11",
35
38
  "statsmodels>=0.14.2,<0.15",
36
39
  "pyomo>=6.8,<7",
40
+ "bokeh>=3.4,<4",
37
41
  ],
38
42
  }
39
43
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bezierv"
7
- version = "1.0.0"
7
+ version = "1.1.1"
8
8
  description = "This package serves as a computational framework for Bézier distributions."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE" }
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "matplotlib>=3.9,<3.11; python_version == '3.10'",
34
34
  "statsmodels>=0.14.2,<0.15; python_version == '3.10'",
35
35
  "pyomo>=6.8,<7; python_version == '3.10'",
36
+ "bokeh>=3.4,<4; python_version == '3.10'",
36
37
 
37
38
  # --- Python 3.11 ---
38
39
  "numpy>=1.26,<2.3; python_version == '3.11'",
@@ -40,6 +41,7 @@ dependencies = [
40
41
  "matplotlib>=3.9,<3.11; python_version == '3.11'",
41
42
  "statsmodels>=0.14.2,<0.15; python_version == '3.11'",
42
43
  "pyomo>=6.8,<7; python_version == '3.11'",
44
+ "bokeh>=3.4,<4; python_version == '3.11'",
43
45
 
44
46
  # --- Python 3.12 ---
45
47
  "numpy>=2.1,<2.3; python_version == '3.12'",
@@ -47,6 +49,7 @@ dependencies = [
47
49
  "matplotlib>=3.9,<3.11; python_version == '3.12'",
48
50
  "statsmodels>=0.14.2,<0.15; python_version == '3.12'",
49
51
  "pyomo>=6.8,<7; python_version == '3.12'",
52
+ "bokeh>=3.4,<4; python_version == '3.12'",
50
53
 
51
54
  # --- Python 3.13 ---
52
55
  "numpy>=2.2,<2.4; python_version == '3.13'",
@@ -54,9 +57,9 @@ dependencies = [
54
57
  "matplotlib>=3.9,<3.11; python_version == '3.13'",
55
58
  "statsmodels>=0.14.2,<0.15; python_version == '3.13'",
56
59
  "pyomo>=6.8,<7; python_version == '3.13'",
60
+ "bokeh>=3.4,<4; python_version == '3.13'",
57
61
  ]
58
62
 
59
63
  [project.urls]
60
- Homepage = "https://github.com/you/your-package"
61
- Documentation = "https://your-package.readthedocs.io/"
62
- Source = "https://github.com/you/your-package"
64
+ Documentation = "https://estebanleiva.github.io/bezierv/"
65
+ Source = "https://github.com/EstebanLeiva/bezierv"
@@ -0,0 +1,16 @@
1
+ from bezierv.classes.bezierv import InteractiveBezierv
2
+ from bokeh.plotting import curdoc
3
+
4
+ # Define the initial control points for the single curve
5
+ initial_controls_x = [0.0, 0.25, 0.75, 1.0]
6
+ initial_controls_z = [0.0, 0.1, 0.9, 1.0]
7
+
8
+ # Create the manager instance with the initial curve
9
+ manager = InteractiveBezierv(
10
+ controls_x=initial_controls_x,
11
+ controls_z=initial_controls_z
12
+ )
13
+
14
+ # Add the plot layout to the document
15
+ curdoc().add_root(manager.layout)
16
+ curdoc().title = "Single Bézier Tool"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes