2022-09-04 13:21:07 +00:00
open FunctionRegistry_Core
open FunctionRegistry_Helpers
let nameSpace = "Danger"
let requiresNamespace = true
2022-09-05 10:38:15 +00:00
module NNumbersToNumber = {
module One = {
let make = (name, fn) =>
FnDefinition.make(
~name,
~inputs=[FRTypeNumber],
~run=(_, inputs, _, _) => {
inputs
->getOrError(0)
->E.R.bind(Prepare.oneNumber)
->E.R2.fmap(fn)
->E.R2.fmap(Wrappers.evNumber)
},
(),
)
}
2022-09-04 13:21:07 +00:00
2022-09-05 10:38:15 +00:00
module Two = {
let make = (name, fn) =>
FnDefinition.make(
~name,
~inputs=[FRTypeNumber, FRTypeNumber],
~run=(_, inputs, _, _) => {
inputs->Prepare.ToValueTuple.twoNumbers->E.R2.fmap(fn)->E.R2.fmap(Wrappers.evNumber)
},
(),
)
}
2022-09-04 13:21:07 +00:00
2022-09-05 10:38:15 +00:00
module Three = {
let make = (name, fn) =>
FnDefinition.make(
~name,
~inputs=[FRTypeNumber, FRTypeNumber, FRTypeNumber],
~run=(_, inputs, _, _) => {
inputs->Prepare.ToValueTuple.threeNumbers->E.R2.fmap(fn)->E.R2.fmap(Wrappers.evNumber)
},
(),
)
}
2022-09-04 16:53:57 +00:00
}
2022-09-04 15:12:18 +00:00
module Internals = {
2022-09-05 07:08:53 +00:00
// Probability functions
2022-09-04 15:12:18 +00:00
let factorial = Stdlib.Math.factorial
let choose = ((n, k)) => factorial(n) /. (factorial(n -. k) *. factorial(k))
let pow = (base, exp) => Js.Math.pow_float(~base, ~exp)
let binomial = ((n, k, p)) => choose((n, k)) *. pow(p, k) *. pow(1.0 -. p, n -. k)
2022-09-05 10:38:15 +00:00
2022-09-05 07:08:53 +00:00
// Integral helper functions
2022-09-04 18:26:57 +00:00
let applyFunctionAtPoint = (
aLambda,
internalNumber: internalExpressionValue,
environment,
reducer,
): result<ReducerInterface_InternalExpressionValue.t, Reducer_ErrorValue.errorValue> => {
2022-09-05 10:38:15 +00:00
let result = Reducer_Expression_Lambda.doLambdaCall(
2022-09-05 07:39:55 +00:00
aLambda,
2022-09-05 10:38:15 +00:00
list{internalNumber},
2022-09-05 07:39:55 +00:00
environment,
reducer,
)
2022-09-04 20:45:55 +00:00
result
2022-09-04 18:07:10 +00:00
}
2022-09-05 10:38:15 +00:00
let castFloatToInternalNumber = x => ReducerInterface_InternalExpressionValue.IEvNumber(x)
2022-09-05 13:27:59 +00:00
let castArrayOfFloatsToInternalArrayOfInternals = xs => ReducerInterface_InternalExpressionValue.IEvArray(
Belt.Array.map(xs, x => castFloatToInternalNumber(x)),
)
@dead
let applyFunctionAtFloat = (aLambda, point, environment, reducer) =>
2022-09-05 10:38:15 +00:00
// reason for existence: might be an useful template to have for calculating diminishing marginal returns later on
applyFunctionAtPoint(aLambda, castFloatToInternalNumber(point), environment, reducer)
// integrate function itself
let integrateFunctionBetweenWithNumIntegrationPoints = (
2022-09-05 07:08:53 +00:00
aLambda,
min: float,
max: float,
2022-09-05 10:38:15 +00:00
numIntegrationPoints: float, // cast as int?
2022-09-05 07:08:53 +00:00
environment,
reducer,
) => {
let applyFunctionAtFloatToFloatOption = (point: float) => {
2022-09-05 10:38:15 +00:00
// Defined here so that it has access to environment, reducer
2022-09-05 13:27:59 +00:00
let pointAsInternalExpression = castFloatToInternalNumber(point)
2022-09-05 07:08:53 +00:00
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
aLambda,
list{pointAsInternalExpression},
environment,
reducer,
)
let result = switch resultAsInternalExpression {
| Ok(IEvNumber(x)) => Ok(x)
2022-09-05 10:38:15 +00:00
| Error(_) =>
Error(
2022-09-05 10:49:47 +00:00
"Integration 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",
2022-09-05 10:38:15 +00:00
)
2022-09-05 10:49:47 +00:00
| _ => Error("Integration error 2 in Danger.integrate")
2022-09-05 07:08:53 +00:00
}
result
}
2022-09-05 10:11:42 +00:00
// worked example in comments below, assuming min=0, max = 10
2022-09-05 10:38:15 +00:00
let numTotalPoints = Belt.Float.toInt(numIntegrationPoints) // superflous declaration, but useful to keep track that we are interpreting "numIntegrationPoints" as the total number on which we evaluate the function, not e.g., as the inner integration points.
2022-09-05 10:16:19 +00:00
let numInnerPoints = numTotalPoints - 2
let numOuterPoints = 2
let totalWeight = max -. min
let weightForAnInnerPoint = totalWeight /. E.I.toFloat(numTotalPoints - 1)
let weightForAnOuterPoint = totalWeight /. E.I.toFloat(numTotalPoints - 1) /. 2.0
let innerPointIncrement = (max -. min) /. E.I.toFloat(numTotalPoints - 1)
let innerXs = Belt.Array.makeBy(numInnerPoints, i =>
min +. Belt_Float.fromInt(i + 1) *. innerPointIncrement
)
2022-09-05 10:38:15 +00:00
// Gotcha: makeBy goes from 0 to (n-1): <https://rescript-lang.org/docs/manual/latest/api/belt/array#makeby>
2022-09-05 10:11:42 +00:00
let ysOptions = Belt.Array.map(innerXs, x => applyFunctionAtFloatToFloatOption(x))
2022-09-05 07:08:53 +00:00
let okYs = E.A.R.filterOk(ysOptions)
2022-09-05 10:11:42 +00:00
2022-09-05 10:38:15 +00:00
/* Logging, with a worked example. */
// Useful for understanding what is happening.
// assuming min = 0, max = 10, numTotalPoints=10, results below:
let verbose = false
if verbose {
Js.Console.log2("numTotalPoints", numTotalPoints) // 5
Js.Console.log2("numInnerPoints", numInnerPoints) // 3
Js.Console.log2("numOuterPoints", numOuterPoints) // always 2
Js.Console.log2("totalWeight", totalWeight) // 10 - 0 = 10
Js.Console.log2("weightForAnInnerPoint", weightForAnInnerPoint) // 10/4 = 2.5
Js.Console.log2("weightForAnOuterPoint", weightForAnOuterPoint) // 10/4/2 = 1.25
Js.Console.log2(
"weightForAnInnerPoint * numInnerPoints + weightForAnOuterPoint * numOuterPoints",
weightForAnInnerPoint *. E.I.toFloat(numInnerPoints) +.
weightForAnOuterPoint *. E.I.toFloat(numOuterPoints),
) // should be 10
Js.Console.log2(
"sum of weights == totalWeight",
weightForAnInnerPoint *. E.I.toFloat(numInnerPoints) +.
weightForAnOuterPoint *. E.I.toFloat(numOuterPoints) == totalWeight,
) // true
Js.Console.log2("innerPointIncrement", innerPointIncrement) // (10-0)/4 = 2.5
Js.Console.log2("innerXs", innerXs) // 2.5, 5, 7.5
Js.Console.log2("ysOptions", ysOptions)
Js.Console.log2("okYs", okYs)
}
2022-09-05 10:11:42 +00:00
2022-09-05 07:08:53 +00:00
let result = switch E.A.length(ysOptions) == E.A.length(okYs) {
| true => {
2022-09-05 10:11:42 +00:00
let innerPointsSum = okYs->E.A.reduce(0.0, (a, b) => a +. b)
2022-09-05 10:16:19 +00:00
let resultWithOuterPoints = switch (
applyFunctionAtFloatToFloatOption(min),
applyFunctionAtFloatToFloatOption(max),
) {
| (Ok(yMin), Ok(yMax)) => {
let result =
(yMin +. yMax) *. weightForAnOuterPoint +. innerPointsSum *. weightForAnInnerPoint
let wrappedResult = result->ReducerInterface_InternalExpressionValue.IEvNumber->Ok
wrappedResult
}
| (Error(b), _) => Error(b)
| (_, Error(b)) => Error(b)
}
resultWithOuterPoints
2022-09-05 07:08:53 +00:00
}
2022-09-05 11:03:56 +00:00
| false =>
Error(
"Integration error 3 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",
)
2022-09-05 07:08:53 +00:00
}
result
}
2022-09-05 13:27:59 +00:00
type diminishingReturnsAccumulatorInner = {
optimalAllocations: array<float>,
currentMarginalReturns: array<result<float, string>>,
}
type diminishingReturnsAccumulator = result<diminishingReturnsAccumulatorInner, string>
2022-09-05 12:00:14 +00:00
let diminishingMarginalReturnsSkeleton = (
lambda1,
lambda2,
2022-09-05 13:27:59 +00:00
funds,
approximateIncrement,
2022-09-05 12:00:14 +00:00
environment,
reducer,
) => {
2022-09-05 13:27:59 +00:00
/*
Two possible algorithms (n=funds/increment, m=num lambdas)
1. O(n): Iterate through value on next n dollars. At each step, only compute the new marginal return of the function which is spent
2. O(n*m): Iterate through all possible spending combinations. Fun is, it doesn't assume that the returns of marginal spending are diminishing.
*/
let applyFunctionAtFloatToFloatOption = (lambda, point: float) => {
// Defined here so that it has access to environment, reducer
let pointAsInternalExpression = castFloatToInternalNumber(point)
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
lambda,
list{pointAsInternalExpression},
environment,
reducer,
)
let result = switch resultAsInternalExpression {
| Ok(IEvNumber(x)) => Ok(x)
| Error(_) =>
Error(
"Integration 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",
)
| _ => Error("Integration error 2 in Danger.integrate")
}
result
}
let numDivisions = Js.Math.round(funds /. approximateIncrement)
let numDivisionsInt = Belt.Float.toInt(numDivisions)
let increment = funds /. numDivisions
let arrayOfIncrements = Belt.Array.makeBy(numDivisionsInt, _ => increment)
let initAccumulator: diminishingReturnsAccumulator = Ok({
optimalAllocations: [0.0, 0.0],
currentMarginalReturns: [
applyFunctionAtFloatToFloatOption(lambda1, 0.0),
applyFunctionAtFloatToFloatOption(lambda2, 0.0),
],
})
let optimalAllocationEndAccumulator = E.A.reduce(arrayOfIncrements, initAccumulator, (
acc,
new,
) => {
acc
})
let optimalAllocationResult = switch optimalAllocationEndAccumulator {
| Ok(inner) => Ok(castArrayOfFloatsToInternalArrayOfInternals(inner.optimalAllocations))
| Error(b) => Error(b)
}
optimalAllocationResult
2022-09-05 12:00:14 +00:00
}
2022-09-04 15:12:18 +00:00
}
2022-09-04 13:21:07 +00:00
let library = [
Function.make(
~name="laplace",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
2022-09-04 15:12:18 +00:00
~examples=[`Danger.laplace(1, 20)`],
2022-09-04 13:21:07 +00:00
~definitions=[
2022-09-05 10:38:15 +00:00
NNumbersToNumber.Two.make("laplace", ((successes, trials)) =>
2022-09-04 15:12:18 +00:00
(successes +. 1.0) /. (trials +. 2.0)
2022-09-04 13:21:07 +00:00
),
],
(),
),
2022-09-04 15:12:18 +00:00
Function.make(
~name="factorial",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`Danger.factorial(20)`],
2022-09-05 10:38:15 +00:00
~definitions=[NNumbersToNumber.One.make("factorial", Internals.factorial)],
2022-09-04 15:12:18 +00:00
(),
),
Function.make(
~name="choose",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`Danger.choose(1, 20)`],
2022-09-05 10:38:15 +00:00
~definitions=[NNumbersToNumber.Two.make("choose", Internals.choose)],
2022-09-04 15:12:18 +00:00
(),
),
Function.make(
~name="binomial",
~nameSpace,
~requiresNamespace,
~output=EvtNumber,
~examples=[`Danger.binomial(1, 20, 0.5)`],
2022-09-05 10:38:15 +00:00
~definitions=[NNumbersToNumber.Three.make("binomial", Internals.binomial)],
2022-09-04 15:12:18 +00:00
(),
),
2022-09-05 07:08:53 +00:00
// Helper functions building up to the integral
2022-09-04 17:29:22 +00:00
Function.make(
~name="applyFunctionAtZero",
~nameSpace,
2022-09-05 07:10:42 +00:00
~output=EvtNumber,
2022-09-04 17:29:22 +00:00
~requiresNamespace=false,
~examples=[`Danger.applyFunctionAtZero({|x| x+1})`],
~definitions=[
FnDefinition.make(
2022-09-04 18:07:10 +00:00
~name="applyFunctionAtZero",
2022-09-04 17:29:22 +00:00
~inputs=[FRTypeLambda],
2022-09-05 10:38:15 +00:00
~run=(inputs, _, environment, reducer) => {
2022-09-04 20:45:55 +00:00
let result = switch inputs {
2022-09-04 17:29:22 +00:00
| [IEvLambda(aLambda)] =>
2022-09-05 10:38:15 +00:00
Internals.applyFunctionAtPoint(
aLambda,
Internals.castFloatToInternalNumber(0.0),
environment,
reducer,
)->E.R2.errMap(_ => "Error!")
2022-09-04 17:29:22 +00:00
| _ => Error(impossibleError)
2022-09-04 20:45:55 +00:00
}
result
},
2022-09-04 17:29:22 +00:00
(),
),
],
(),
),
2022-09-04 16:53:57 +00:00
Function.make(
2022-09-04 17:22:12 +00:00
~name="applyFunctionAtPoint",
2022-09-04 16:53:57 +00:00
~nameSpace,
2022-09-05 07:10:42 +00:00
~output=EvtNumber,
2022-09-04 16:53:57 +00:00
~requiresNamespace=false,
2022-09-04 17:22:12 +00:00
~examples=[`Danger.applyFunctionAtPoint({|x| x+1}, 1)`],
2022-09-04 16:53:57 +00:00
~definitions=[
FnDefinition.make(
2022-09-04 17:22:12 +00:00
~name="applyFunctionAtPoint",
~inputs=[FRTypeLambda, FRTypeNumber],
2022-09-04 16:53:57 +00:00
~run=(inputs, _, env, reducer) =>
switch inputs {
2022-09-04 17:22:12 +00:00
| [IEvLambda(aLambda), point] =>
Internals.applyFunctionAtPoint(aLambda, point, env, reducer)->E.R2.errMap(_ => "Error!")
2022-09-04 16:53:57 +00:00
| _ => Error(impossibleError)
},
(),
),
],
(),
),
2022-09-05 10:38:15 +00:00
// Integral in terms of function, min, max, num points
// Note that execution time will be more predictable, because it
// will only depend on num points and the complexity of the function
2022-09-04 20:45:55 +00:00
Function.make(
2022-09-05 10:38:15 +00:00
~name="integrateFunctionBetweenWithNumIntegrationPoints",
2022-09-04 20:45:55 +00:00
~nameSpace,
2022-09-05 07:10:42 +00:00
~output=EvtNumber,
2022-09-04 20:45:55 +00:00
~requiresNamespace=false,
2022-09-05 10:38:15 +00:00
~examples=[`Danger.integrateFunctionBetweenWithNumIntegrationPoints({|x| x+1}, 1, 10, 10)`],
2022-09-05 06:41:33 +00:00
// should be [x^2/2 + x]1_10 = (100/2 + 10) - (1/2 + 1) = 60 - 1.5 = 58.5
// https://www.wolframalpha.com/input?i=integrate+x%2B1+from+1+to+10
2022-09-04 20:45:55 +00:00
~definitions=[
FnDefinition.make(
2022-09-05 10:38:15 +00:00
~name="integrateFunctionBetweenWithNumIntegrationPoints",
2022-09-04 20:45:55 +00:00
~inputs=[FRTypeLambda, FRTypeNumber, FRTypeNumber, FRTypeNumber],
~run=(inputs, _, env, reducer) => {
let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] =>
2022-09-05 10:49:47 +00:00
Error("Integration error 4 in Danger.integrate: Increment can't be 0.")
2022-09-05 10:38:15 +00:00
| [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(numIntegrationPoints)] =>
Internals.integrateFunctionBetweenWithNumIntegrationPoints(
2022-09-04 20:45:55 +00:00
aLambda,
min,
max,
2022-09-05 10:38:15 +00:00
numIntegrationPoints,
2022-09-04 20:45:55 +00:00
env,
reducer,
)
| _ =>
Error(
2022-09-05 11:03:56 +00:00
"Integration error 5 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))",
2022-09-04 20:45:55 +00:00
)
}
result
},
(),
),
],
(),
),
2022-09-05 10:38:15 +00:00
// Integral in terms of function, min, max, epsilon (distance between points)
// Note that execution time will be less predictable, because it
// will depend on min, max and epsilon together,
// as well and the complexity of the function
2022-09-05 07:39:55 +00:00
Function.make(
2022-09-05 10:38:15 +00:00
~name="integrateFunctionBetweenWithEpsilon",
2022-09-05 07:08:53 +00:00
~nameSpace,
2022-09-05 07:10:42 +00:00
~output=EvtNumber,
2022-09-05 07:08:53 +00:00
~requiresNamespace=false,
2022-09-05 11:09:28 +00:00
~examples=[`Danger.integrateFunctionBetweenWithEpsilon({|x| x+1}, 1, 10, 0.1)`],
2022-09-05 07:08:53 +00:00
~definitions=[
FnDefinition.make(
2022-09-05 10:38:15 +00:00
~name="integrateFunctionBetweenWithEpsilon",
2022-09-05 07:08:53 +00:00
~inputs=[FRTypeLambda, FRTypeNumber, FRTypeNumber, FRTypeNumber],
~run=(inputs, _, env, reducer) => {
let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] =>
Error("Integration error in Danger.integrate: Increment can't be 0.")
2022-09-05 10:38:15 +00:00
| [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] =>
Internals.integrateFunctionBetweenWithNumIntegrationPoints(
2022-09-05 07:08:53 +00:00
aLambda,
min,
max,
2022-09-05 10:38:15 +00:00
(max -. min) /. epsilon,
2022-09-05 07:39:55 +00:00
env,
2022-09-05 07:08:53 +00:00
reducer,
)->E.R2.errMap(_ =>
2022-09-05 10:49:47 +00:00
"Integration error 7 in Danger.integrate. Something went wrong along the way"
2022-09-05 07:08:53 +00:00
)
| _ =>
Error(
2022-09-05 10:49:47 +00:00
"Integration error 8 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))",
2022-09-05 07:08:53 +00:00
)
}
result
},
(),
),
],
(),
),
2022-09-05 13:27:59 +00:00
Function.make(
2022-09-05 12:00:14 +00:00
~name="diminishingMarginalReturnsSkeleton",
~nameSpace,
2022-09-05 12:12:34 +00:00
~output=EvtArray,
2022-09-05 12:00:14 +00:00
~requiresNamespace=false,
~examples=[`Danger.diminishingMarginalReturnsSkeleton({|x| x+1}, {|y| 10}, 100, 1)`],
~definitions=[
FnDefinition.make(
~name="diminishingMarginalReturnsSkeleton",
~inputs=[FRTypeLambda, FRTypeLambda, FRTypeNumber, FRTypeNumber],
~run=(inputs, _, env, reducer) =>
switch inputs {
2022-09-05 13:27:59 +00:00
| [
IEvLambda(lambda1),
IEvLambda(lambda2),
IEvNumber(funds),
IEvNumber(approximateIncrement),
] =>
Internals.diminishingMarginalReturnsSkeleton(
lambda1,
lambda2,
funds,
approximateIncrement,
env,
reducer,
)
2022-09-05 12:00:14 +00:00
| _ => Error("Error in Danger.diminishingMarginalReturnsSkeleton")
},
(),
),
],
(),
),
2022-09-04 13:21:07 +00:00
]