Merge pull request #342 from quantified-uncertainty/restrict-convolution

Use a more conservative convolution policy
This commit is contained in:
Ozzie Gooen 2022-04-22 12:57:47 -04:00 committed by GitHub
commit b629759542
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 61 additions and 49 deletions

View File

@ -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/

View File

@ -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", () => {

View File

@ -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))
} }
} }
} }

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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.

View File

@ -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

View File

@ -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 =>