UNPKG

16.9 kBJavaScriptView Raw
1function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2
3/**
4 * @copyright 2013 Sonia Keys
5 * @copyright 2016 commenthol
6 * @license MIT
7 * @module interpolation
8 */
9/**
10 * Interp: Chapter 3, Interpolation.
11 *
12 * Len3 and Len5 types
13 *
14 * These types allow interpolation from a table of equidistant x values
15 * and corresponding y values. Since the x values are equidistant,
16 * only the first and last values are supplied as arguments to the
17 * constructors. The interior x values are implicit. All y values must be
18 * supplied however. They are passed as a slice, and the length of y is fixed.
19 * For Len3 it must be 3 and for (Len5 it must be 5.0
20 *
21 * For these Len3 and Len5 functions, Meeus notes the importance of choosing
22 * the 3 or 5 rows of a larger table that will minimize the interpolating
23 * factor n. He does not provide algorithms for doing this however.
24 *
25 * For an example of a selection function, see len3ForInterpolateX. This
26 * was useful for computing Delta T.
27 */
28import base from './base';
29
30var int = Math.trunc;
31
32/**
33 * Error values returned by functions and methods in this package.
34 * Defined here to help testing for specific errors.
35 */
36export var errorNot3 = new Error('Argument y must be length 3');
37export var errorNot4 = new Error('Argument y must be length 4');
38export var errorNot5 = new Error('Argument y must be length 5');
39export var errorNoXRange = new Error('Argument x3 (or x5) cannot equal x1');
40export var errorNOutOfRange = new Error('Interpolating factor n must be in range -1 to 1');
41export var errorNoExtremum = new Error('No extremum in table');
42export var errorExtremumOutside = new Error('Extremum falls outside of table');
43export var errorZeroOutside = new Error('Zero falls outside of table');
44export var errorNoConverge = new Error('Failure to converge');
45
46/**
47 * Len3 allows second difference interpolation.
48 */
49export var Len3 = function () {
50 /**
51 * NewLen3 prepares a Len3 object from a table of three rows of x and y values.
52 *
53 * X values must be equally spaced, so only the first and last are supplied.
54 * X1 must not equal to x3. Y must be a slice of three y values.
55 *
56 * @throws Error
57 * @param {Number} x1 - is the x value corresponding to the first y value of the table.
58 * @param {Number} x3 - is the x value corresponding to the last y value of the table.
59 * @param {Number[]} y - is all y values in the table. y.length should be >= 3.0
60 */
61 function Len3(x1, x3, y) {
62 _classCallCheck(this, Len3);
63
64 if (y.length !== 3) {
65 throw errorNot3;
66 }
67 if (x3 === x1) {
68 throw errorNoXRange;
69 }
70 this.x1 = x1;
71 this.x3 = x3;
72 this.y = y;
73 // differences. (3.1) p. 23
74 this.a = y[1] - y[0];
75 this.b = y[2] - y[1];
76 this.c = this.b - this.a;
77 // other intermediate values
78 this.abSum = this.a + this.b;
79 this.xSum = x3 + x1;
80 this.xDiff = x3 - x1;
81 }
82
83 /**
84 * InterpolateX interpolates for a given x value.
85 */
86
87
88 Len3.prototype.interpolateX = function interpolateX(x) {
89 var n = (2 * x - this.xSum) / this.xDiff;
90 return this.interpolateN(n);
91 };
92
93 /**
94 * InterpolateXStrict interpolates for a given x value,
95 * restricting x to the range x1 to x3 given to the constructor NewLen3.
96 */
97
98
99 Len3.prototype.interpolateXStrict = function interpolateXStrict(x) {
100 var n = (2 * x - this.xSum) / this.xDiff;
101 var y = this.interpolateNStrict(n, true);
102 return y;
103 };
104
105 /**
106 * InterpolateN interpolates for (a given interpolating factor n.
107 *
108 * This is interpolation formula (3.3)
109 *
110 * The interpolation factor n is x-x2 in units of the tabular x interval.
111 * (See Meeus p. 24.)
112 */
113
114
115 Len3.prototype.interpolateN = function interpolateN(n) {
116 return this.y[1] + n * 0.5 * (this.abSum + n * this.c);
117 };
118
119 /**
120 * InterpolateNStrict interpolates for (a given interpolating factor n.
121 *
122 * N is restricted to the range [-1..1] corresponding to the range x1 to x3
123 * given to the constructor of Len3.
124 */
125
126
127 Len3.prototype.interpolateNStrict = function interpolateNStrict(n) {
128 if (n < -1 || n > 1) {
129 throw errorNOutOfRange;
130 }
131 return this.interpolateN(n);
132 };
133
134 /**
135 * Extremum returns the x and y values at the extremum.
136 *
137 * Results are restricted to the range of the table given to the constructor
138 * new Len3.
139 */
140
141
142 Len3.prototype.extremum = function extremum() {
143 if (this.c === 0) {
144 throw errorNoExtremum;
145 }
146 var n = this.abSum / (-2 * this.c); // (3.5), p. 25
147 if (n < -1 || n > 1) {
148 throw errorExtremumOutside;
149 }
150 var x = 0.5 * (this.xSum + this.xDiff * n);
151 var y = this.y[1] - this.abSum * this.abSum / (8 * this.c); // (3.4), p. 25
152 return [x, y];
153 };
154
155 /**
156 * Len3Zero finds a zero of the quadratic function represented by the table.
157 *
158 * That is, it returns an x value that yields y=0.
159 *
160 * Argument strong switches between two strategies for the estimation step.
161 * when iterating to converge on the zero.
162 *
163 * Strong=false specifies a quick and dirty estimate that works well
164 * for gentle curves, but can work poorly or fail on more dramatic curves.
165 *
166 * Strong=true specifies a more sophisticated and thus somewhat more
167 * expensive estimate. However, if the curve has quick changes, This estimate
168 * will converge more reliably and in fewer steps, making it a better choice.
169 *
170 * Results are restricted to the range of the table given to the constructor
171 * NewLen3.
172 */
173
174
175 Len3.prototype.zero = function zero(strong) {
176 var _this = this;
177
178 var f = void 0;
179 if (strong) {
180 // (3.7), p. 27
181 f = function f(n0) {
182 return n0 - (2 * _this.y[1] + n0 * (_this.abSum + _this.c * n0)) / (_this.abSum + 2 * _this.c * n0);
183 };
184 } else {
185 // (3.6), p. 26
186 f = function f(n0) {
187 return -2 * _this.y[1] / (_this.abSum + _this.c * n0);
188 };
189 }
190
191 var _iterate = iterate(0, f),
192 n0 = _iterate[0],
193 ok = _iterate[1];
194
195 if (!ok) {
196 throw errorNoConverge;
197 }
198 if (n0 > 1 || n0 < -1) {
199 throw errorZeroOutside;
200 }
201 return 0.5 * (this.xSum + this.xDiff * n0); // success
202 };
203
204 return Len3;
205}();
206
207/**
208 * Len3ForInterpolateX is a special purpose Len3 constructor.
209 *
210 * Like NewLen3, it takes a table of x and y values, but it is not limited
211 * to tables of 3 rows. An X value is also passed that represents the
212 * interpolation target x value. Len3ForInterpolateX will locate the
213 * appropriate three rows of the table for interpolating for x, and initialize
214 * the Len3 object for those rows.
215 *
216 * @param {Number} x - is the target for interpolation
217 * @param {Number} x1 - is the x value corresponding to the first y value of the table.
218 * @param {Number} xn - is the x value corresponding to the last y value of the table.
219 * @param {Number[]} y - is all y values in the table. y.length should be >= 3.0
220 * @returns {Number} interpolation value
221 */
222export function len3ForInterpolateX(x, x1, xN, y) {
223 var y3 = y;
224 if (y.length > 3) {
225 var interval = (xN - x1) / (y.length - 1);
226 if (interval === 0) {
227 throw errorNoXRange;
228 }
229 var nearestX = int((x - x1) / interval + 0.5);
230 if (nearestX < 1) {
231 nearestX = 1;
232 } else if (nearestX > y.length - 2) {
233 nearestX = y.length - 2;
234 }
235 y3 = y.slice(nearestX - 1, nearestX + 2);
236 xN = x1 + (nearestX + 1) * interval;
237 x1 = x1 + (nearestX - 1) * interval;
238 }
239 return new Len3(x1, xN, y3);
240}
241
242/**
243 * @private
244 * @param {Number} n0
245 * @param {Function} f
246 * @returns {Array}
247 * {Number} n1
248 * {Boolean} ok - if `false` failure to converge
249 */
250export var iterate = function iterate(n0, f) {
251 for (var limit = 0; limit < 50; limit++) {
252 var n1 = f(n0);
253 if (!isFinite(n1) || isNaN(n1)) {
254 break; // failure to converge
255 }
256 if (Math.abs((n1 - n0) / n0) < 1e-15) {
257 return [n1, true]; // success
258 }
259 n0 = n1;
260 }
261 return [0, false]; // failure to converge
262};
263
264/**
265 * Len4Half interpolates a center value from a table of four rows.
266 * @param {Number[]} y - 4 values
267 * @returns {Number} interpolation result
268 */
269export function len4Half(y) {
270 if (y.length !== 4) {
271 throw errorNot4;
272 }
273 // (3.12) p. 32
274 return (9 * (y[1] + y[2]) - y[0] - y[3]) / 16;
275}
276
277/**
278 * Len5 allows fourth Difference interpolation.
279 */
280export var Len5 = function () {
281 /**
282 * NewLen5 prepares a Len5 object from a table of five rows of x and y values.
283 *
284 * X values must be equally spaced, so only the first and last are suppliethis.
285 * X1 must not equal x5. Y must be a slice of five y values.
286 */
287 function Len5(x1, x5, y) {
288 _classCallCheck(this, Len5);
289
290 if (y.length !== 5) {
291 throw errorNot5;
292 }
293 if (x5 === x1) {
294 throw errorNoXRange;
295 }
296 this.x1 = x1;
297 this.x5 = x5;
298 this.y = y;
299 this.y3 = y[2];
300 // differences
301 this.a = y[1] - y[0];
302 this.b = y[2] - y[1];
303 this.c = y[3] - y[2];
304 this.d = y[4] - y[3];
305
306 this.e = this.b - this.a;
307 this.f = this.c - this.b;
308 this.g = this.d - this.c;
309
310 this.h = this.f - this.e;
311 this.j = this.g - this.f;
312
313 this.k = this.j - this.h;
314 // other intermediate values
315 this.xSum = x5 + x1;
316 this.xDiff = x5 - x1;
317 this.interpCoeff = [// (3.8) p. 28
318 this.y3, (this.b + this.c) / 2 - (this.h + this.j) / 12, this.f / 2 - this.k / 24, (this.h + this.j) / 12, this.k / 24];
319 }
320
321 /**
322 * InterpolateX interpolates for (a given x value.
323 */
324
325
326 Len5.prototype.interpolateX = function interpolateX(x) {
327 var n = (4 * x - 2 * this.xSum) / this.xDiff;
328 return this.interpolateN(n);
329 };
330
331 /**
332 * InterpolateXStrict interpolates for a given x value,
333 * restricting x to the range x1 to x5 given to the the constructor NewLen5.
334 */
335
336
337 Len5.prototype.interpolateXStrict = function interpolateXStrict(x) {
338 var n = (4 * x - 2 * this.xSum) / this.xDiff;
339 var y = this.interpolateNStrict(n);
340 return y;
341 };
342
343 /**
344 * InterpolateN interpolates for (a given interpolating factor n.
345 *
346 * The interpolation factor n is x-x3 in units of the tabular x interval.
347 * (See Meeus p. 28.)
348 */
349
350
351 Len5.prototype.interpolateN = function interpolateN(n) {
352 return base.horner.apply(base, [n].concat(this.interpCoeff));
353 };
354
355 /**
356 * InterpolateNStrict interpolates for (a given interpolating factor n.
357 *
358 * N is restricted to the range [-1..1]. This is only half the range given
359 * to the constructor NewLen5, but is the recommendation given on p. 31.0
360 */
361
362
363 Len5.prototype.interpolateNStrict = function interpolateNStrict(n) {
364 if (n < -1 || n > 1) {
365 throw errorNOutOfRange;
366 }
367 return base.horner.apply(base, [n].concat(this.interpCoeff));
368 };
369
370 /**
371 * Extremum returns the x and y values at the extremum.
372 *
373 * Results are restricted to the range of the table given to the constructor
374 * NewLen5. (Meeus actually recommends restricting the range to one unit of
375 * the tabular interval, but that seems a little harsh.)
376 */
377
378
379 Len5.prototype.extremum = function extremum() {
380 // (3.9) p. 29
381 var nCoeff = [6 * (this.b + this.c) - this.h - this.j, 0, 3 * (this.h + this.k), 2 * this.k];
382 var den = this.k - 12 * this.f;
383 if (den === 0) {
384 throw errorExtremumOutside;
385 }
386
387 var _iterate2 = iterate(0, function (n0) {
388 return base.horner.apply(base, [n0].concat(nCoeff)) / den;
389 }),
390 n0 = _iterate2[0],
391 ok = _iterate2[1];
392
393 if (!ok) {
394 throw errorNoConverge;
395 }
396 if (n0 < -2 || n0 > 2) {
397 throw errorExtremumOutside;
398 }
399 var x = 0.5 * this.xSum + 0.25 * this.xDiff * n0;
400 var y = base.horner.apply(base, [n0].concat(this.interpCoeff));
401 return [x, y];
402 };
403
404 /**
405 * Len5Zero finds a zero of the quartic function represented by the table.
406 *
407 * That is, it returns an x value that yields y=0.
408 *
409 * Argument strong switches between two strategies for the estimation step.
410 * when iterating to converge on the zero.
411 *
412 * Strong=false specifies a quick and dirty estimate that works well
413 * for gentle curves, but can work poorly or fail on more dramatic curves.
414 *
415 * Strong=true specifies a more sophisticated and thus somewhat more
416 * expensive estimate. However, if the curve has quick changes, This estimate
417 * will converge more reliably and in fewer steps, making it a better choice.
418 *
419 * Results are restricted to the range of the table given to the constructor
420 * NewLen5.
421 */
422
423
424 Len5.prototype.zero = function zero(strong) {
425 var f = void 0;
426 if (strong) {
427 // (3.11), p. 29
428 var M = this.k / 24;
429 var N = (this.h + this.j) / 12;
430 var P = this.f / 2 - M;
431 var Q = (this.b + this.c) / 2 - N;
432 var numCoeff = [this.y3, Q, P, N, M];
433 var denCoeff = [Q, 2 * P, 3 * N, 4 * M];
434 f = function f(n0) {
435 return n0 - base.horner.apply(base, [n0].concat(numCoeff)) / base.horner.apply(base, [n0].concat(denCoeff));
436 };
437 } else {
438 // (3.10), p. 29
439 var _numCoeff = [-24 * this.y3, 0, this.k - 12 * this.f, -2 * (this.h + this.j), -this.k];
440 var den = 12 * (this.b + this.c) - 2 * (this.h + this.j);
441 f = function f(n0) {
442 return base.horner.apply(base, [n0].concat(_numCoeff)) / den;
443 };
444 }
445
446 var _iterate3 = iterate(0, f),
447 n0 = _iterate3[0],
448 ok = _iterate3[1];
449
450 if (!ok) {
451 throw errorNoConverge;
452 }
453 if (n0 > 2 || n0 < -2) {
454 throw errorZeroOutside;
455 }
456 var x = 0.5 * this.xSum + 0.25 * this.xDiff * n0;
457 return x;
458 };
459
460 return Len5;
461}();
462
463/**
464 * Lagrange performs interpolation with unequally-spaced abscissae.
465 *
466 * Given a table of X and Y values, interpolate a new y value for argument x.
467 *
468 * X values in the table do not have to be equally spaced; they do not even
469 * have to be in order. They must however, be distinct.
470 *
471 * @param {Number} x - x-value of interpolation
472 * @param {Array} table - `[[x0, y0], ... [xN, yN]]` of x, y values
473 * @returns {Number} interpolation result `y` of `x`
474 */
475export function lagrange(x, table) {
476 // method of BASIC program, p. 33.0
477 var sum = 0;
478 table.forEach(function (ti, i) {
479 var xi = ti[0];
480 var prod = 1.0;
481 table.forEach(function (tj, j) {
482 if (i !== j) {
483 var xj = tj[0];
484 prod *= (x - xj) / (xi - xj);
485 }
486 });
487 sum += ti[1] * prod;
488 });
489 return sum;
490}
491
492/**
493 * LagrangePoly uses the formula of Lagrange to produce an interpolating
494 * polynomial.
495 *
496 * X values in the table do not have to be equally spaced; they do not even
497 * have to be in order. They must however, be distinct.
498 *
499 * The returned polynomial will be of degree n-1 where n is the number of rows
500 * in the table. It can be evaluated for x using base.horner.
501 *
502 * @param {Array} table - `[[x0, y0], ... [xN, yN]]`
503 * @returns {Array} - polynomial array
504 */
505export function lagrangePoly(table) {
506 // Method not fully described by Meeus, but needed for (numerical solution
507 // to Example 3.g.
508 var sum = new Array(table.length).fill(0);
509 var prod = new Array(table.length).fill(0);
510 var last = table.length - 1;
511
512 var _loop = function _loop(i) {
513 var xi = table[i][0] || table[i].x || 0;
514 var yi = table[i][1] || table[i].y || 0;
515 prod[last] = 1;
516 var den = 1.0;
517 var n = last;
518 for (var j = 0; j < table.length; j++) {
519 if (i !== j) {
520 var xj = table[j][0] || table[j].x || 0;
521 prod[n - 1] = prod[n] * -xj;
522 for (var k = n; k < last; k++) {
523 prod[k] -= prod[k + 1] * xj;
524 }
525 n--;
526 den *= xi - xj;
527 }
528 }
529 prod.forEach(function (pj, j) {
530 sum[j] += yi * pj / den;
531 });
532 };
533
534 for (var i = 0; i < table.length; i++) {
535 _loop(i);
536 }
537 return sum;
538}
539
540/**
541 * Linear Interpolation of x
542 */
543export function linear(x, x1, xN, y) {
544 var interval = (xN - x1) / (y.length - 1);
545 if (interval === 0) {
546 throw errorNoXRange;
547 }
548 var nearestX = Math.floor((x - x1) / interval);
549 if (nearestX < 0) {
550 nearestX = 0;
551 } else if (nearestX > y.length - 2) {
552 nearestX = y.length - 2;
553 }
554 var y2 = y.slice(nearestX, nearestX + 2);
555 var x01 = x1 + nearestX * interval;
556 return y2[0] + (y[1] - y[0]) * (x - x01) / interval;
557}
558
559export default {
560 errorNot3: errorNot3,
561 errorNot4: errorNot4,
562 errorNot5: errorNot5,
563 errorNoXRange: errorNoXRange,
564 errorNOutOfRange: errorNOutOfRange,
565 errorNoExtremum: errorNoExtremum,
566 errorExtremumOutside: errorExtremumOutside,
567 errorZeroOutside: errorZeroOutside,
568 errorNoConverge: errorNoConverge,
569 Len3: Len3,
570 len3ForInterpolateX: len3ForInterpolateX,
571 iterate: iterate,
572 len4Half: len4Half,
573 Len5: Len5,
574 lagrange: lagrange,
575 lagrangePoly: lagrangePoly,
576 linear: linear
577};
\No newline at end of file