Continuous.js (11055B)
1 import { epsilon_float } from "../magicNumbers.js"; 2 import * as XYShape from "../XYShape.js"; 3 import * as MixedPoint from "./MixedPoint.js"; 4 import * as Result from "../utility/result.js"; 5 import { MixedShape } from "./Mixed.js"; 6 import * as AlgebraicShapeCombination from "./AlgebraicShapeCombination.js"; 7 import * as Common from "./Common.js"; 8 import * as Discrete from "./Discrete.js"; 9 export class ContinuousShape { 10 constructor(args) { 11 this.xyShape = args.xyShape; 12 this.interpolation = args.interpolation ?? "Linear"; 13 this._integralSumCache = args.integralSumCache; 14 this._integralCache = args.integralCache; 15 } 16 get integralCache() { 17 return this._integralCache; 18 } 19 get integralSumCache() { 20 return this._integralSumCache; 21 } 22 withAdjustedIntegralSum(integralSumCache) { 23 return new ContinuousShape({ 24 xyShape: this.xyShape, 25 interpolation: this.interpolation, 26 integralSumCache, 27 integralCache: this.integralCache, 28 }); 29 } 30 lastY() { 31 return XYShape.T.lastY(this.xyShape); 32 } 33 minX() { 34 return XYShape.T.minX(this.xyShape); 35 } 36 maxX() { 37 return XYShape.T.maxX(this.xyShape); 38 } 39 isEqual(t) { 40 return (XYShape.T.isEqual(this.xyShape, t.xyShape) && 41 this.interpolation === t.interpolation); 42 } 43 mapY(fn, integralSumCacheFn, integralCacheFn) { 44 return new ContinuousShape({ 45 xyShape: XYShape.T.mapY(this.xyShape, fn), 46 interpolation: this.interpolation, 47 integralSumCache: this.integralSumCache === undefined 48 ? undefined 49 : integralSumCacheFn?.(this.integralSumCache), 50 integralCache: this.integralCache === undefined 51 ? undefined 52 : integralCacheFn?.(this.integralCache), 53 }); 54 } 55 mapYResult(fn, integralSumCacheFn, integralCacheFn) { 56 const result = XYShape.T.mapYResult(this.xyShape, fn); 57 if (!result.ok) { 58 return result; 59 } 60 return Result.Ok(new ContinuousShape({ 61 xyShape: result.value, 62 interpolation: this.interpolation, 63 integralSumCache: this.integralSumCache === undefined 64 ? undefined 65 : integralSumCacheFn?.(this.integralSumCache), 66 integralCache: this.integralCache === undefined 67 ? undefined 68 : integralCacheFn?.(this.integralCache), 69 })); 70 } 71 toDiscreteProbabilityMassFraction() { 72 return 0; 73 } 74 xToY(f) { 75 switch (this.interpolation) { 76 case "Stepwise": 77 return MixedPoint.makeContinuous(XYShape.XtoY.stepwiseIncremental(this.xyShape, f) ?? 0); 78 case "Linear": 79 return MixedPoint.makeContinuous(XYShape.XtoY.linear(this.xyShape, f)); 80 } 81 } 82 truncate(leftCutoff, rightCutoff) { 83 const lc = leftCutoff ?? -Infinity; 84 const rc = rightCutoff ?? Infinity; 85 const truncatedZippedPairs = XYShape.Zipped.filterByX(XYShape.T.zip(this.xyShape), (x) => x >= lc && x <= rc); 86 const leftNewPoint = leftCutoff === undefined ? [] : [[lc - epsilon_float, 0]]; 87 const rightNewPoint = rightCutoff === undefined ? [] : [[rc + epsilon_float, 0]]; 88 const truncatedZippedPairsWithNewPoints = [ 89 ...leftNewPoint, 90 ...truncatedZippedPairs, 91 ...rightNewPoint, 92 ]; 93 const truncatedShape = XYShape.T.fromZippedArray(truncatedZippedPairsWithNewPoints); 94 return new ContinuousShape({ xyShape: truncatedShape }); 95 } 96 integral() { 97 if (!this._integralCache) { 98 if (XYShape.T.isEmpty(this.xyShape)) { 99 this._integralCache = emptyIntegral(); 100 } 101 else { 102 this._integralCache = new ContinuousShape({ 103 xyShape: XYShape.Range.integrateWithTriangles(this.xyShape), 104 }); 105 } 106 } 107 return this._integralCache; 108 } 109 integralSum() { 110 return (this._integralSumCache ??= this.integral().lastY()); 111 } 112 integralXtoY(f) { 113 return XYShape.XtoY.linear(this.integral().xyShape, f); 114 } 115 integralYtoX(f) { 116 return XYShape.YtoX.linear(this.integral().xyShape, f); 117 } 118 shapeMap(fn) { 119 return new ContinuousShape({ 120 xyShape: fn(this.xyShape), 121 interpolation: this.interpolation, 122 integralSumCache: this.integralSumCache, 123 integralCache: this.integralCache, 124 }); 125 } 126 downsample(length) { 127 return this.shapeMap((shape) => XYShape.XsConversion.proportionByProbabilityMass(shape, length, this.integral().xyShape)); 128 } 129 isEmpty() { 130 return this.xyShape.xs.length === 0; 131 } 132 toContinuous() { 133 return this; 134 } 135 toDiscrete() { 136 return undefined; 137 } 138 toMixed() { 139 return new MixedShape({ 140 continuous: this, 141 discrete: Discrete.empty(), 142 integralSumCache: this.integralSumCache, 143 integralCache: this.integralCache, 144 }); 145 } 146 scaleBy(scale) { 147 return this.mapY((r) => r * scale, (sum) => sum * scale, (cache) => cache.scaleBy(scale)); 148 } 149 normalize() { 150 return this.scaleBy(1 / this.integralSum()).withAdjustedIntegralSum(1); 151 } 152 mean() { 153 const indefiniteIntegralStepwise = (p, h1) => (h1 * p ** 2) / 2; 154 const indefiniteIntegralLinear = (p, a, b) => (a * p ** 2) / 2 + (b * p ** 3) / 3; 155 return this.integrate(indefiniteIntegralStepwise, indefiniteIntegralLinear); 156 } 157 integrate(indefiniteIntegralStepwise = (p, h1) => h1 * p, indefiniteIntegralLinear = (p, a, b) => a * p + (b * p ** 2) / 2) { 158 const xs = this.xyShape.xs; 159 const ys = this.xyShape.ys; 160 let areaUnderIntegral = 0; 161 for (let i = 1; i < xs.length; i++) { 162 if (this.interpolation === "Stepwise") { 163 areaUnderIntegral += 164 indefiniteIntegralStepwise(xs[i], ys[i - 1]) - 165 indefiniteIntegralStepwise(xs[i - 1], ys[i - 1]); 166 } 167 else if (this.interpolation === "Linear") { 168 const x1 = xs[i - 1]; 169 const x2 = xs[i]; 170 if (x1 !== x2) { 171 const h1 = ys[i - 1]; 172 const h2 = ys[i]; 173 const b = (h1 - h2) / (x1 - x2); 174 const a = h1 - b * x1; 175 areaUnderIntegral += 176 indefiniteIntegralLinear(x2, a, b) - 177 indefiniteIntegralLinear(x1, a, b); 178 } 179 } 180 else { 181 throw new Error(`Unknown interpolation strategy ${this.interpolation}`); 182 } 183 } 184 return areaUnderIntegral; 185 } 186 getMeanOfSquares() { 187 const indefiniteIntegralLinear = (p, a, b) => (a * p ** 3) / 3 + (b * p ** 4) / 4; 188 const indefiniteIntegralStepwise = (p, h1) => (h1 * p ** 3) / 3; 189 return this.integrate(indefiniteIntegralStepwise, indefiniteIntegralLinear); 190 } 191 variance() { 192 return XYShape.Analysis.getVarianceDangerously(this, (t) => t.mean(), (t) => t.getMeanOfSquares()); 193 } 194 downsampleEquallyOverX(length) { 195 return this.shapeMap((shape) => XYShape.XsConversion.proportionEquallyOverX(shape, length)); 196 } 197 } 198 export const Analysis = {}; 199 const emptyIntegral = () => new ContinuousShape({ 200 xyShape: { 201 xs: [-Infinity], 202 ys: [0.0], 203 }, 204 interpolation: "Linear", 205 integralSumCache: 0, 206 integralCache: undefined, 207 }); 208 export const empty = () => new ContinuousShape({ 209 xyShape: XYShape.T.empty, 210 interpolation: "Linear", 211 integralSumCache: 0, 212 integralCache: emptyIntegral(), 213 }); 214 export const stepwiseToLinear = (t) => { 215 return new ContinuousShape({ 216 xyShape: XYShape.Range.stepwiseToLinear(t.xyShape), 217 interpolation: "Linear", 218 integralSumCache: t.integralSumCache, 219 integralCache: t.integralCache, 220 }); 221 }; 222 export const combinePointwise = (t1, t2, fn, distributionType = "PDF", integralSumCachesFn = () => undefined) => { 223 const combiner = XYShape.PointwiseCombination.combine; 224 const combinedIntegralSum = Common.combineIntegralSums(integralSumCachesFn, t1.integralSumCache, t2.integralSumCache); 225 if (t1.interpolation === "Stepwise" && t2.interpolation === "Linear") { 226 t1 = stepwiseToLinear(t1); 227 } 228 else if (t1.interpolation === "Linear" && t2.interpolation === "Stepwise") { 229 t2 = stepwiseToLinear(t2); 230 } 231 const extrapolation = { 232 PDF: "UseZero", 233 CDF: "UseOutermostPoints", 234 }[distributionType]; 235 const interpolator = XYShape.XtoY.continuousInterpolator(t1.interpolation, extrapolation); 236 return Result.fmap(combiner(interpolator, fn, t1.xyShape, t2.xyShape), (x) => new ContinuousShape({ 237 xyShape: x, 238 interpolation: "Linear", 239 integralSumCache: combinedIntegralSum, 240 })); 241 }; 242 export const getShape = (t) => t.xyShape; 243 export const sum = (continuousShapes) => { 244 return continuousShapes.reduce((x, y) => { 245 const result = combinePointwise(x, y, (a, b) => Result.Ok(a + b)); 246 if (!result.ok) { 247 throw new Error("Addition should never fail"); 248 } 249 return result.value; 250 }, empty()); 251 }; 252 export const combineAlgebraicallyWithDiscrete = (op, t1, t2, discretePosition) => { 253 const t1s = t1.xyShape; 254 const t2s = t2.xyShape; 255 if (XYShape.T.isEmpty(t1s) || XYShape.T.isEmpty(t2s)) { 256 return empty(); 257 } 258 else { 259 const continuousAsLinear = { 260 Linear: t1, 261 Stepwise: stepwiseToLinear(t1), 262 }[t1.interpolation]; 263 const combinedShape = AlgebraicShapeCombination.combineShapesContinuousDiscrete(op, continuousAsLinear.xyShape, t2s, { discretePosition }); 264 const combinedIntegralSum = op === "Multiply" 265 ? Common.combineIntegralSums((a, b) => a * b, t1.integralSumCache, t2.integralSumCache) 266 : undefined; 267 return new ContinuousShape({ 268 xyShape: combinedShape, 269 interpolation: t1.interpolation, 270 integralSumCache: combinedIntegralSum, 271 }); 272 } 273 }; 274 export const combineAlgebraically = (op, t1, t2) => { 275 const s1 = t1.xyShape; 276 const s2 = t2.xyShape; 277 const t1n = XYShape.T.length(s1); 278 const t2n = XYShape.T.length(s2); 279 if (t1n === 0 || t2n === 0) { 280 return empty(); 281 } 282 else { 283 const combinedShape = AlgebraicShapeCombination.combineShapesContinuousContinuous(op, s1, s2); 284 const combinedIntegralSum = Common.combineIntegralSums((a, b) => a * b, t1.integralSumCache, t2.integralSumCache); 285 return new ContinuousShape({ 286 xyShape: combinedShape, 287 interpolation: "Linear", 288 integralSumCache: combinedIntegralSum, 289 }); 290 } 291 }; 292 //# sourceMappingURL=Continuous.js.map