Merge pull request #342 from quantified-uncertainty/restrict-convolution
Use a more conservative convolution policy
This commit is contained in:
commit
b629759542
|
@ -7,3 +7,5 @@ node_modules
|
||||||
packages/*/node_modules
|
packages/*/node_modules
|
||||||
packages/website/.docusaurus
|
packages/website/.docusaurus
|
||||||
packages/squiggle-lang/lib
|
packages/squiggle-lang/lib
|
||||||
|
packages/squiggle-lang/.nyc_output/
|
||||||
|
packages/squiggle-lang/coverage/
|
||||||
|
|
|
@ -67,7 +67,7 @@ describe("eval on distribution functions", () => {
|
||||||
testEval("lognormal(10,2) / lognormal(5,2)", "Ok(Lognormal(5,2.8284271247461903))")
|
testEval("lognormal(10,2) / lognormal(5,2)", "Ok(Lognormal(5,2.8284271247461903))")
|
||||||
testEval("lognormal(5, 2) / 2", "Ok(Lognormal(4.306852819440055,2))")
|
testEval("lognormal(5, 2) / 2", "Ok(Lognormal(4.306852819440055,2))")
|
||||||
testEval("2 / lognormal(5, 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("2 / normal(10, 2)", "Ok(Sample Set Distribution)")
|
||||||
testEval("normal(10, 2) / 2", "Ok(Normal(5,1))")
|
testEval("normal(10, 2) / 2", "Ok(Normal(5,1))")
|
||||||
})
|
})
|
||||||
describe("truncate", () => {
|
describe("truncate", () => {
|
||||||
|
@ -77,21 +77,21 @@ describe("eval on distribution functions", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("exp", () => {
|
describe("exp", () => {
|
||||||
testEval("exp(normal(5,2))", "Ok(Point Set Distribution)")
|
testEval("exp(normal(5,2))", "Ok(Sample Set Distribution)")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("pow", () => {
|
describe("pow", () => {
|
||||||
testEval("pow(3, uniform(5,8))", "Ok(Point Set Distribution)")
|
testEval("pow(3, uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||||
testEval("pow(uniform(5,8), 3)", "Ok(Point Set Distribution)")
|
testEval("pow(uniform(5,8), 3)", "Ok(Sample Set Distribution)")
|
||||||
testEval("pow(uniform(5,8), uniform(9, 10))", "Ok(Sample Set Distribution)")
|
testEval("pow(uniform(5,8), uniform(9, 10))", "Ok(Sample Set Distribution)")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("log", () => {
|
describe("log", () => {
|
||||||
testEval("log(2, uniform(5,8))", "Ok(Point Set Distribution)")
|
testEval("log(2, uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||||
testEval("log(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
testEval("log(normal(5,2), 3)", "Ok(Sample Set Distribution)")
|
||||||
testEval("log(normal(5,2), normal(10,1))", "Ok(Sample Set Distribution)")
|
testEval("log(normal(5,2), normal(10,1))", "Ok(Sample Set Distribution)")
|
||||||
testEval("log(uniform(5,8))", "Ok(Point Set Distribution)")
|
testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||||
testEval("log10(uniform(5,8))", "Ok(Point Set Distribution)")
|
testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("dotLog", () => {
|
describe("dotLog", () => {
|
||||||
|
|
|
@ -164,7 +164,7 @@ module AlgebraicCombination = {
|
||||||
|
|
||||||
let runConvolution = (
|
let runConvolution = (
|
||||||
toPointSet: toPointSetFn,
|
toPointSet: toPointSetFn,
|
||||||
arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation,
|
arithmeticOperation: Operation.convolutionOperation,
|
||||||
t1: t,
|
t1: t,
|
||||||
t2: t,
|
t2: t,
|
||||||
) =>
|
) =>
|
||||||
|
@ -197,10 +197,23 @@ module AlgebraicCombination = {
|
||||||
| _ => 1000
|
| _ => 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
let chooseConvolutionOrMonteCarlo = (t2: t, t1: t) =>
|
type calculationMethod = MonteCarlo | Convolution(Operation.convolutionOperation)
|
||||||
expectedConvolutionCost(t1) * expectedConvolutionCost(t2) > 10000
|
|
||||||
? #CalculateWithMonteCarlo
|
let chooseConvolutionOrMonteCarlo = (
|
||||||
: #CalculateWithConvolution
|
op: Operation.algebraicOperation,
|
||||||
|
t2: t,
|
||||||
|
t1: t,
|
||||||
|
): calculationMethod =>
|
||||||
|
switch op {
|
||||||
|
| #Divide
|
||||||
|
| #Power
|
||||||
|
| #Logarithm =>
|
||||||
|
MonteCarlo
|
||||||
|
| (#Add | #Subtract | #Multiply) as convOp =>
|
||||||
|
expectedConvolutionCost(t1) * expectedConvolutionCost(t2) > 10000
|
||||||
|
? MonteCarlo
|
||||||
|
: Convolution(convOp)
|
||||||
|
}
|
||||||
|
|
||||||
let run = (
|
let run = (
|
||||||
t1: t,
|
t1: t,
|
||||||
|
@ -213,15 +226,10 @@ module AlgebraicCombination = {
|
||||||
| Some(Ok(symbolicDist)) => Ok(Symbolic(symbolicDist))
|
| Some(Ok(symbolicDist)) => Ok(Symbolic(symbolicDist))
|
||||||
| Some(Error(e)) => Error(Other(e))
|
| Some(Error(e)) => Error(Other(e))
|
||||||
| None =>
|
| None =>
|
||||||
switch chooseConvolutionOrMonteCarlo(t1, t2) {
|
switch chooseConvolutionOrMonteCarlo(arithmeticOperation, t1, t2) {
|
||||||
| #CalculateWithMonteCarlo => runMonteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
|
| MonteCarlo => runMonteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
|
||||||
| #CalculateWithConvolution =>
|
| Convolution(convOp) =>
|
||||||
runConvolution(
|
runConvolution(toPointSetFn, convOp, t1, t2)->E.R2.fmap(r => DistributionTypes.PointSet(r))
|
||||||
toPointSetFn,
|
|
||||||
arithmeticOperation,
|
|
||||||
t1,
|
|
||||||
t2,
|
|
||||||
)->E.R2.fmap(r => DistributionTypes.PointSet(r))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,36 +96,25 @@ let toDiscretePointMassesFromTriangulars = (
|
||||||
}
|
}
|
||||||
|
|
||||||
let combineShapesContinuousContinuous = (
|
let combineShapesContinuousContinuous = (
|
||||||
op: Operation.algebraicOperation,
|
op: Operation.convolutionOperation,
|
||||||
s1: PointSetTypes.xyShape,
|
s1: PointSetTypes.xyShape,
|
||||||
s2: PointSetTypes.xyShape,
|
s2: PointSetTypes.xyShape,
|
||||||
): PointSetTypes.xyShape => {
|
): PointSetTypes.xyShape => {
|
||||||
// if we add the two distributions, we should probably use normal filters.
|
// if we add the two distributions, we should probably use normal filters.
|
||||||
// if we multiply the two distributions, we should probably use lognormal filters.
|
// if we multiply the two distributions, we should probably use lognormal filters.
|
||||||
let t1m = toDiscretePointMassesFromTriangulars(s1)
|
let t1m = toDiscretePointMassesFromTriangulars(s1)
|
||||||
let t2m = switch op {
|
let t2m = toDiscretePointMassesFromTriangulars(~inverse=false, s2)
|
||||||
| #Divide => toDiscretePointMassesFromTriangulars(~inverse=true, s2)
|
|
||||||
| _ => toDiscretePointMassesFromTriangulars(~inverse=false, s2)
|
|
||||||
}
|
|
||||||
|
|
||||||
let combineMeansFn = switch op {
|
let combineMeansFn = switch op {
|
||||||
| #Add => (m1, m2) => m1 +. m2
|
| #Add => (m1, m2) => m1 +. m2
|
||||||
| #Subtract => (m1, m2) => m1 -. m2
|
| #Subtract => (m1, m2) => m1 -. m2
|
||||||
| #Multiply => (m1, m2) => m1 *. m2
|
| #Multiply => (m1, m2) => m1 *. m2
|
||||||
| #Divide => (m1, mInv2) => m1 *. mInv2
|
|
||||||
| #Power => (m1, mInv2) => m1 ** mInv2
|
|
||||||
| #Logarithm => (m1, m2) => log(m1) /. log(m2)
|
|
||||||
} // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
|
} // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
|
||||||
|
|
||||||
// TODO: Variances are for exponentatiation or logarithms are almost totally made up and very likely very wrong.
|
|
||||||
// converts the variances and means of the two inputs into the variance of the output
|
|
||||||
let combineVariancesFn = switch op {
|
let combineVariancesFn = switch op {
|
||||||
| #Add => (v1, v2, _, _) => v1 +. v2
|
| #Add => (v1, v2, _, _) => v1 +. v2
|
||||||
| #Subtract => (v1, v2, _, _) => v1 +. v2
|
| #Subtract => (v1, v2, _, _) => v1 +. v2
|
||||||
| #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
| #Multiply => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||||
| #Power => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
|
||||||
| #Logarithm => (v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
|
||||||
| #Divide => (v1, vInv2, m1, mInv2) => v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: If operating on two positive-domain distributions, we should take that into account
|
// TODO: If operating on two positive-domain distributions, we should take that into account
|
||||||
|
@ -199,7 +188,7 @@ let toDiscretePointMassesFromDiscrete = (s: PointSetTypes.xyShape): pointMassesW
|
||||||
}
|
}
|
||||||
|
|
||||||
let combineShapesContinuousDiscrete = (
|
let combineShapesContinuousDiscrete = (
|
||||||
op: Operation.algebraicOperation,
|
op: Operation.convolutionOperation,
|
||||||
continuousShape: PointSetTypes.xyShape,
|
continuousShape: PointSetTypes.xyShape,
|
||||||
discreteShape: PointSetTypes.xyShape,
|
discreteShape: PointSetTypes.xyShape,
|
||||||
): PointSetTypes.xyShape => {
|
): PointSetTypes.xyShape => {
|
||||||
|
@ -207,7 +196,7 @@ let combineShapesContinuousDiscrete = (
|
||||||
let t2n = discreteShape |> XYShape.T.length
|
let t2n = discreteShape |> XYShape.T.length
|
||||||
|
|
||||||
// each x pair is added/subtracted
|
// each x pair is added/subtracted
|
||||||
let fn = Operation.Algebraic.toFn(op)
|
let fn = Operation.Convolution.toFn(op)
|
||||||
|
|
||||||
let outXYShapes: array<array<(float, float)>> = Belt.Array.makeUninitializedUnsafe(t2n)
|
let outXYShapes: array<array<(float, float)>> = Belt.Array.makeUninitializedUnsafe(t2n)
|
||||||
|
|
||||||
|
@ -231,10 +220,7 @@ let combineShapesContinuousDiscrete = (
|
||||||
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore
|
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
| #Multiply
|
| #Multiply =>
|
||||||
| #Power
|
|
||||||
| #Logarithm
|
|
||||||
| #Divide =>
|
|
||||||
for j in 0 to t2n - 1 {
|
for j in 0 to t2n - 1 {
|
||||||
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
||||||
let dxyShape: array<(float, float)> = Belt.Array.makeUninitializedUnsafe(t1n)
|
let dxyShape: array<(float, float)> = Belt.Array.makeUninitializedUnsafe(t1n)
|
||||||
|
|
|
@ -241,7 +241,7 @@ let downsampleEquallyOverX = (length, t): t =>
|
||||||
/* This simply creates multiple copies of the continuous distribution, scaled and shifted according to
|
/* This simply creates multiple copies of the continuous distribution, scaled and shifted according to
|
||||||
each discrete data point, and then adds them all together. */
|
each discrete data point, and then adds them all together. */
|
||||||
let combineAlgebraicallyWithDiscrete = (
|
let combineAlgebraicallyWithDiscrete = (
|
||||||
op: Operation.algebraicOperation,
|
op: Operation.convolutionOperation,
|
||||||
t1: t,
|
t1: t,
|
||||||
t2: PointSetTypes.discreteShape,
|
t2: PointSetTypes.discreteShape,
|
||||||
) => {
|
) => {
|
||||||
|
@ -263,8 +263,7 @@ let combineAlgebraicallyWithDiscrete = (
|
||||||
)
|
)
|
||||||
|
|
||||||
let combinedIntegralSum = switch op {
|
let combinedIntegralSum = switch op {
|
||||||
| #Multiply
|
| #Multiply =>
|
||||||
| #Divide =>
|
|
||||||
Common.combineIntegralSums((a, b) => Some(a *. b), t1.integralSumCache, t2.integralSumCache)
|
Common.combineIntegralSums((a, b) => Some(a *. b), t1.integralSumCache, t2.integralSumCache)
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
|
@ -274,7 +273,7 @@ let combineAlgebraicallyWithDiscrete = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t) => {
|
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t) => {
|
||||||
let s1 = t1 |> getShape
|
let s1 = t1 |> getShape
|
||||||
let s2 = t2 |> getShape
|
let s2 = t2 |> getShape
|
||||||
let t1n = s1 |> XYShape.T.length
|
let t1n = s1 |> XYShape.T.length
|
||||||
|
|
|
@ -72,7 +72,7 @@ let updateIntegralCache = (integralCache, t: t): t => {
|
||||||
|
|
||||||
/* This multiples all of the data points together and creates a new discrete distribution from the results.
|
/* This multiples all of the data points together and creates a new discrete distribution from the results.
|
||||||
Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */
|
Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */
|
||||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t => {
|
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t): t => {
|
||||||
let t1s = t1 |> getShape
|
let t1s = t1 |> getShape
|
||||||
let t2s = t2 |> getShape
|
let t2s = t2 |> getShape
|
||||||
let t1n = t1s |> XYShape.T.length
|
let t1n = t1s |> XYShape.T.length
|
||||||
|
@ -84,7 +84,7 @@ let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =
|
||||||
t2.integralSumCache,
|
t2.integralSumCache,
|
||||||
)
|
)
|
||||||
|
|
||||||
let fn = Operation.Algebraic.toFn(op)
|
let fn = Operation.Convolution.toFn(op)
|
||||||
let xToYMap = E.FloatFloatMap.empty()
|
let xToYMap = E.FloatFloatMap.empty()
|
||||||
|
|
||||||
for i in 0 to t1n - 1 {
|
for i in 0 to t1n - 1 {
|
||||||
|
|
|
@ -221,7 +221,7 @@ module T = Dist({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t => {
|
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t): t => {
|
||||||
// Discrete convolution can cause a huge increase in the number of samples,
|
// Discrete convolution can cause a huge increase in the number of samples,
|
||||||
// so we'll first downsample.
|
// so we'll first downsample.
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ let toMixed = mapToAll((
|
||||||
))
|
))
|
||||||
|
|
||||||
//TODO WARNING: The combineAlgebraicallyWithDiscrete will break for subtraction and division, like, discrete - continous
|
//TODO WARNING: The combineAlgebraicallyWithDiscrete will break for subtraction and division, like, discrete - continous
|
||||||
let combineAlgebraically = (op: Operation.algebraicOperation, t1: t, t2: t): t =>
|
let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t): t =>
|
||||||
switch (t1, t2) {
|
switch (t1, t2) {
|
||||||
| (Continuous(m1), Continuous(m2)) =>
|
| (Continuous(m1), Continuous(m2)) =>
|
||||||
Continuous.combineAlgebraically(op, m1, m2) |> Continuous.T.toPointSetDist
|
Continuous.combineAlgebraically(op, m1, m2) |> Continuous.T.toPointSetDist
|
||||||
|
|
|
@ -9,6 +9,13 @@ type algebraicOperation = [
|
||||||
| #Power
|
| #Power
|
||||||
| #Logarithm
|
| #Logarithm
|
||||||
]
|
]
|
||||||
|
|
||||||
|
type convolutionOperation = [
|
||||||
|
| #Add
|
||||||
|
| #Multiply
|
||||||
|
| #Subtract
|
||||||
|
]
|
||||||
|
|
||||||
@genType
|
@genType
|
||||||
type pointwiseOperation = [#Add | #Multiply | #Power]
|
type pointwiseOperation = [#Add | #Multiply | #Power]
|
||||||
type scaleOperation = [#Multiply | #Power | #Logarithm | #Divide]
|
type scaleOperation = [#Multiply | #Power | #Logarithm | #Divide]
|
||||||
|
@ -20,6 +27,16 @@ type distToFloatOperation = [
|
||||||
| #Sample
|
| #Sample
|
||||||
]
|
]
|
||||||
|
|
||||||
|
module Convolution = {
|
||||||
|
type t = convolutionOperation
|
||||||
|
let toFn: (t, float, float) => float = x =>
|
||||||
|
switch x {
|
||||||
|
| #Add => \"+."
|
||||||
|
| #Subtract => \"-."
|
||||||
|
| #Multiply => \"*."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module Algebraic = {
|
module Algebraic = {
|
||||||
type t = algebraicOperation
|
type t = algebraicOperation
|
||||||
let toFn: (t, float, float) => float = x =>
|
let toFn: (t, float, float) => float = x =>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user