-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.ts
185 lines (173 loc) · 5.78 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
* Port of SLATEC function DPCHST
* @param arg1
* @param arg2
* @returns 1 if arg1 is the same sign as arg2, 0 if either of them is zero, -1 if arg1 is a different sign from arg2
*/
export function dpchst(arg1: number, arg2: number) {
if (arg1 === 0 || arg2 === 0) {
return 0;
}
if (arg1 > 0) {
return arg2 > 0 ? 1 : -1;
}
return arg2 > 0 ? -1 : 1;
}
/**
* Port of SLATEC function DPCHIM: calculates dy/dx at each interpolating point to produce a shape-preserving spline
* @param x Independent variable values
* @param y Dependent variable values
* @returns Derivative dy/dx
*/
export function dpchim(x: number[], y: number[]) {
if (x.length !== y.length) {
throw new Error('input array lengths must match');
}
const n = x.length;
if (n < 2) {
throw new Error('number of data points less than two');
}
for (let i = 1; i < n; ++i) {
if (x[i] <= x[i - 1]) {
throw new Error('x-array not strictly increasing');
}
}
if (n === 2) {
const deriv = (y[1] - y[0]) / (x[1] - x[0]);
return [deriv, deriv];
}
const d = new Array(n);
let h1 = x[1] - x[0];
let del1 = (y[1] - y[0]) / h1;
let h2 = x[2] - x[1];
let del2 = (y[2] - y[1]) / h2;
// set d[0] via non-centered three-point formula, adjusted to be shape-preserving
let hsum = h1 + h2;
let w1 = (h1 + hsum) / hsum;
let w2 = -h1 / hsum;
d[0] = w1 * del1 + w2 * del2;
if (dpchst(d[0], del1) < 0) {
d[0] = 0;
}
else if (dpchst(del1, del2) < 0) {
// need do this check only if monotonicity switches
const dmax = 3 * del1;
if (Math.abs(d[0]) > Math.abs(dmax)) {
d[0] = dmax;
}
}
// loop through interior points
for (let i = 1; i < n - 1; ++i) {
if (i > 1) {
h1 = h2;
h2 = x[i + 1] - x[i];
hsum = h1 + h2;
del1 = del2;
del2 = (y[i + 1] - y[i]) / h2;
}
d[i] = 0;
if (dpchst(del1, del2) > 0) {
// use Brodlie modification of Butland formula
const hsumt3 = hsum * 3;
w1 = (hsum + h1) / hsumt3;
w2 = (hsum + h2) / hsumt3;
const dmax = Math.max(Math.abs(del1), Math.abs(del2));
const dmin = Math.min(Math.abs(del1), Math.abs(del2));
const drat1 = del1 / dmax;
const drat2 = del2 / dmax;
d[i] = dmin / (w1 * drat1 + w2 * drat2);
}
else {
d[i] = 0; // set d[i] = 0 unless data are strictly monotonic
}
}
// set d[n - 1] via non-centered three-point formula, adjusted to be shape-preserving
w1 = -h2 / hsum;
w2 = (h2 + hsum) / hsum;
d[n - 1] = w1 * del1 + w2 * del2;
if (dpchst(d[n - 1], del2) < 0) {
d[n - 1] = 0;
}
else if (dpchst(del1, del2) < 0) {
// need do this check only if monotonicity switches
const dmax = 3 * del2;
if (Math.abs(d[n - 1]) > Math.abs(dmax)) {
d[n - 1] = dmax;
}
}
return d;
}
/**
* @param arr An array of monotonically increasing numbers
* @param value A number
* @returns Position in `arr` at which `value` could be inserted while maintaining `arr`'s sort order
*/
export function lowerBound(arr: number[], value: number): number {
if (arr.length === 0) {
return 0;
}
let low = 0;
let high = arr.length;
while (low < high) {
const mid = Math.floor((low + high) / 2);
if (arr[mid] < value) {
low = mid + 1;
}
else {
high = mid;
}
}
return high;
}
/**
* Piecewise cubic interpolation, using specified derivatives at the interpolating points
* @param x Independent variable values to interpolate between
* @param y Dependent variable values to interpolate between
* @param m Derivatives of the interpolant at the points described by x, y
* @param xI Independent variable values to interpolate
* @returns Dependent variable values corresponding to xI, computed using a shape-preserving piecewise cubic Hermite spline
*/
export function piecewiseCubic(x: number[], y: number[], m: number[], xI: number[]): number[] {
const yI = new Array(xI.length);
for (let i = 0; i < x.length - 1; ++i) {
const dX = x[i + 1] - x[i];
const dY = y[i + 1] - y[i];
const c = (dY / dX - m[i]) / dX;
const d = (m[i] + m[i + 1] - (2 * dY) / dX) / (dX * dX);
const leftIndex = lowerBound(xI, x[i]);
let rightIndex = lowerBound(xI, x[i + 1]);
if (i === x.length - 2 && xI[rightIndex] === x[i + 1]) {
++rightIndex;
}
const xISubset = xI.slice(leftIndex, rightIndex);
const yISubset = xISubset.map((v) => y[i] + (v - x[i]) * (m[i] + (v - x[i]) * (c + d * (v - x[i + 1]))));
for (let j = 0; j < yISubset.length; ++j) {
yI[j + leftIndex] = yISubset[j];
}
}
return yI;
}
/**
* Similar to Octave's interp1("pchip")
* @param x Independent variable values to interpolate between
* @param y Dependent variable values to interpolate between
* @param xI Independent variable values to interpolate
* @returns Dependent variable values corresponding to xI, computed using a shape-preserving piecewise cubic Hermite spline
*/
export function pchip(x: number[], y: number[], xI: number[]) {
return piecewiseCubic(x, y, dpchim(x, y), xI);
}
export class Pchip {
private deriv: number[];
constructor(private x: number[], private y: number[]) {
this.deriv = dpchim(x, y);
}
evaluate(xI: number | number[]) {
if (Array.isArray(xI)) {
return piecewiseCubic(this.x, this.y, this.deriv, xI);
}
else {
return piecewiseCubic(this.x, this.y, this.deriv, [xI])[0];
}
}
}