danger.js (12314B)
1 import jstat from "jstat"; 2 import { scaleLog, scaleLogWithThreshold, } from "../dist/distOperations/index.js"; 3 import { scaleMultiply, scalePower, } from "../dist/distOperations/scaleOperations.js"; 4 import { REArgumentError, REOther } from "../errors/messages.js"; 5 import { makeDefinition } from "../library/registry/fnDefinition.js"; 6 import { frAny, frArray, frDist, frLambda, frNumber, } from "../library/registry/frTypes.js"; 7 import { FnFactory, unpackDistResult, distResultToValue, makeTwoArgsDist, makeOneArgDist, } from "../library/registry/helpers.js"; 8 import * as E_A from "../utility/E_A.js"; 9 import { vArray, vNumber } from "../value/index.js"; 10 import * as SymbolicDist from "../dist/SymbolicDist.js"; 11 const { factorial } = jstat; 12 const maker = new FnFactory({ 13 nameSpace: "Danger", 14 requiresNamespace: true, 15 }); 16 function combinations(arr, k) { 17 if (k === 0) 18 return [[]]; 19 if (k === arr.length) 20 return [arr]; 21 const withoutFirst = combinations(arr.slice(1), k); 22 const withFirst = combinations(arr.slice(1), k - 1).map((comb) => [ 23 arr[0], 24 ...comb, 25 ]); 26 return withFirst.concat(withoutFirst); 27 } 28 function allCombinations(arr) { 29 let allCombs = []; 30 for (let k = 1; k <= arr.length; k++) { 31 allCombs = allCombs.concat(combinations(arr, k)); 32 } 33 return allCombs; 34 } 35 const choose = (n, k) => factorial(n) / (factorial(n - k) * factorial(k)); 36 const combinatoricsLibrary = [ 37 maker.nn2n({ 38 name: "laplace", 39 examples: [`Danger.laplace(1, 20)`], 40 fn: (successes, trials) => (successes + 1) / (trials + 2), 41 }), 42 maker.n2n({ 43 name: "factorial", 44 examples: [`Danger.factorial(20)`], 45 fn: factorial, 46 }), 47 maker.nn2n({ 48 name: "choose", 49 examples: [`Danger.choose(1, 20)`], 50 fn: choose, 51 }), 52 maker.make({ 53 name: "binomial", 54 output: "Number", 55 examples: [`Danger.binomial(1, 20, 0.5)`], 56 definitions: [ 57 makeDefinition([frNumber, frNumber, frNumber], ([n, k, p]) => vNumber(choose(n, k) * Math.pow(p, k) * Math.pow(1 - p, n - k))), 58 ], 59 }), 60 ]; 61 const integrateFunctionBetweenWithNumIntegrationPoints = (lambda, min, max, numIntegrationPoints, context) => { 62 const applyFunctionAtFloatToFloatOption = (point) => { 63 const result = lambda.call([vNumber(point)], context); 64 if (result.type === "Number") { 65 return result.value; 66 } 67 throw new REOther("Error 1 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"); 68 }; 69 const numTotalPoints = numIntegrationPoints | 0; 70 const numInnerPoints = numTotalPoints - 2; 71 const numOuterPoints = 2; 72 const totalWeight = max - min; 73 const weightForAnInnerPoint = totalWeight / (numTotalPoints - 1); 74 const weightForAnOuterPoint = totalWeight / (numTotalPoints - 1) / 2; 75 const innerPointIncrement = (max - min) / (numTotalPoints - 1); 76 const innerXs = E_A.makeBy(numInnerPoints, (i) => min + (i + 1) * innerPointIncrement); 77 const ys = innerXs.map((x) => applyFunctionAtFloatToFloatOption(x)); 78 const verbose = false; 79 if (verbose) { 80 console.log("numTotalPoints", numTotalPoints); 81 console.log("numInnerPoints", numInnerPoints); 82 console.log("numOuterPoints", numOuterPoints); 83 console.log("totalWeight", totalWeight); 84 console.log("weightForAnInnerPoint", weightForAnInnerPoint); 85 console.log("weightForAnOuterPoint", weightForAnOuterPoint); 86 console.log("weightForAnInnerPoint * numInnerPoints + weightForAnOuterPoint * numOuterPoints", weightForAnInnerPoint * numInnerPoints + 87 weightForAnOuterPoint * numOuterPoints); 88 console.log("sum of weights == totalWeight", weightForAnInnerPoint * numInnerPoints + 89 weightForAnOuterPoint * numOuterPoints === 90 totalWeight); 91 console.log("innerPointIncrement", innerPointIncrement); 92 console.log("innerXs", innerXs); 93 console.log("ys", ys); 94 } 95 const innerPointsSum = ys.reduce((a, b) => a + b, 0); 96 const yMin = applyFunctionAtFloatToFloatOption(min); 97 const yMax = applyFunctionAtFloatToFloatOption(max); 98 const result = (yMin + yMax) * weightForAnOuterPoint + 99 innerPointsSum * weightForAnInnerPoint; 100 return vNumber(result); 101 }; 102 const integrationLibrary = [ 103 maker.make({ 104 name: "integrateFunctionBetweenWithNumIntegrationPoints", 105 requiresNamespace: false, 106 output: "Number", 107 examples: [ 108 `Danger.integrateFunctionBetweenWithNumIntegrationPoints({|x| x+1}, 1, 10, 10)`, 109 ], 110 definitions: [ 111 makeDefinition([frLambda, frNumber, frNumber, frNumber], ([lambda, min, max, numIntegrationPoints], context) => { 112 if (numIntegrationPoints === 0) { 113 throw new REOther("Integration error 4 in Danger.integrate: Increment can't be 0."); 114 } 115 return integrateFunctionBetweenWithNumIntegrationPoints(lambda, min, max, numIntegrationPoints, context); 116 }), 117 ], 118 }), 119 maker.make({ 120 name: "integrateFunctionBetweenWithEpsilon", 121 requiresNamespace: false, 122 output: "Number", 123 examples: [ 124 `Danger.integrateFunctionBetweenWithEpsilon({|x| x+1}, 1, 10, 0.1)`, 125 ], 126 definitions: [ 127 makeDefinition([frLambda, frNumber, frNumber, frNumber], ([lambda, min, max, epsilon], context) => { 128 if (epsilon === 0) { 129 throw new REOther("Integration error in Danger.integrate: Increment can't be 0."); 130 } 131 return integrateFunctionBetweenWithNumIntegrationPoints(lambda, min, max, (max - min) / epsilon, context); 132 }), 133 ], 134 }), 135 ]; 136 const findBiggestElementIndex = (xs) => xs.reduce((acc, newElement, index) => { 137 if (newElement > xs[acc]) { 138 return index; 139 } 140 else { 141 return acc; 142 } 143 }, 0); 144 const diminishingReturnsLibrary = [ 145 maker.make({ 146 name: "optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions", 147 output: "Array", 148 requiresNamespace: false, 149 examples: [ 150 `Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions([{|x| x+1}, {|y| 10}], 100, 0.01)`, 151 ], 152 definitions: [ 153 makeDefinition([frArray(frLambda), frNumber, frNumber], ([lambdas, funds, approximateIncrement], context) => { 154 if (lambdas.length <= 1) { 155 throw new REOther("Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."); 156 } 157 if (funds <= 0) { 158 throw new REOther("Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."); 159 } 160 if (approximateIncrement <= 0) { 161 throw new REOther("Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."); 162 } 163 if (approximateIncrement >= funds) { 164 throw new REOther("Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."); 165 } 166 const applyFunctionAtPoint = (lambda, point) => { 167 const lambdaResult = lambda.call([vNumber(point)], context); 168 if (lambdaResult.type === "Number") { 169 return lambdaResult.value; 170 } 171 throw new REOther("Error 1 in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"); 172 }; 173 const numDivisions = Math.round(funds / approximateIncrement); 174 const increment = funds / numDivisions; 175 const arrayOfIncrements = new Array(numDivisions).fill(increment); 176 const initAccumulator = { 177 optimalAllocations: new Array(lambdas.length).fill(0), 178 currentMarginalReturns: lambdas.map((lambda) => applyFunctionAtPoint(lambda, 0)), 179 }; 180 const optimalAllocationEndAccumulator = arrayOfIncrements.reduce((acc, newIncrement) => { 181 const oldMarginalReturns = acc.currentMarginalReturns; 182 const indexOfBiggestDMR = findBiggestElementIndex(oldMarginalReturns); 183 const newOptimalAllocations = [...acc.optimalAllocations]; 184 const newOptimalAllocationsi = newOptimalAllocations[indexOfBiggestDMR] + newIncrement; 185 newOptimalAllocations[indexOfBiggestDMR] = newOptimalAllocationsi; 186 const lambdai = lambdas[indexOfBiggestDMR]; 187 const newMarginalResultsLambdai = applyFunctionAtPoint(lambdai, newOptimalAllocationsi); 188 const newCurrentMarginalReturns = [...oldMarginalReturns]; 189 newCurrentMarginalReturns[indexOfBiggestDMR] = 190 newMarginalResultsLambdai; 191 const newAcc = { 192 optimalAllocations: newOptimalAllocations, 193 currentMarginalReturns: newCurrentMarginalReturns, 194 }; 195 return newAcc; 196 }, initAccumulator); 197 return vArray(optimalAllocationEndAccumulator.optimalAllocations.map(vNumber)); 198 }), 199 ], 200 }), 201 ]; 202 const mapYLibrary = [ 203 maker.d2d({ 204 name: "mapYLog", 205 fn: (dist, env) => unpackDistResult(scaleLog(dist, Math.E, { env })), 206 }), 207 maker.d2d({ 208 name: "mapYLog10", 209 fn: (dist, env) => unpackDistResult(scaleLog(dist, 10, { env })), 210 }), 211 maker.dn2d({ 212 name: "mapYLog", 213 fn: (dist, x, env) => unpackDistResult(scaleLog(dist, x, { env })), 214 }), 215 maker.make({ 216 name: "mapYLogWithThreshold", 217 definitions: [ 218 makeDefinition([frDist, frNumber, frNumber], ([dist, base, eps], { environment }) => distResultToValue(scaleLogWithThreshold(dist, { 219 env: environment, 220 eps, 221 base, 222 }))), 223 ], 224 }), 225 maker.make({ 226 name: "combinations", 227 definitions: [ 228 makeDefinition([frArray(frAny), frNumber], ([elements, n]) => { 229 if (n > elements.length) { 230 throw new REArgumentError(`Combinations of length ${n} were requested, but full list is only ${elements.length} long.`); 231 } 232 return vArray(combinations(elements, n).map((v) => vArray(v))); 233 }), 234 ], 235 }), 236 maker.make({ 237 name: "allCombinations", 238 definitions: [ 239 makeDefinition([frArray(frAny)], ([elements]) => { 240 return vArray(allCombinations(elements).map((v) => vArray(v))); 241 }), 242 ], 243 }), 244 maker.dn2d({ 245 name: "mapYMultiply", 246 fn: (dist, f, env) => unpackDistResult(scaleMultiply(dist, f, { env })), 247 }), 248 maker.dn2d({ 249 name: "mapYPow", 250 fn: (dist, f, env) => unpackDistResult(scalePower(dist, f, { env })), 251 }), 252 maker.d2d({ 253 name: "mapYExp", 254 fn: (dist, env) => unpackDistResult(scalePower(dist, Math.E, { env })), 255 }), 256 maker.make({ 257 name: "binomialDist", 258 examples: ["Danger.binomialDist(8, 0.5)"], 259 definitions: [makeTwoArgsDist((n, p) => SymbolicDist.Binomial.make(n, p))], 260 }), 261 maker.make({ 262 name: "poissonDist", 263 examples: ["Danger.poissonDist(10)"], 264 definitions: [ 265 makeOneArgDist((lambda) => SymbolicDist.Poisson.make(lambda)), 266 ], 267 }), 268 ]; 269 export const library = [ 270 ...combinatoricsLibrary, 271 ...integrationLibrary, 272 ...diminishingReturnsLibrary, 273 ...mapYLibrary, 274 ]; 275 //# sourceMappingURL=danger.js.map