InterpolatePy 1.0.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.
@@ -0,0 +1,177 @@
1
+ from dataclasses import dataclass
2
+
3
+ import numpy as np
4
+
5
+ from interpolatepy.c_s_smoothing import CubicSmoothingSpline
6
+
7
+
8
+ # Constants to replace magic values
9
+ EPSILON = 1e-6 # Convergence tolerance
10
+
11
+
12
+ @dataclass
13
+ class SplineConfig:
14
+ """Configuration parameters for smoothing spline calculation."""
15
+
16
+ weights: list[float] | np.ndarray | None = None
17
+ v0: float = 0.0
18
+ vn: float = 0.0
19
+ max_iterations: int = 50
20
+ debug: bool = False
21
+
22
+
23
+ def smoothing_spline_with_tolerance(
24
+ t_points: np.ndarray,
25
+ q_points: np.ndarray,
26
+ tolerance: float,
27
+ config: SplineConfig,
28
+ ) -> tuple[CubicSmoothingSpline, float, float, int]:
29
+ """Find a cubic smoothing spline with a maximum approximation error smaller than
30
+ a given tolerance using binary search on the μ parameter.
31
+
32
+ This implements the algorithm for "Smoothing spline with prescribed tolerance".
33
+
34
+ Parameters
35
+ ----------
36
+ t_points : np.ndarray
37
+ Time points [t₀, t₁, t₂, ..., tₙ]
38
+ q_points : np.ndarray
39
+ Position points [q₀, q₁, q₂, ..., qₙ]
40
+ tolerance : float
41
+ Maximum allowed approximation error δ between original and smoothed points
42
+ config : SplineConfig
43
+ Configuration object with optional parameters:
44
+ - weights: Individual point weights [w₀, w₁, ..., wₙ] (None = equal weights)
45
+ - v0: Initial velocity constraint at t₀
46
+ - vn: Final velocity constraint at tₙ
47
+ - max_iterations: Maximum number of iterations for the binary search
48
+ - debug: Whether to print debug information
49
+
50
+ Returns
51
+ -------
52
+ spline : CubicSmoothingSpline
53
+ The final CubicSmoothingSpline object
54
+ mu : float
55
+ The found value of μ parameter
56
+ e_max : float
57
+ The maximum approximation error achieved
58
+ iterations : int
59
+ Number of iterations performed
60
+
61
+ Examples
62
+ --------
63
+ >>> import numpy as np
64
+ >>> from interpolatepy.c_s_smoothing import CubicSmoothingSpline
65
+ >>> # Create sample data
66
+ >>> t = np.linspace(0, 10, 100)
67
+ >>> q = np.sin(t) + 0.1 * np.random.randn(100)
68
+ >>> config = SplineConfig(max_iterations=20)
69
+ >>> # Find spline with tolerance of 0.05
70
+ >>> spline, mu, error, iterations = smoothing_spline_with_tolerance(t, q, 0.05, config)
71
+ >>> print(f"Found spline with μ={mu:.6f}, error={error:.6f} in {iterations} iterations")
72
+
73
+ Notes
74
+ -----
75
+ The algorithm uses binary search to find the optimal μ parameter value that
76
+ produces a smoothing spline with maximum error below the specified tolerance.
77
+ The parameter μ controls the trade-off between smoothness and accuracy, with
78
+ values closer to 0 producing smoother curves and values closer to 1 producing
79
+ more accurate but less smooth curves.
80
+ """
81
+
82
+ # Initialize the search range for μ
83
+ # Note: CubicSmoothingSpline requires 0 < μ ≤ 1, so we start with a small positive value
84
+ lower_bound = EPSILON # lower_bound(0) = small positive number (close to 0)
85
+ upper_bound = 1.0 # upper_bound(0) = 1
86
+
87
+ if config.debug:
88
+ print(f"Starting binary search with tolerance δ={tolerance}")
89
+ print(f"Initial lower_bound={lower_bound}, upper_bound={upper_bound}")
90
+
91
+ # Create initial (fallback) spline with μ=1.0 (most accurate)
92
+ # This ensures we always have a valid spline to return
93
+ default_spline = CubicSmoothingSpline(
94
+ t_points, q_points, mu=1.0, weights=config.weights, v0=config.v0, vn=config.vn, debug=False
95
+ )
96
+ default_error = np.max(np.abs(default_spline.q - default_spline.s))
97
+
98
+ # Keep track of the best solution found so far
99
+ best_spline = default_spline
100
+ best_mu = 1.0
101
+ best_error = default_error
102
+
103
+ # Iteration loop
104
+ for i in range(config.max_iterations):
105
+ # Step 1: Assume μ(i) = (lower_bound(i) + upper_bound(i))/2
106
+ mu = (lower_bound + upper_bound) / 2
107
+
108
+ if config.debug:
109
+ print(f"\nIteration {i + 1}: μ={mu}")
110
+
111
+ # Step 2: Compute spline and maximum error
112
+ try:
113
+ # Create spline with current μ
114
+ spline = CubicSmoothingSpline(
115
+ t_points,
116
+ q_points,
117
+ mu=mu,
118
+ weights=config.weights,
119
+ v0=config.v0,
120
+ vn=config.vn,
121
+ debug=False,
122
+ )
123
+
124
+ # Calculate maximum error e_max(i)
125
+ e_max = np.max(np.abs(spline.q - spline.s))
126
+
127
+ if config.debug:
128
+ print(f" Maximum error e_max({i})={e_max}")
129
+
130
+ # Update best solution if better
131
+ if e_max < best_error:
132
+ best_spline = spline
133
+ best_mu = mu
134
+ best_error = e_max
135
+
136
+ if config.debug:
137
+ print(f" New best solution: μ={mu}, error={e_max}")
138
+
139
+ # Step 3: Update lower_bound and upper_bound according to e_max
140
+ if e_max > tolerance:
141
+ # Error is too large, need more accuracy, increase μ
142
+ lower_bound_new = mu
143
+ upper_bound_new = upper_bound
144
+ if config.debug:
145
+ print(f" Error > tolerance, updating lower_bound({i + 1})={lower_bound_new}")
146
+ else:
147
+ # Error is acceptable, can try more smoothing
148
+ upper_bound_new = mu
149
+ lower_bound_new = lower_bound
150
+ if config.debug:
151
+ print(f" Error ≤ tolerance, updating upper_bound({i + 1})={upper_bound_new}")
152
+
153
+ # Update lower_bound and upper_bound for next iteration
154
+ lower_bound = lower_bound_new
155
+ upper_bound = upper_bound_new
156
+
157
+ # Check for convergence or solution
158
+ if abs(e_max - tolerance) < EPSILON or (
159
+ e_max < tolerance and upper_bound - lower_bound < EPSILON
160
+ ):
161
+ if config.debug:
162
+ print(f"\nConverged to solution with error {e_max} after {i + 1} iterations")
163
+ return spline, mu, e_max, i + 1
164
+
165
+ except ValueError as e:
166
+ # Handle potential errors with invalid μ values
167
+ if config.debug:
168
+ print(f" Error with μ={mu}: {e}")
169
+ # If μ caused an error, try a value closer to 1 (more accuracy)
170
+ lower_bound = mu
171
+
172
+ if config.debug:
173
+ print(f"\nReached maximum iterations ({config.max_iterations})")
174
+ print(f"Best solution found: μ={best_mu}, error={best_error}")
175
+
176
+ # Return the best solution found - this is guaranteed to be non-None now
177
+ return best_spline, best_mu, best_error, config.max_iterations