SSJ API Documentation
Stochastic Simulation in Java
Loading...
Searching...
No Matches
BSpline.java
1/*
2 * Class: BSpline
3 * Description: B-spline
4 * Environment: Java
5 * Software: SSJ
6 * Copyright (C) 2001 Pierre L'Ecuyer and Universite de Montreal
7 * Organization: DIRO, Universite de Montreal
8 * @author
9 * @since
10 *
11 *
12 * Licensed under the Apache License, Version 2.0 (the "License");
13 * you may not use this file except in compliance with the License.
14 * You may obtain a copy of the License at
15 *
16 * http://www.apache.org/licenses/LICENSE-2.0
17 *
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
23 *
24 */
25package umontreal.ssj.functionfit;
26
27import umontreal.ssj.util.Misc;
28import umontreal.ssj.util.RootFinder;
29import umontreal.ssj.functions.MathFunction;
30import umontreal.ssj.functions.MathFunctionWithIntegral;
31import umontreal.ssj.functions.MathFunctionWithDerivative;
32import umontreal.ssj.functions.MathFunctionWithFirstDerivative;
33import umontreal.ssj.functions.MathFunctionUtil;
34import cern.colt.matrix.linalg.Algebra;
35import cern.colt.matrix.DoubleMatrix2D;
36import cern.colt.matrix.DoubleFactory2D;
37import java.util.Arrays;
38import java.util.Random;
39import java.io.*;
40
62public class BSpline implements MathFunction
63
65 // Formula taken from http://www.ibiblio.org/e-notes/Splines/Basis.htm
66 // and http://en.wikipedia.org/wiki/De_Boor_algorithm
67 private double[] x; // x original
68 private double[] y; // y original
69
70 private int degree;
71
72 // variables sur lesquelles on travaille
73 private double[] myX;
74 private double[] myY;
75 private double[] knots;
76
86 public BSpline(final double[] x, final double[] y, final int degree) {
87 if (x.length != y.length)
88 throw new IllegalArgumentException("The arrays x and y must share the same length");
89 this.degree = degree;
90 this.x = x.clone();
91 this.y = y.clone();
92 init(x, y, null);
93 }
94
103 public BSpline(final double[] x, final double[] y, final double[] knots) {
104 if (x.length != y.length)
105 throw new IllegalArgumentException("The arrays x and y must share the same length");
106 if (knots.length <= x.length + 1)
107 throw new IllegalArgumentException("The number of knots must be at least n+2");
108
109 this.x = x.clone();
110 this.y = y.clone();
111 this.knots = knots.clone();
112 init(x, y, knots);
113 }
114
120 public double[] getX() {
121 return myX.clone();
122 }
123
129 public double[] getY() {
130 return myY.clone();
131 }
132
138 public double getMaxKnot() {
139 return knots[knots.length - 1];
140 }
141
147 public double getMinKnot() {
148 return knots[0];
149 }
150
156 public double[] getKnots() {
157 return knots.clone();
158 }
159
172 public static BSpline createInterpBSpline(double[] x, double[] y, int degree) {
173 if (x.length != y.length)
174 throw new IllegalArgumentException("The arrays x and y must share the same length");
175 if (x.length <= degree)
176 throw new IllegalArgumentException("The arrays length must be greater than degree");
177
178 int n = x.length - 1;
179 // compute t : parameters vector uniformly from 0 to 1
180 double[] t = new double[x.length];
181 for (int i = 0; i < t.length; i++) {
182 t[i] = (double) i / (t.length - 1);
183 }
184
185 // compute U : clamped knots vector uniformly from 0 to 1
186 double U[] = new double[x.length + degree + 1];
187 int m = U.length - 1;
188 for (int i = 0; i <= degree; i++)
189 U[i] = 0;
190 for (int i = 1; i < x.length - degree; i++)
191 U[i + degree] = (double) i / (x.length - degree);
192 for (int i = U.length - 1 - degree; i < U.length; i++)
193 U[i] = 1;
194
195 // compute matrix N : made of BSpline coefficients
196 double[][] N = new double[x.length][x.length];
197 for (int i = 0; i < x.length; i++) {
198 N[i] = computeN(U, degree, t[i], x.length);
199 }
200
201 // initialize D : initial points matrix
202 double[][] D = new double[x.length][2];
203 for (int i = 0; i < x.length; i++) {
204 D[i][0] = x[i];
205 D[i][1] = y[i];
206 }
207
208 // solve the linear equation system using colt library
209 DoubleMatrix2D coltN = DoubleFactory2D.dense.make(N);
210 DoubleMatrix2D coltD = DoubleFactory2D.dense.make(D);
211 DoubleMatrix2D coltP;
212 coltP = Algebra.ZERO.solve(coltN, coltD);
213
214 return new BSpline(coltP.viewColumn(0).toArray(), coltP.viewColumn(1).toArray(), U);
215 }
216
234 public static BSpline createApproxBSpline(double[] x, double[] y, int degree, int hp1) {
235 if (x.length != y.length)
236 throw new IllegalArgumentException("The arrays x and y must share the same length");
237 if (x.length <= degree)
238 throw new IllegalArgumentException("The arrays length must be greater than degree");
239
240 int h = hp1 - 1;
241 int n = x.length - 1;
242
243 // compute t : parameters vector uniformly from 0 to 1
244 double[] t = new double[x.length];
245 for (int i = 0; i < t.length; i++) {
246 t[i] = (double) i / n;
247 }
248
249 // compute U : clamped knots vector uniformly from 0 to 1
250 int m = h + degree + 1;
251 double U[] = new double[m + 1];
252 for (int i = 0; i <= degree; i++)
253 U[i] = 0;
254 for (int i = 1; i < hp1 - degree; i++)
255 U[i + degree] = (double) i / (hp1 - degree);
256 for (int i = m - degree; i <= m; i++)
257 U[i] = 1;
258
259 // compute matrix N : composed of BSpline coefficients
260 double[][] N = new double[n + 1][h + 1];
261 for (int i = 0; i < N.length; i++) {
262 N[i] = computeN(U, degree, t[i], h + 1);
263 }
264
265 // initialize D : initial points matrix
266 double[][] D = new double[x.length][2];
267 for (int i = 0; i < x.length; i++) {
268 D[i][0] = x[i];
269 D[i][1] = y[i];
270 }
271
272 // compute Q :
273 double[][] tempQ = new double[x.length][2];
274 for (int k = 1; k < n; k++) {
275 tempQ[k][0] = D[k][0] - N[k][0] * D[0][0] - N[k][h] * D[D.length - 1][0];
276 tempQ[k][1] = D[k][1] - N[k][0] * D[0][1] - N[k][h] * D[D.length - 1][1];
277 }
278 double[][] Q = new double[h - 1][2];
279 for (int i = 1; i < h; i++) {
280 for (int k = 1; k < n; k++) {
281 Q[i - 1][0] += N[k][i] * tempQ[k][0];
282 Q[i - 1][1] += N[k][i] * tempQ[k][1];
283 }
284 }
285
286 // compute N matrix for computation:
287 double[][] N2 = new double[n - 1][h - 1];
288 for (int i = 0; i < N2.length; i++) {
289 for (int j = 0; j < h - 1; j++)
290 N2[i][j] = N[i + 1][j + 1];
291 }
292
293 // solve the linear equation system using colt library
294 DoubleMatrix2D coltQ = DoubleFactory2D.dense.make(Q);
295 DoubleMatrix2D coltN = DoubleFactory2D.dense.make(N2);
296 DoubleMatrix2D coltM = Algebra.ZERO.mult(Algebra.ZERO.transpose(coltN), coltN);
297 DoubleMatrix2D coltP = Algebra.ZERO.solve(coltM, coltQ);
298 double[] pxTemp = coltP.viewColumn(0).toArray();
299 double[] pyTemp = coltP.viewColumn(1).toArray();
300 double[] px = new double[hp1];
301 double[] py = new double[hp1];
302 px[0] = D[0][0];
303 py[0] = D[0][1];
304 px[h] = D[D.length - 1][0];
305 py[h] = D[D.length - 1][1];
306 for (int i = 0; i < pxTemp.length; i++) {
307 px[i + 1] = pxTemp[i];
308 py[i + 1] = pyTemp[i];
309 }
310
311 return new BSpline(px, py, U);
312 // return new BSpline(px, py, degree);
313 }
314
323 double xTemp[] = new double[this.myX.length - 1];
324 double yTemp[] = new double[this.myY.length - 1];
325 for (int i = 0; i < xTemp.length; i++) {
326 xTemp[i] = (myX[i + 1] - myX[i]) * degree / (knots[i + degree + 1] - knots[i + 1]);
327 yTemp[i] = (myY[i + 1] - myY[i]) * degree / (knots[i + degree + 1] - knots[i + 1]);
328 }
329
330 double[] newKnots = new double[knots.length - 2];
331 for (int i = 0; i < newKnots.length; i++) {
332 newKnots[i] = knots[i + 1];
333 }
334
335 // tri pas optimise du tout
336 double xTemp2[] = new double[this.myX.length - 1];
337 double yTemp2[] = new double[this.myY.length - 1];
338 for (int i = 0; i < xTemp.length; i++) {
339 int k = 0;
340 for (int j = 0; j < xTemp.length; j++) {
341 if (xTemp[i] > xTemp[j])
342 k++;
343 }
344 while (xTemp2[k] != 0)
345 k++;
346 xTemp2[k] = xTemp[i];
347 yTemp2[k] = yTemp[i];
348 }
349
350 return new BSpline(xTemp2, yTemp2, newKnots);
351 }
352
364 BSpline bs = this;
365 while (i > 0) {
366 i--;
367 bs = bs.derivativeBSpline();
368 }
369 return bs;
370 }
371
372 public double evaluate(final double u) {
373 final MathFunction xFunction = new MathFunction() {
374 public double evaluate(double t) {
375 return evalX(t) - u;
376 }
377 };
378 // brentDekker may be unstable; using bisection instead
379 // final double t = RootFinder.brentDekker (0, 1, xFunction, 1e-6);
380 final double t = RootFinder.bisection(0, 1, xFunction, 1e-6);
381 return evalY(t);
382 }
383
384 public double integral(double a, double b) {
385 return MathFunctionUtil.simpsonIntegral(this, a, b, 500);
386 }
387
388 public double derivative(double u) {
389 return derivativeBSpline().evaluate(u);
390 }
391
392 public double derivative(double u, int n) {
393 return derivativeBSpline(n).evaluate(u);
394 }
395
396 private void init(double[] x, double[] y, double[] initialKnots) {
397 if (initialKnots == null) {
398 // Cree un vecteur de noeud uniforme entre 0 et 1
399 knots = new double[x.length + degree + 1];
400 for (int i = degree; i < this.knots.length - degree; i++)
401 this.knots[i] = (double) (i - degree) / (knots.length - (2.0 * degree) - 1);
402 for (int i = this.knots.length - degree; i < this.knots.length; i++)
403 this.knots[i] = this.knots[i - 1];
404 for (int i = degree; i > 0; i--)
405 this.knots[i - 1] = this.knots[i];
406
407 // cree notre vecteur interne de Points de controle
408 // ici, aucune modification a faire sur les tableaux originaux
409 myX = x;
410 myY = y;
411 } else {
412 degree = initialKnots.length - x.length - 1;
413
414 // on adapte le tableau des noeuds a notre algorithme
415 // le tableau knot necessite d'avoir degree fois la meme valeur en debut et en
416 // fin de tableau
417 // on adapte la taille des tableau X et Y en consequence afin de continuer a
418 // respecter la condition :
419 // x.length + degree + 1 = this.knots.length
420 // Cette modification n'influence pas le resultat et permet de fermer notre
421 // courbe
422
423 // Compute the number of values that need to be added
424 int iBorneInf = 1, iBorneSup = initialKnots.length - 2;
425 // while(initialKnots[iBorneInf] == initialKnots[0])
426 while (areEqual(initialKnots[iBorneInf], initialKnots[0], 1e-10))
427 iBorneInf++;
428 if (iBorneInf <= degree)
429 iBorneInf = degree - iBorneInf + 1;
430 else
431 iBorneInf = 0;// on a alors iBorneInf valeurs a rajouter en debut de tableau
432
433 // while(initialKnots[iBorneSup] == initialKnots[initialKnots.length-1])
434 while (areEqual(initialKnots[iBorneSup], initialKnots[initialKnots.length - 1], 1e-10))
435 iBorneSup--;
436 if (iBorneSup >= initialKnots.length - 1 - degree)
437 iBorneSup = degree + 1 - (initialKnots.length - 1 - iBorneSup);
438 else
439 iBorneSup = 0; // on a alors iBorneSup valeurs a rajouter en fin de tableau
440
441 // add computed values
442 this.knots = new double[initialKnots.length + iBorneInf + iBorneSup];
443 myX = new double[x.length + iBorneInf + iBorneSup];
444 myY = new double[y.length + iBorneInf + iBorneSup];
445 for (int i = 0; i < iBorneInf; i++) {
446 this.knots[i] = initialKnots[0];
447 myX[i] = x[0];
448 myY[i] = y[0];
449 }
450 for (int i = 0; i < initialKnots.length; i++)
451 this.knots[iBorneInf + i] = initialKnots[i];
452 for (int i = 0; i < x.length; i++) {
453 myX[iBorneInf + i] = x[i];
454 myY[iBorneInf + i] = y[i];
455 }
456 for (int i = 0; i < iBorneSup; i++) {
457 this.knots[this.knots.length - 1 - i] = initialKnots[initialKnots.length - 1];
458 myX[myX.length - 1 - i] = x[x.length - 1];
459 myY[myY.length - 1 - i] = y[y.length - 1];
460 }
461 }
462 }
463
464 public double evalX(double u) {
465 int k = Misc.getTimeInterval(knots, 0, knots.length - 1, u);
466 if (k >= myX.length) // set to last control point
467 k = myX.length - 1;
468
469 double[][] X = new double[degree + 1][myX.length];
470
471 for (int i = k - degree; i <= k; i++)
472 X[0][i] = myX[i];
473
474 for (int j = 1; j <= degree; j++) {
475 for (int i = k - degree + j; i <= k; i++) {
476 double aij = (u - this.knots[i]) / (this.knots[i + 1 + degree - j] - this.knots[i]);
477 X[j][i] = (1 - aij) * X[j - 1][i - 1] + aij * X[j - 1][i];
478 }
479 }
480 return X[degree][k];
481 }
482
483 public double evalY(double u) {
484 int k = Misc.getTimeInterval(knots, 0, knots.length - 1, u);
485 if (k >= myY.length) // set to last control point
486 k = myY.length - 1;
487
488 double[][] Y = new double[degree + 1][myX.length];
489
490 for (int i = k - degree; i <= k; i++)
491 Y[0][i] = myY[i];
492 for (int j = 1; j <= degree; j++) {
493 for (int i = k - degree + j; i <= k; i++) {
494 double aij = (u - this.knots[i]) / (this.knots[i + 1 + degree - j] - this.knots[i]);
495 Y[j][i] = (1 - aij) * Y[j - 1][i - 1] + aij * Y[j - 1][i];
496 }
497 }
498 return Y[degree][k];
499 }
500
509 private static boolean areEqual(double a, double b, double tol) {
510 return Math.abs(a - b) < tol;
511 }
512
513 private static double[] computeN(double[] U, int degree, double u, int np1) {
514 double[] N = new double[np1];
515
516 // special cases at bounds
517 if (areEqual(u, U[0], 1e-10)) {
518 N[0] = 1.0;
519 return N;
520 } else if (areEqual(u, U[U.length - 1], 1e-10)) {
521 N[N.length - 1] = 1.0;
522 return N;
523 }
524
525 // find the knot index k such that U[k]<= u < U[k+1]
526 int k = Misc.getTimeInterval(U, 0, U.length - 1, u);
527
528 N[k] = 1.0;
529 for (int d = 1; d <= degree; d++) {
530 N[k - d] = N[k - d + 1] * (U[k + 1] - u) / (U[k + 1] - U[k - d + 1]);
531 for (int i = k - d + 1; i <= k - 1; i++)
532 N[i] = (u - U[i]) / (U[i + d] - U[i]) * N[i] + ((U[i + d + 1] - u) / (U[i + d + 1] - U[i + 1])) * N[i + 1];
533 N[k] = (u - U[k]) / (U[k + d] - U[k]) * N[k];
534 }
535 return N;
536 }
537
538}
static BSpline createApproxBSpline(double[] x, double[] y, int degree, int hp1)
Returns a B-spline curve of degree degree smoothing , for points.
Definition BSpline.java:234
double integral(double a, double b)
Computes (or estimates) the integral of the function over the interval .
Definition BSpline.java:384
double[] getY()
Returns the coordinates for this spline.
Definition BSpline.java:129
double derivative(double u, int n)
Computes (or estimates) the th derivative of the function at point x.
Definition BSpline.java:392
double getMinKnot()
Returns the knot minimal value.
Definition BSpline.java:147
double[] getX()
Returns the coordinates for this spline.
Definition BSpline.java:120
BSpline derivativeBSpline(int i)
Returns the th derivative B-spline object of the current variable; must be less than the degree of t...
Definition BSpline.java:363
BSpline(final double[] x, final double[] y, final double[] knots)
Constructs a new uniform B-spline with control points at (x[i], y[i]), and knot vector given by the a...
Definition BSpline.java:103
static BSpline createInterpBSpline(double[] x, double[] y, int degree)
Returns a B-spline curve of degree degree interpolating the.
Definition BSpline.java:172
double derivative(double u)
Computes (or estimates) the first derivative of the function at point x.
Definition BSpline.java:388
BSpline(final double[] x, final double[] y, final int degree)
Constructs a new uniform B-spline of degree degree with control points at (x[i], y[i]).
Definition BSpline.java:86
double evaluate(final double u)
Returns the value of the function evaluated at .
Definition BSpline.java:372
double[] getKnots()
Returns an array containing the knot vector .
Definition BSpline.java:156
double getMaxKnot()
Returns the knot maximal value.
Definition BSpline.java:138
BSpline derivativeBSpline()
Returns the derivative B-spline object of the current variable.
Definition BSpline.java:322
Provides utility methods for computing derivatives and integrals of functions.
static double simpsonIntegral(MathFunction func, double a, double b, int numIntervals)
Computes and returns an approximation of the integral of func over , using the Simpson’s method wit...
This class provides numerical methods to solve non-linear equations.
static double bisection(double a, double b, MathFunction f, double tol)
Computes a root of the function in f using the bisection method.
Represents a mathematical function whose th derivative can be computed using derivative(double,...
Represents a mathematical function whose derivative can be computed using derivative(double).
Represents a mathematical function whose integral can be computed by the integral(double,...
This interface should be implemented by classes which represent univariate mathematical functions.