@@ -80,45 +80,69 @@ T binomial_coefficient( unsigned int n, unsigned int k )
80
80
and C(n,i) is the binomial coefficient.
81
81
82
82
@param x The evaluation point, must be in the range [0, 1]
83
- @param coefficients Vector of Bernstein coefficients, size determines polynomial degree
83
+ @param coefficients Pointer to array of Bernstein coefficients
84
+ @param num_coefficients Number of coefficients, determines polynomial degree
84
85
@return The evaluated polynomial value
85
86
86
87
Template parameter T can be double, float, or ceres::Jet<> types.
87
88
*/
88
89
template <typename T>
89
- T evaluate ( const T& x, const std::vector<T>& coefficients )
90
+ T evaluate ( const T& x, const T * const coefficients, const size_t num_coefficients )
90
91
{
91
- if ( coefficients. empty () )
92
+ if ( num_coefficients == 0 )
92
93
throw std::invalid_argument ( " BersteinPolynomial::evaluate: coefficients cannot be empty" );
93
94
94
- const unsigned int n = static_cast <unsigned int >(coefficients. size () - 1 ); // degree
95
- T result = T (0 );
95
+ const unsigned int n = static_cast <unsigned int >(num_coefficients - 1 ); // degree
96
+ T result = T (0.0 );
96
97
97
- // For numerical stability, we'll compute using the recursive formula
98
- // B_{i,n}(x) = (1-x) * B_{i,n-1}(x) + x * B_{i-1,n-1}(x)
99
- // But for simplicity and given the expected low orders (up to 5-6),
100
- // we'll use the direct formula with binomial coefficients
98
+ // Optimized evaluation using incremental power computation
99
+ // This avoids O(n^2) power calculations while being cache-friendly
100
+ const T one_minus_x = T (1.0 ) - x;
101
101
102
- for ( unsigned int i = 0 ; i <= n; ++i )
102
+ // For small degrees (common case), use stack allocation to avoid heap overhead
103
+ if ( n <= 6 ) // Matches our precomputed binomial table
103
104
{
104
- // Compute binomial coefficient C(n,i)
105
- const T binomial_coeff = binomial_coefficient<T>( n, i );
105
+ T x_powers[7 ], one_minus_x_powers[7 ];
106
106
107
- // Compute x^i
108
- T x_pow_i = T (1 );
109
- for ( size_t j = 0 ; j < i; ++j )
110
- x_pow_i *= x;
107
+ x_powers[0 ] = T (1.0 );
108
+ one_minus_x_powers[0 ] = T (1.0 );
111
109
112
- // Compute (1-x)^{n-i}
113
- T one_minus_x_pow = T ( 1 );
114
- const T one_minus_x = T ( 1 ) - x;
115
- for ( size_t j = 0 ; j < (n - i); ++j )
116
- one_minus_x_pow *= one_minus_x;
110
+ for ( unsigned int i = 1 ; i <= n; ++i )
111
+ {
112
+ x_powers[i] = x_powers[i- 1 ] * x;
113
+ one_minus_x_powers[i] = one_minus_x_powers[i- 1 ] * one_minus_x;
114
+ }
117
115
118
- // Add term to result
119
- result += coefficients[i] * binomial_coeff * x_pow_i * one_minus_x_pow;
116
+ for ( unsigned int i = 0 ; i <= n; ++i )
117
+ {
118
+ const T binomial_coeff = binomial_coefficient<T>( n, i );
119
+ const T basis_value = binomial_coeff * x_powers[i] * one_minus_x_powers[n-i];
120
+ result += coefficients[i] * basis_value;
121
+ }
122
+ }
123
+ else
124
+ {
125
+ // For higher degrees, use the original nested loop approach to avoid heap allocation
126
+ // This is rare in practice (comment mentions expected orders up to 5-6)
127
+ for ( unsigned int i = 0 ; i <= n; ++i )
128
+ {
129
+ const T binomial_coeff = binomial_coefficient<T>( n, i );
130
+
131
+ T x_pow_i = T (1 );
132
+ for ( unsigned int j = 0 ; j < i; ++j )
133
+ x_pow_i *= x;
134
+
135
+ T one_minus_x_pow = T (1 );
136
+ for ( unsigned int j = 0 ; j < (n - i); ++j )
137
+ one_minus_x_pow *= one_minus_x;
138
+
139
+ result += coefficients[i] * binomial_coeff * x_pow_i * one_minus_x_pow;
140
+ }
120
141
}
121
142
143
+ // TODO: Consider scalar power tracking optimization (x_power *= x, one_minus_x_power /= one_minus_x)
144
+ // but need to handle edge cases like x=0, x=1 more carefully to avoid division by zero
145
+
122
146
return result;
123
147
}
124
148
@@ -130,23 +154,25 @@ T evaluate( const T& x, const std::vector<T>& coefficients )
130
154
131
155
The input x range [x_min, x_max] is mapped to [0, 1] for the Bernstein representation.
132
156
133
- @param power_coeffs Power series coefficients [a_0, a_1, ..., a_n]
157
+ @param power_coeffs Pointer to array of power series coefficients [a_0, a_1, ..., a_n]
158
+ @param num_coefficients Number of power series coefficients
134
159
@param x_min Minimum x value for the power series domain
135
160
@param x_max Maximum x value for the power series domain
136
161
@return Vector of Bernstein coefficients
137
162
*/
138
163
template <typename T>
139
- std::vector<T> power_series_to_bernstein ( const std::vector<T>& power_coeffs,
164
+ std::vector<T> power_series_to_bernstein ( const T* power_coeffs,
165
+ size_t num_coefficients,
140
166
const T& x_min,
141
167
const T& x_max )
142
168
{
143
- if ( power_coeffs. empty () )
169
+ if ( num_coefficients == 0 )
144
170
throw std::invalid_argument ( " BersteinPolynomial::power_series_to_bernstein: coefficients cannot be empty" );
145
171
146
172
if ( x_max <= x_min )
147
173
throw std::invalid_argument ( " BersteinPolynomial::power_series_to_bernstein: x_max must be greater than x_min" );
148
174
149
- const unsigned int n = static_cast <unsigned int >(power_coeffs. size () - 1 ); // degree
175
+ const unsigned int n = static_cast <unsigned int >(num_coefficients - 1 ); // degree
150
176
std::vector<T> bernstein_coeffs ( n + 1 , T (0 ) );
151
177
152
178
// First, transform the polynomial coefficients from domain [x_min, x_max] to [0, 1]
@@ -198,23 +224,25 @@ std::vector<T> power_series_to_bernstein( const std::vector<T>& power_coeffs,
198
224
Converts from Bernstein representation: P(x) = sum_{i=0}^n b_i * B_{i,n}(x) defined on [0,1]
199
225
to power series representation: P(x) = a_0 + a_1*x + a_2*x^2 + ... + a_n*x^n defined on [x_min, x_max]
200
226
201
- @param bernstein_coeffs Bernstein coefficients [b_0, b_1, ..., b_n]
227
+ @param bernstein_coeffs Pointer to array of Bernstein coefficients [b_0, b_1, ..., b_n]
228
+ @param num_coefficients Number of Bernstein coefficients
202
229
@param x_min Minimum x value for the output power series domain
203
230
@param x_max Maximum x value for the output power series domain
204
231
@return Vector of power series coefficients
205
232
*/
206
233
template <typename T>
207
- std::vector<T> bernstein_to_power_series ( const std::vector<T>& bernstein_coeffs,
234
+ std::vector<T> bernstein_to_power_series ( const T* bernstein_coeffs,
235
+ size_t num_coefficients,
208
236
const T& x_min,
209
237
const T& x_max )
210
238
{
211
- if ( bernstein_coeffs. empty () )
239
+ if ( num_coefficients == 0 )
212
240
throw std::invalid_argument ( " BersteinPolynomial::bernstein_to_power_series: coefficients cannot be empty" );
213
241
214
242
if ( x_max <= x_min )
215
243
throw std::invalid_argument ( " BersteinPolynomial::bernstein_to_power_series: x_max must be greater than x_min" );
216
244
217
- const unsigned int n = static_cast <unsigned int >(bernstein_coeffs. size () - 1 ); // degree
245
+ const unsigned int n = static_cast <unsigned int >(num_coefficients - 1 ); // degree
218
246
219
247
// First convert Bernstein coefficients to power series coefficients on [0,1]
220
248
// Use the inverse of the transformation matrix M where M[i][j] = C(i,j)/C(n,j) for j<=i
@@ -280,28 +308,30 @@ std::vector<T> bernstein_to_power_series( const std::vector<T>& bernstein_coeffs
280
308
Bernstein polynomial.
281
309
282
310
@param x The evaluation point in [x_min, x_max]
283
- @param power_coeffs Power series coefficients
311
+ @param power_coeffs Pointer to array of power series coefficients
312
+ @param num_coefficients Number of power series coefficients
284
313
@param x_min Minimum x value for the power series domain
285
314
@param x_max Maximum x value for the power series domain
286
315
@return The evaluated polynomial value
287
316
*/
288
317
template <typename T>
289
318
T evaluate_power_series_via_bernstein ( const T& x,
290
- const std::vector<T>& power_coeffs,
319
+ const T* power_coeffs,
320
+ size_t num_coefficients,
291
321
const T& x_min,
292
322
const T& x_max )
293
323
{
294
- if ( power_coeffs. empty () )
324
+ if ( num_coefficients == 0 )
295
325
throw std::invalid_argument ( " BersteinPolynomial::evaluate_power_series_via_bernstein: coefficients cannot be empty" );
296
326
297
327
// Convert to Bernstein representation
298
- const std::vector<T> bernstein_coeffs = power_series_to_bernstein ( power_coeffs, x_min, x_max );
328
+ const std::vector<T> bernstein_coeffs = power_series_to_bernstein ( power_coeffs, num_coefficients, x_min, x_max );
299
329
300
330
// Transform x to [0, 1]
301
331
const T x_normalized = (x - x_min) / (x_max - x_min);
302
332
303
333
// Evaluate Bernstein polynomial
304
- return evaluate ( x_normalized, bernstein_coeffs );
334
+ return evaluate ( x_normalized, bernstein_coeffs. data (), bernstein_coeffs. size () );
305
335
}
306
336
307
337
@@ -360,23 +390,47 @@ std::vector<T> fit_bernstein_lls( const std::vector<T>& x_values,
360
390
// Normalize x to [0,1]
361
391
const T x_norm = (x_values[i] - x_min) / x_range;
362
392
const T inv_sigma = T (1 ) / uncertainties[i];
393
+ const T one_minus_x = T (1 ) - x_norm;
363
394
364
395
// Fill row i of design matrix with weighted Bernstein basis functions
365
- for ( unsigned int j = 0 ; j <= degree; ++j )
396
+ // Use optimized power computation for common small degrees
397
+ if ( degree <= 6 )
366
398
{
367
- // Compute B_j,degree(x_norm)
368
- const T binomial_coeff = binomial_coefficient<T>( degree, j );
399
+ T x_powers[7 ], one_minus_x_powers[7 ];
369
400
370
- T x_pow_j = T (1 );
371
- for ( unsigned int k = 0 ; k < j; ++k )
372
- x_pow_j *= x_norm;
401
+ x_powers[0 ] = T (1 );
402
+ one_minus_x_powers[0 ] = T (1 );
373
403
374
- T one_minus_x_pow = T (1 );
375
- const T one_minus_x = T (1 ) - x_norm;
376
- for ( unsigned int k = 0 ; k < (degree - j); ++k )
377
- one_minus_x_pow *= one_minus_x;
404
+ for ( unsigned int k = 1 ; k <= degree; ++k )
405
+ {
406
+ x_powers[k] = x_powers[k-1 ] * x_norm;
407
+ one_minus_x_powers[k] = one_minus_x_powers[k-1 ] * one_minus_x;
408
+ }
378
409
379
- A[i][j] = binomial_coeff * x_pow_j * one_minus_x_pow * inv_sigma;
410
+ for ( unsigned int j = 0 ; j <= degree; ++j )
411
+ {
412
+ const T binomial_coeff = binomial_coefficient<T>( degree, j );
413
+ const T basis_value = binomial_coeff * x_powers[j] * one_minus_x_powers[degree-j];
414
+ A[i][j] = basis_value * inv_sigma;
415
+ }
416
+ }
417
+ else
418
+ {
419
+ // Fallback for higher degrees (rare case)
420
+ for ( unsigned int j = 0 ; j <= degree; ++j )
421
+ {
422
+ const T binomial_coeff = binomial_coefficient<T>( degree, j );
423
+
424
+ T x_pow_j = T (1 );
425
+ for ( unsigned int k = 0 ; k < j; ++k )
426
+ x_pow_j *= x_norm;
427
+
428
+ T one_minus_x_pow = T (1 );
429
+ for ( unsigned int k = 0 ; k < (degree - j); ++k )
430
+ one_minus_x_pow *= one_minus_x;
431
+
432
+ A[i][j] = binomial_coeff * x_pow_j * one_minus_x_pow * inv_sigma;
433
+ }
380
434
}
381
435
382
436
// Weighted y value
@@ -427,6 +481,44 @@ std::vector<T> fit_bernstein_lls( const std::vector<T>& x_values,
427
481
return coeffs;
428
482
}
429
483
484
+
485
+ // Convenience overloads for backward compatibility with std::vector
486
+
487
+ /* * Convenience overload for evaluate() that accepts std::vector */
488
+ template <typename T>
489
+ T evaluate ( const T& x, const std::vector<T>& coefficients )
490
+ {
491
+ return evaluate ( x, coefficients.data (), coefficients.size () );
492
+ }
493
+
494
+ /* * Convenience overload for power_series_to_bernstein() that accepts std::vector */
495
+ template <typename T>
496
+ std::vector<T> power_series_to_bernstein ( const std::vector<T>& power_coeffs,
497
+ const T& x_min,
498
+ const T& x_max )
499
+ {
500
+ return power_series_to_bernstein ( power_coeffs.data (), power_coeffs.size (), x_min, x_max );
501
+ }
502
+
503
+ /* * Convenience overload for bernstein_to_power_series() that accepts std::vector */
504
+ template <typename T>
505
+ std::vector<T> bernstein_to_power_series ( const std::vector<T>& bernstein_coeffs,
506
+ const T& x_min,
507
+ const T& x_max )
508
+ {
509
+ return bernstein_to_power_series ( bernstein_coeffs.data (), bernstein_coeffs.size (), x_min, x_max );
510
+ }
511
+
512
+ /* * Convenience overload for evaluate_power_series_via_bernstein() that accepts std::vector */
513
+ template <typename T>
514
+ T evaluate_power_series_via_bernstein ( const T& x,
515
+ const std::vector<T>& power_coeffs,
516
+ const T& x_min,
517
+ const T& x_max )
518
+ {
519
+ return evaluate_power_series_via_bernstein ( x, power_coeffs.data (), power_coeffs.size (), x_min, x_max );
520
+ }
521
+
430
522
} // namespace BersteinPolynomial
431
523
432
524
#endif // BersteinPolynomial_hpp
0 commit comments