Add analytic solutions for normal and lognormal

This commit is contained in:
Sam Nolan 2022-04-13 15:11:14 +10:00
parent 348b1c9ac6
commit 948a8dd651
2 changed files with 52 additions and 11 deletions

View File

@ -50,6 +50,26 @@ describe("eval on distribution functions", () => {
testEval("3+normal(5,2)", "Ok(Normal(8,2))") testEval("3+normal(5,2)", "Ok(Normal(8,2))")
testEval("normal(5,2)+3", "Ok(Normal(8,2))") testEval("normal(5,2)+3", "Ok(Normal(8,2))")
}) })
describe("subtract", () => {
testEval("10 - normal(5, 1)", "Ok(Normal(5,1))")
testEval("normal(5, 1) - 10", "Ok(Normal(-5,1))")
})
describe("multiply", () => {
testEval("normal(10, 2) * 2", "Ok(Normal(20,4))")
testEval("2 * normal(10, 2)", "Ok(Normal(20,4))")
testEval("lognormal(5,2) * lognormal(10,2)", "Ok(Lognormal(15,4))")
testEval("lognormal(10, 2) * lognormal(5, 2)", "Ok(Lognormal(15,4))")
testEval("2 * lognormal(5, 2)", "Ok(Lognormal(5.693147180559945,2))")
testEval("lognormal(5, 2) * 2", "Ok(Lognormal(5.693147180559945,2))")
})
describe("division", () => {
testEval("lognormal(5,2) / lognormal(10,2)", "Ok(Lognormal(-5,4))")
testEval("lognormal(10,2) / lognormal(5,2)", "Ok(Lognormal(5,4))")
testEval("lognormal(5, 2) / 2", "Ok(Lognormal(4.306852819440055,2))")
testEval("2 / lognormal(5, 2)", "Ok(Lognormal(-4.306852819440055,2))")
testEval("2 / normal(10, 2)", "Ok(Point Set Distribution)")
testEval("normal(10, 2) / 2", "Ok(Normal(5,1))")
})
describe("truncate", () => { describe("truncate", () => {
testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)") testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("truncateRight(normal(5,2), 3)", "Ok(Point Set Distribution)") testEval("truncateRight(normal(5,2), 3)", "Ok(Point Set Distribution)")
@ -93,11 +113,6 @@ describe("eval on distribution functions", () => {
testEval("mx(normal(5,2), normal(10,1), normal(15, 1))", "Ok(Point Set Distribution)") testEval("mx(normal(5,2), normal(10,1), normal(15, 1))", "Ok(Point Set Distribution)")
testEval("mixture(normal(5,2), normal(10,1), [0.2, 0.4])", "Ok(Point Set Distribution)") testEval("mixture(normal(5,2), normal(10,1), [0.2, 0.4])", "Ok(Point Set Distribution)")
}) })
describe("subtract", () => {
testEval("10 - normal(5, 1)", "Ok(Normal(5,1))")
testEval("normal(5, 1) - 10", "Ok(Normal(-5,1))")
})
}) })
describe("parse on distribution functions", () => { describe("parse on distribution functions", () => {

View File

@ -57,7 +57,7 @@ module Normal = {
switch operation { switch operation {
| #Add => Some(#Normal({mean: n1.mean +. n2, stdev: n1.stdev})) | #Add => Some(#Normal({mean: n1.mean +. n2, stdev: n1.stdev}))
| #Subtract => Some(#Normal({mean: n1.mean -. n2, stdev: n1.stdev})) | #Subtract => Some(#Normal({mean: n1.mean -. n2, stdev: n1.stdev}))
| #Multiply => Some(#Normal({mean: n1.mean *. n2, stdev: n1.stdev})) | #Multiply => Some(#Normal({mean: n1.mean *. n2, stdev: n1.stdev *. n2}))
| #Divide => Some(#Normal({mean: n1.mean /. n2, stdev: n1.stdev /. n2})) | #Divide => Some(#Normal({mean: n1.mean /. n2, stdev: n1.stdev /. n2}))
| _ => None | _ => None
} }
@ -167,6 +167,22 @@ module Lognormal = {
| #Divide => Some(divide(n1, n2)) | #Divide => Some(divide(n1, n2))
| _ => None | _ => None
} }
let operateFloatFirst = (operation: Operation.Algebraic.t, n1: float, n2: t) =>
switch operation {
| #Multiply =>
n1 > 0.0 ? Some(#Lognormal({mu: Js.Math.log(n1) +. n2.mu, sigma: n2.sigma})) : None
| #Divide => n1 > 0.0 ? Some(#Lognormal({mu: Js.Math.log(n1) -. n2.mu, sigma: n2.sigma})) : None
| _ => None
}
let operateFloatSecond = (operation: Operation.Algebraic.t, n1: t, n2: float) =>
switch operation {
| #Multiply =>
n2 > 0.0 ? Some(#Lognormal({mu: n1.mu +. Js.Math.log(n2), sigma: n1.sigma})) : None
| #Divide => n2 > 0.0 ? Some(#Lognormal({mu: n1.mu -. Js.Math.log(n2), sigma: n1.sigma})) : None
| _ => None
}
} }
module Uniform = { module Uniform = {
@ -358,18 +374,28 @@ module T = {
} }
| (#Normal(v1), #Normal(v2)) => | (#Normal(v1), #Normal(v2)) =>
Normal.operate(op, v1, v2) |> E.O.dimap(r => #AnalyticalSolution(r), () => #NoSolution) Normal.operate(op, v1, v2) |> E.O.dimap(r => #AnalyticalSolution(r), () => #NoSolution)
| (#Normal(v1), #Float(v2)) =>
Normal.operateFloatSecond(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| (#Float(v1), #Normal(v2)) => | (#Float(v1), #Normal(v2)) =>
Normal.operateFloatFirst(op, v1, v2) |> E.O.dimap( Normal.operateFloatFirst(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r), r => #AnalyticalSolution(r),
() => #NoSolution, () => #NoSolution,
) )
| (#Normal(v1), #Float(v2)) =>
Normal.operateFloatSecond(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| (#Lognormal(v1), #Lognormal(v2)) => | (#Lognormal(v1), #Lognormal(v2)) =>
Lognormal.operate(op, v1, v2) |> E.O.dimap(r => #AnalyticalSolution(r), () => #NoSolution) Lognormal.operate(op, v1, v2) |> E.O.dimap(r => #AnalyticalSolution(r), () => #NoSolution)
| (#Float(v1), #Lognormal(v2)) =>
Lognormal.operateFloatFirst(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| (#Lognormal(v1), #Float(v2)) =>
Lognormal.operateFloatSecond(op, v1, v2) |> E.O.dimap(
r => #AnalyticalSolution(r),
() => #NoSolution,
)
| _ => #NoSolution | _ => #NoSolution
} }