diff --git a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res index 078cad05..60e74d74 100644 --- a/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/DistributionOperation_test.res @@ -30,7 +30,7 @@ let toExt: option<'a> => 'a = E.O.toExt( describe("sparkline", () => { let runTest = ( name: string, - dist: GenericDist_Types.genericDist, + dist: DistributionTypes.genericDist, expected: DistributionOperation.outputType, ) => { test(name, () => { diff --git a/packages/squiggle-lang/__tests__/Distributions/GenericDist_Fixtures.res b/packages/squiggle-lang/__tests__/Distributions/GenericDist_Fixtures.res index a967b924..4d41930f 100644 --- a/packages/squiggle-lang/__tests__/Distributions/GenericDist_Fixtures.res +++ b/packages/squiggle-lang/__tests__/Distributions/GenericDist_Fixtures.res @@ -1,14 +1,14 @@ -let normalDist5: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0})) -let normalDist10: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 10.0, stdev: 2.0})) -let normalDist20: GenericDist_Types.genericDist = Symbolic(#Normal({mean: 20.0, stdev: 2.0})) -let normalDist: GenericDist_Types.genericDist = normalDist5 +let normalDist5: DistributionTypes.genericDist = Symbolic(#Normal({mean: 5.0, stdev: 2.0})) +let normalDist10: DistributionTypes.genericDist = Symbolic(#Normal({mean: 10.0, stdev: 2.0})) +let normalDist20: DistributionTypes.genericDist = Symbolic(#Normal({mean: 20.0, stdev: 2.0})) +let normalDist: DistributionTypes.genericDist = normalDist5 -let betaDist: GenericDist_Types.genericDist = Symbolic(#Beta({alpha: 2.0, beta: 5.0})) -let lognormalDist: GenericDist_Types.genericDist = Symbolic(#Lognormal({mu: 0.0, sigma: 1.0})) -let cauchyDist: GenericDist_Types.genericDist = Symbolic(#Cauchy({local: 1.0, scale: 1.0})) -let triangularDist: GenericDist_Types.genericDist = Symbolic( +let betaDist: DistributionTypes.genericDist = Symbolic(#Beta({alpha: 2.0, beta: 5.0})) +let lognormalDist: DistributionTypes.genericDist = Symbolic(#Lognormal({mu: 0.0, sigma: 1.0})) +let cauchyDist: DistributionTypes.genericDist = Symbolic(#Cauchy({local: 1.0, scale: 1.0})) +let triangularDist: DistributionTypes.genericDist = Symbolic( #Triangular({low: 1.0, medium: 2.0, high: 3.0}), ) -let exponentialDist: GenericDist_Types.genericDist = Symbolic(#Exponential({rate: 2.0})) -let uniformDist: GenericDist_Types.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0})) -let floatDist: GenericDist_Types.genericDist = Symbolic(#Float(1e1)) +let exponentialDist: DistributionTypes.genericDist = Symbolic(#Exponential({rate: 2.0})) +let uniformDist: DistributionTypes.genericDist = Symbolic(#Uniform({low: 9.0, high: 10.0})) +let floatDist: DistributionTypes.genericDist = Symbolic(#Float(1e1)) diff --git a/packages/squiggle-lang/__tests__/Distributions/Invariants/AlgebraicCombination_test.res b/packages/squiggle-lang/__tests__/Distributions/Invariants/AlgebraicCombination_test.res index c7e0ac46..718a4e14 100644 --- a/packages/squiggle-lang/__tests__/Distributions/Invariants/AlgebraicCombination_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/Invariants/AlgebraicCombination_test.res @@ -43,7 +43,7 @@ describe("(Algebraic) addition of distributions", () => { test("normal(mean=5) + normal(mean=20)", () => { normalDist5 ->algebraicAdd(normalDist20) - ->E.R2.fmap(GenericDist_Types.Constructors.UsingDists.mean) + ->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -57,7 +57,7 @@ describe("(Algebraic) addition of distributions", () => { let received = uniformDist ->algebraicAdd(betaDist) - ->E.R2.fmap(GenericDist_Types.Constructors.UsingDists.mean) + ->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -74,7 +74,7 @@ describe("(Algebraic) addition of distributions", () => { let received = betaDist ->algebraicAdd(uniformDist) - ->E.R2.fmap(GenericDist_Types.Constructors.UsingDists.mean) + ->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -95,7 +95,7 @@ describe("(Algebraic) addition of distributions", () => { let received = normalDist10 // this should be normal(10, sqrt(8)) ->Ok - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, x)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, x)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -103,7 +103,7 @@ describe("(Algebraic) addition of distributions", () => { let calculated = normalDist5 ->algebraicAdd(normalDist5) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, x)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, x)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -126,7 +126,7 @@ describe("(Algebraic) addition of distributions", () => { let received = normalDist20 ->Ok - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1.9e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -134,7 +134,7 @@ describe("(Algebraic) addition of distributions", () => { let calculated = normalDist10 ->algebraicAdd(normalDist10) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1.9e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -155,7 +155,7 @@ describe("(Algebraic) addition of distributions", () => { let received = uniformDist ->algebraicAdd(betaDist) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -170,7 +170,7 @@ describe("(Algebraic) addition of distributions", () => { let received = betaDist ->algebraicAdd(uniformDist) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.pdf(d, 1e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -187,7 +187,7 @@ describe("(Algebraic) addition of distributions", () => { let received = normalDist10 ->Ok - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, x)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -195,7 +195,7 @@ describe("(Algebraic) addition of distributions", () => { let calculated = normalDist5 ->algebraicAdd(normalDist5) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, x)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -217,7 +217,7 @@ describe("(Algebraic) addition of distributions", () => { let received = normalDist20 ->Ok - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1.25e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -225,7 +225,7 @@ describe("(Algebraic) addition of distributions", () => { let calculated = normalDist10 ->algebraicAdd(normalDist10) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1.25e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -246,7 +246,7 @@ describe("(Algebraic) addition of distributions", () => { let received = uniformDist ->algebraicAdd(betaDist) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -261,7 +261,7 @@ describe("(Algebraic) addition of distributions", () => { let received = betaDist ->algebraicAdd(uniformDist) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.cdf(d, 1e1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -279,7 +279,7 @@ describe("(Algebraic) addition of distributions", () => { let received = normalDist10 ->Ok - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, x)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -287,7 +287,7 @@ describe("(Algebraic) addition of distributions", () => { let calculated = normalDist5 ->algebraicAdd(normalDist5) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, x)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -309,7 +309,7 @@ describe("(Algebraic) addition of distributions", () => { let received = normalDist20 ->Ok - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 1e-1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -317,7 +317,7 @@ describe("(Algebraic) addition of distributions", () => { let calculated = normalDist10 ->algebraicAdd(normalDist10) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 1e-1)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toOption @@ -338,7 +338,7 @@ describe("(Algebraic) addition of distributions", () => { let received = uniformDist ->algebraicAdd(betaDist) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 2e-2)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn @@ -353,7 +353,7 @@ describe("(Algebraic) addition of distributions", () => { let received = betaDist ->algebraicAdd(uniformDist) - ->E.R2.fmap(d => GenericDist_Types.Constructors.UsingDists.inv(d, 2e-2)) + ->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2)) ->E.R2.fmap(run) ->E.R2.fmap(toFloat) ->E.R.toExn diff --git a/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res b/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res index bcc461bc..3806c7f9 100644 --- a/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res +++ b/packages/squiggle-lang/__tests__/Distributions/Invariants/Means_test.res @@ -15,7 +15,7 @@ open TestHelpers module Internals = { let epsilon = 5e1 - let mean = GenericDist_Types.Constructors.UsingDists.mean + let mean = DistributionTypes.Constructors.UsingDists.mean let expectImpossiblePath: string => assertion = algebraicOp => `${algebraicOp} has`->expect->toEqual("failed") diff --git a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res index d3defb40..65a76b88 100644 --- a/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res +++ b/packages/squiggle-lang/__tests__/ReducerInterface/ReducerInterface_Distribution_test.res @@ -22,7 +22,7 @@ describe("eval on distribution functions", () => { testEval("mean(-normal(5,2))", "Ok(-5)") }) describe("to", () => { - testEval("5 to 2", "Error(TODO: Low value must be less than high value.)") + testEval("5 to 2", "Error(Math Error: Low value must be less than high value.)") testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))") testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))") }) @@ -88,8 +88,14 @@ describe("eval on distribution functions", () => { describe("log", () => { testEval("log(2, uniform(5,8))", "Ok(Sample 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), 3)", + "Error(Math Error: Operation Error: Operation returned complex result)", + ) + testEval( + "log(normal(5,2), normal(10,1))", + "Error(Math Error: Operation Error: Operation returned complex result)", + ) testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)") testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)") }) diff --git a/packages/squiggle-lang/__tests__/TS/Symbolic_test.ts b/packages/squiggle-lang/__tests__/TS/Symbolic_test.ts index 71d87642..1bcb1e2e 100644 --- a/packages/squiggle-lang/__tests__/TS/Symbolic_test.ts +++ b/packages/squiggle-lang/__tests__/TS/Symbolic_test.ts @@ -12,7 +12,7 @@ describe("Symbolic mean", () => { expect(squiggleResult.value).toBeCloseTo((x + y + z) / 3); } catch (err) { expect((err as Error).message).toEqual( - "Expected squiggle expression to evaluate but got error: TODO: Triangular values must be increasing order." + "Expected squiggle expression to evaluate but got error: Math Error: Triangular values must be increasing order." ); } } diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res index 44584791..77d6acc5 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.res @@ -1,4 +1,4 @@ -type functionCallInfo = GenericDist_Types.Operation.genericFunctionCallInfo +type functionCallInfo = DistributionTypes.DistributionOperation.genericFunctionCallInfo type genericDist = DistributionTypes.genericDist type error = DistributionTypes.error @@ -120,7 +120,10 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => { (), )->OutputLocal.toDistR - let fromDistFn = (subFnName: GenericDist_Types.Operation.fromDist, dist: genericDist) => { + let fromDistFn = ( + subFnName: DistributionTypes.DistributionOperation.fromDist, + dist: genericDist, + ) => { let response = switch subFnName { | ToFloat(distToFloatOperation) => GenericDist.toFloatOperation(dist, ~toPointSetFn, ~distToFloatOperation) @@ -162,9 +165,9 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => { ->GenericDist.pointwiseCombination(~toPointSetFn, ~arithmeticOperation, ~t2) ->E.R2.fmap(r => Dist(r)) ->OutputLocal.fromResult - | ToDistCombination(Pointwise, arithmeticOperation, #Float(float)) => + | ToDistCombination(Pointwise, arithmeticOperation, #Float(f)) => dist - ->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~arithmeticOperation, ~float) + ->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~arithmeticOperation, ~f) ->E.R2.fmap(r => Dist(r)) ->OutputLocal.fromResult } @@ -192,7 +195,7 @@ module Output = { let fmap = ( ~env, input: outputType, - functionCallInfo: GenericDist_Types.Operation.singleParamaterFunction, + functionCallInfo: DistributionTypes.DistributionOperation.singleParamaterFunction, ): outputType => { let newFnCall: result = switch (functionCallInfo, input) { | (FromDist(fromDist), Dist(o)) => Ok(FromDist(fromDist, o)) @@ -205,11 +208,11 @@ module Output = { } } -// See comment above GenericDist_Types.Constructors to explain the purpose of this module. +// See comment above DistributionTypes.Constructors to explain the purpose of this module. // I tried having another internal module called UsingDists, similar to how its done in -// GenericDist_Types.Constructors. However, this broke GenType for me, so beware. +// DistributionTypes.Constructors. However, this broke GenType for me, so beware. module Constructors = { - module C = GenericDist_Types.Constructors.UsingDists + module C = DistributionTypes.Constructors.UsingDists open OutputLocal let mean = (~env, dist) => C.mean(dist)->run(~env)->toFloatR let sample = (~env, dist) => C.sample(dist)->run(~env)->toFloatR diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi index 9b21a04e..5ad34354 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionOperation/DistributionOperation.resi @@ -4,7 +4,7 @@ type env = { xyPointLength: int, } -open GenericDist_Types +open DistributionTypes @genType type outputType = @@ -15,15 +15,15 @@ type outputType = | GenDistError(error) @genType -let run: (~env: env, GenericDist_Types.Operation.genericFunctionCallInfo) => outputType +let run: (~env: env, DistributionTypes.DistributionOperation.genericFunctionCallInfo) => outputType let runFromDist: ( ~env: env, - ~functionCallInfo: GenericDist_Types.Operation.fromDist, + ~functionCallInfo: DistributionTypes.DistributionOperation.fromDist, genericDist, ) => outputType let runFromFloat: ( ~env: env, - ~functionCallInfo: GenericDist_Types.Operation.fromDist, + ~functionCallInfo: DistributionTypes.DistributionOperation.fromDist, float, ) => outputType @@ -38,7 +38,7 @@ module Output: { let toBool: t => option let toBoolR: t => result let toError: t => option - let fmap: (~env: env, t, GenericDist_Types.Operation.singleParamaterFunction) => t + let fmap: (~env: env, t, DistributionTypes.DistributionOperation.singleParamaterFunction) => t } module Constructors: { diff --git a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res index 4082cb6e..b171fa95 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res +++ b/packages/squiggle-lang/src/rescript/Distributions/DistributionTypes.res @@ -9,9 +9,43 @@ type error = | NotYetImplemented | Unreachable | DistributionVerticalShiftIsInvalid + | TooFewSamples | ArgumentError(string) + | OperationError(Operation.invalidOperationError) + | PointSetConversionError(SampleSetDist.pointsetConversionError) + | SparklineError(PointSetDist.sparklineError) // This type of error is for when we find a sparkline of a discrete distribution. This should probably at some point be actually implemented | Other(string) +let sampleErrorToDistErr = (err: SampleSetDist.sampleSetError): error => + switch err { + | TooFewSamples => TooFewSamples + } + +@genType +module Error = { + type t = error + + let fromString = (s: string): t => Other(s) + + @genType + let toString = (err: error): string => + switch err { + | NotYetImplemented => "Function Not Yet Implemented" + | Unreachable => "Unreachable" + | DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift is Invalid" + | ArgumentError(s) => `Argument Error ${s}` + | TooFewSamples => "Too Few Samples" + | OperationError(err) => `Operation Error: ${Operation.invalidOperationErrorToString(err)}` + | PointSetConversionError(err) => SampleSetDist.pointsetConversionErrorToString(err) + | SparklineError(err) => PointSetDist.sparklineErrorToString(err) + | Other(s) => s + } + + let resultStringToResultError: result<'a, string> => result<'a, error> = n => + n->E.R2.errMap(r => r->fromString) +} + +@genType module Operation = { type direction = | Algebraic @@ -43,6 +77,9 @@ module Operation = { | #Mean | #Sample ] + + @genType + type pointsetXSelection = [#Linear | #ByWeight] } module DistributionOperation = { @@ -55,6 +92,12 @@ module DistributionOperation = { type toFloatArray = Sample(int) + type toBool = IsNormalized + + type toString = + | ToString + | ToSparkline(int) + type fromDist = | ToFloat(Operation.toFloat) | ToDist(toDist) @@ -63,7 +106,8 @@ module DistributionOperation = { Operation.arithmeticOperation, [#Dist(genericDist) | #Float(float)], ) - | ToString + | ToString(toString) + | ToBool(toBool) type singleParamaterFunction = | FromDist(fromDist) @@ -86,7 +130,9 @@ module DistributionOperation = { | ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})` | ToDist(Truncate(_, _)) => `truncate` | ToDist(Inspect) => `inspect` - | ToString => `toString` + | ToString(ToString) => `toString` + | ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})` + | ToBool(IsNormalized) => `isNormalized` | ToDistCombination(Algebraic, _, _) => `algebraic` | ToDistCombination(Pointwise, _, _) => `pointwise` } @@ -97,3 +143,71 @@ module DistributionOperation = { | Mixture(_) => `mixture` } } +module Constructors = { + type t = DistributionOperation.genericFunctionCallInfo + + module UsingDists = { + @genType + let mean = (dist): t => FromDist(ToFloat(#Mean), dist) + let sample = (dist): t => FromDist(ToFloat(#Sample), dist) + let cdf = (dist, x): t => FromDist(ToFloat(#Cdf(x)), dist) + let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist) + let pdf = (dist, x): t => FromDist(ToFloat(#Pdf(x)), dist) + let normalize = (dist): t => FromDist(ToDist(Normalize), dist) + let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist) + let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist) + let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist) + let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist) + let inspect = (dist): t => FromDist(ToDist(Inspect), dist) + let toString = (dist): t => FromDist(ToString(ToString), dist) + let toSparkline = (dist, n): t => FromDist(ToString(ToSparkline(n)), dist) + let algebraicAdd = (dist1, dist2: genericDist): t => FromDist( + ToDistCombination(Algebraic, #Add, #Dist(dist2)), + dist1, + ) + let algebraicMultiply = (dist1, dist2): t => FromDist( + ToDistCombination(Algebraic, #Multiply, #Dist(dist2)), + dist1, + ) + let algebraicDivide = (dist1, dist2): t => FromDist( + ToDistCombination(Algebraic, #Divide, #Dist(dist2)), + dist1, + ) + let algebraicSubtract = (dist1, dist2): t => FromDist( + ToDistCombination(Algebraic, #Subtract, #Dist(dist2)), + dist1, + ) + let algebraicLogarithm = (dist1, dist2): t => FromDist( + ToDistCombination(Algebraic, #Logarithm, #Dist(dist2)), + dist1, + ) + let algebraicPower = (dist1, dist2): t => FromDist( + ToDistCombination(Algebraic, #Power, #Dist(dist2)), + dist1, + ) + let pointwiseAdd = (dist1, dist2): t => FromDist( + ToDistCombination(Pointwise, #Add, #Dist(dist2)), + dist1, + ) + let pointwiseMultiply = (dist1, dist2): t => FromDist( + ToDistCombination(Pointwise, #Multiply, #Dist(dist2)), + dist1, + ) + let pointwiseDivide = (dist1, dist2): t => FromDist( + ToDistCombination(Pointwise, #Divide, #Dist(dist2)), + dist1, + ) + let pointwiseSubtract = (dist1, dist2): t => FromDist( + ToDistCombination(Pointwise, #Subtract, #Dist(dist2)), + dist1, + ) + let pointwiseLogarithm = (dist1, dist2): t => FromDist( + ToDistCombination(Pointwise, #Logarithm, #Dist(dist2)), + dist1, + ) + let pointwisePower = (dist1, dist2): t => FromDist( + ToDistCombination(Pointwise, #Power, #Dist(dist2)), + dist1, + ) + } +} diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res index 5e62de53..5a195f4b 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.res @@ -14,7 +14,7 @@ let sampleN = (t: t, n) => } let toSampleSetDist = (t: t, n) => - SampleSetDist.make(sampleN(t, n))->GenericDist_Types.Error.resultStringToResultError + SampleSetDist.make(sampleN(t, n))->E.R2.errMap(DistributionTypes.sampleErrorToDistErr) let fromFloat = (f: float): t => Symbolic(SymbolicDist.Float.make(f)) @@ -68,7 +68,7 @@ let toPointSet = ( t, ~xyPointLength, ~sampleCount, - ~xSelection: GenericDist_Types.Operation.pointsetXSelection=#ByWeight, + ~xSelection: DistributionTypes.Operation.pointsetXSelection=#ByWeight, (), ): result => { switch (t: t) { @@ -83,7 +83,7 @@ let toPointSet = ( pointSetDistLength: xyPointLength, kernelWidth: None, }, - )->GenericDist_Types.Error.resultStringToResultError + )->E.R2.errMap(x => DistributionTypes.PointSetConversionError(x)) } } @@ -97,7 +97,7 @@ let toSparkline = (t: t, ~sampleCount: int, ~bucketCount: int=20, ()): resulttoPointSet(~xSelection=#Linear, ~xyPointLength=bucketCount * 3, ~sampleCount, ()) ->E.R.bind(r => - r->PointSetDist.toSparkline(bucketCount)->GenericDist_Types.Error.resultStringToResultError + r->PointSetDist.toSparkline(bucketCount)->E.R2.errMap(x => DistributionTypes.SparklineError(x)) ) module Truncate = { @@ -148,10 +148,10 @@ let truncate = Truncate.run */ module AlgebraicCombination = { let tryAnalyticalSimplification = ( - arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation, + arithmeticOperation: DistributionTypes.Operation.arithmeticOperation, t1: t, t2: t, - ): option> => + ): option> => switch (arithmeticOperation, t1, t2) { | (arithmeticOperation, Symbolic(d1), Symbolic(d2)) => switch SymbolicDist.T.tryAnalyticalSimplification(d1, d2, arithmeticOperation) { @@ -174,14 +174,14 @@ module AlgebraicCombination = { let runMonteCarlo = ( toSampleSet: toSampleSetFn, - arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation, + arithmeticOperation: DistributionTypes.Operation.arithmeticOperation, t1: t, t2: t, - ) => { + ): result => { let fn = Operation.Algebraic.toFn(arithmeticOperation) E.R.merge(toSampleSet(t1), toSampleSet(t2)) ->E.R.bind(((t1, t2)) => { - SampleSetDist.map2(~fn, ~t1, ~t2)->GenericDist_Types.Error.resultStringToResultError + SampleSetDist.map2(~fn, ~t1, ~t2)->E.R2.errMap(x => DistributionTypes.OperationError(x)) }) ->E.R2.fmap(r => DistributionTypes.SampleSet(r)) } @@ -224,7 +224,7 @@ module AlgebraicCombination = { ): result => { switch tryAnalyticalSimplification(arithmeticOperation, t1, t2) { | Some(Ok(symbolicDist)) => Ok(Symbolic(symbolicDist)) - | Some(Error(e)) => Error(Other(e)) + | Some(Error(e)) => Error(OperationError(e)) | None => switch chooseConvolutionOrMonteCarlo(arithmeticOperation, t1, t2) { | MonteCarlo => runMonteCarlo(toSampleSetFn, arithmeticOperation, t1, t2) @@ -247,7 +247,7 @@ let pointwiseCombination = ( E.R.merge(toPointSetFn(t1), toPointSetFn(t2)) ->E.R2.fmap(((t1, t2)) => PointSetDist.combinePointwise( - GenericDist_Types.Operation.arithmeticToFn(arithmeticOperation), + DistributionTypes.Operation.arithmeticToFn(arithmeticOperation), t1, t2, ) @@ -258,23 +258,23 @@ let pointwiseCombination = ( let pointwiseCombinationFloat = ( t: t, ~toPointSetFn: toPointSetFn, - ~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation, - ~float: float, + ~arithmeticOperation: DistributionTypes.Operation.arithmeticOperation, + ~f: float, ): result => { let m = switch arithmeticOperation { | #Add | #Subtract => Error(DistributionTypes.DistributionVerticalShiftIsInvalid) | (#Multiply | #Divide | #Power | #Logarithm) as arithmeticOperation => - toPointSetFn(t)->E.R2.fmap(t => { + toPointSetFn(t)->E.R.bind(t => { //TODO: Move to PointSet codebase let fn = (secondary, main) => Operation.Scale.toFn(arithmeticOperation, main, secondary) let integralSumCacheFn = Operation.Scale.toIntegralSumCacheFn(arithmeticOperation) let integralCacheFn = Operation.Scale.toIntegralCacheFn(arithmeticOperation) - PointSetDist.T.mapY( - ~integralSumCacheFn=integralSumCacheFn(float), - ~integralCacheFn=integralCacheFn(float), - ~fn=fn(float), + PointSetDist.T.mapYResult( + ~integralSumCacheFn=integralSumCacheFn(f), + ~integralCacheFn=integralCacheFn(f), + ~fn=fn(f), t, - ) + )->E.R2.errMap(x => DistributionTypes.OperationError(x)) }) } m->E.R2.fmap(r => DistributionTypes.PointSet(r)) diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi index c49d18b5..276ca4e3 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi +++ b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist.resi @@ -1,5 +1,5 @@ -type t = GenericDist_Types.genericDist -type error = GenericDist_Types.error +type t = DistributionTypes.genericDist +type error = DistributionTypes.error type toPointSetFn = t => result type toSampleSetFn = t => result type scaleMultiplyFn = (t, float) => result @@ -28,7 +28,7 @@ let toPointSet: ( t, ~xyPointLength: int, ~sampleCount: int, - ~xSelection: GenericDist_Types.Operation.pointsetXSelection=?, + ~xSelection: DistributionTypes.Operation.pointsetXSelection=?, unit, ) => result let toSparkline: (t, ~sampleCount: int, ~bucketCount: int=?, unit) => result @@ -45,22 +45,22 @@ let algebraicCombination: ( t, ~toPointSetFn: toPointSetFn, ~toSampleSetFn: toSampleSetFn, - ~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation, + ~arithmeticOperation: DistributionTypes.Operation.arithmeticOperation, ~t2: t, ) => result let pointwiseCombination: ( t, ~toPointSetFn: toPointSetFn, - ~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation, + ~arithmeticOperation: DistributionTypes.Operation.arithmeticOperation, ~t2: t, ) => result let pointwiseCombinationFloat: ( t, ~toPointSetFn: toPointSetFn, - ~arithmeticOperation: GenericDist_Types.Operation.arithmeticOperation, - ~float: float, + ~arithmeticOperation: DistributionTypes.Operation.arithmeticOperation, + ~f: float, ) => result let mixture: ( diff --git a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res b/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res deleted file mode 100644 index b045a6d5..00000000 --- a/packages/squiggle-lang/src/rescript/Distributions/GenericDist/GenericDist_Types.res +++ /dev/null @@ -1,194 +0,0 @@ -type genericDist = DistributionTypes.genericDist -@genType -type error = DistributionTypes.error - -@genType -module Error = { - type t = error - - let fromString = (s: string): t => Other(s) - - @genType - let toString = (x: t) => { - switch x { - | NotYetImplemented => "Not Yet Implemented" - | Unreachable => "Unreachable" - | DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift Is Invalid" - | ArgumentError(x) => `Argument Error: ${x}` - | Other(s) => s - } - } - - let resultStringToResultError: result<'a, string> => result<'a, error> = n => - n->E.R2.errMap(r => r->fromString->Error) -} - -module Operation = { - type direction = - | Algebraic - | Pointwise - - type arithmeticOperation = [ - | #Add - | #Multiply - | #Subtract - | #Divide - | #Power - | #Logarithm - ] - - let arithmeticToFn = (arithmetic: arithmeticOperation) => - switch arithmetic { - | #Add => \"+." - | #Multiply => \"*." - | #Subtract => \"-." - | #Power => \"**" - | #Divide => \"/." - | #Logarithm => (a, b) => log(a) /. log(b) - } - - type toFloat = [ - | #Cdf(float) - | #Inv(float) - | #Mean - | #Pdf(float) - | #Sample - ] - - @genType - type pointsetXSelection = [#Linear | #ByWeight] - - type toDist = - | Normalize - | ToPointSet - | ToSampleSet(int) - | Truncate(option, option) - | Inspect - - type toFloatArray = Sample(int) - - type toString = - | ToString - | ToSparkline(int) - - type toBool = IsNormalized - - type fromDist = - | ToFloat(toFloat) - | ToDist(toDist) - | ToDistCombination(direction, arithmeticOperation, [#Dist(genericDist) | #Float(float)]) - | ToString(toString) - | ToBool(toBool) - - type singleParamaterFunction = - | FromDist(fromDist) - | FromFloat(fromDist) - - @genType - type genericFunctionCallInfo = - | FromDist(fromDist, genericDist) - | FromFloat(fromDist, float) - | Mixture(array<(genericDist, float)>) - - let distCallToString = (distFunction: fromDist): string => - switch distFunction { - | ToFloat(#Cdf(r)) => `cdf(${E.Float.toFixed(r)})` - | ToFloat(#Inv(r)) => `inv(${E.Float.toFixed(r)})` - | ToFloat(#Mean) => `mean` - | ToFloat(#Pdf(r)) => `pdf(${E.Float.toFixed(r)})` - | ToFloat(#Sample) => `sample` - | ToDist(Normalize) => `normalize` - | ToDist(ToPointSet) => `toPointSet` - | ToDist(ToSampleSet(r)) => `toSampleSet(${E.I.toString(r)})` - | ToDist(Truncate(_, _)) => `truncate` - | ToDist(Inspect) => `inspect` - | ToString(ToString) => `toString` - | ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})` - | ToBool(IsNormalized) => `isNormalized` - | ToDistCombination(Algebraic, _, _) => `algebraic` - | ToDistCombination(Pointwise, _, _) => `pointwise` - } - - let toString = (d: genericFunctionCallInfo): string => - switch d { - | FromDist(f, _) | FromFloat(f, _) => distCallToString(f) - | Mixture(_) => `mixture` - } -} - -/* -It can be a pain to write out the genericFunctionCallInfo. The constructors help with this. -This code only covers some of genericFunctionCallInfo: many arguments could be called with either a -float or a distribution. The "UsingDists" module assumes that everything is a distribution. -This is a tradeoff of some generality in order to get a bit more simplicity. -I could see having a longer interface in the future, but it could be messy. -Like, algebraicAddDistFloat vs. algebraicAddDistDist -*/ -module Constructors = { - type t = Operation.genericFunctionCallInfo - - module UsingDists = { - @genType - let mean = (dist): t => FromDist(ToFloat(#Mean), dist) - let sample = (dist): t => FromDist(ToFloat(#Sample), dist) - let cdf = (dist, x): t => FromDist(ToFloat(#Cdf(x)), dist) - let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist) - let pdf = (dist, x): t => FromDist(ToFloat(#Pdf(x)), dist) - let normalize = (dist): t => FromDist(ToDist(Normalize), dist) - let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist) - let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist) - let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist) - let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist) - let inspect = (dist): t => FromDist(ToDist(Inspect), dist) - let toString = (dist): t => FromDist(ToString(ToString), dist) - let toSparkline = (dist, n): t => FromDist(ToString(ToSparkline(n)), dist) - let algebraicAdd = (dist1, dist2: genericDist): t => FromDist( - ToDistCombination(Algebraic, #Add, #Dist(dist2)), - dist1, - ) - let algebraicMultiply = (dist1, dist2): t => FromDist( - ToDistCombination(Algebraic, #Multiply, #Dist(dist2)), - dist1, - ) - let algebraicDivide = (dist1, dist2): t => FromDist( - ToDistCombination(Algebraic, #Divide, #Dist(dist2)), - dist1, - ) - let algebraicSubtract = (dist1, dist2): t => FromDist( - ToDistCombination(Algebraic, #Subtract, #Dist(dist2)), - dist1, - ) - let algebraicLogarithm = (dist1, dist2): t => FromDist( - ToDistCombination(Algebraic, #Logarithm, #Dist(dist2)), - dist1, - ) - let algebraicPower = (dist1, dist2): t => FromDist( - ToDistCombination(Algebraic, #Power, #Dist(dist2)), - dist1, - ) - let pointwiseAdd = (dist1, dist2): t => FromDist( - ToDistCombination(Pointwise, #Add, #Dist(dist2)), - dist1, - ) - let pointwiseMultiply = (dist1, dist2): t => FromDist( - ToDistCombination(Pointwise, #Multiply, #Dist(dist2)), - dist1, - ) - let pointwiseDivide = (dist1, dist2): t => FromDist( - ToDistCombination(Pointwise, #Divide, #Dist(dist2)), - dist1, - ) - let pointwiseSubtract = (dist1, dist2): t => FromDist( - ToDistCombination(Pointwise, #Subtract, #Dist(dist2)), - dist1, - ) - let pointwiseLogarithm = (dist1, dist2): t => FromDist( - ToDistCombination(Pointwise, #Logarithm, #Dist(dist2)), - dist1, - ) - let pointwisePower = (dist1, dist2): t => FromDist( - ToDistCombination(Pointwise, #Power, #Dist(dist2)), - dist1, - ) - } -} diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Continuous.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Continuous.res index 6c9cb3fd..5512b5cb 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Continuous.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Continuous.res @@ -146,7 +146,27 @@ let reduce = ( continuousShapes, ) => continuousShapes |> E.A.fold_left(combinePointwise(~integralSumCachesFn, fn), empty) -let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn, t: t) => +let mapYResult = ( + ~integralSumCacheFn=_ => None, + ~integralCacheFn=_ => None, + ~fn: float => result, + t: t, +): result => + XYShape.T.mapYResult(fn, getShape(t))->E.R2.fmap(x => + make( + ~interpolation=t.interpolation, + ~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn), + ~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn), + x, + ) + ) + +let mapY = ( + ~integralSumCacheFn=_ => None, + ~integralCacheFn=_ => None, + ~fn: float => float, + t: t, +): t => make( ~interpolation=t.interpolation, ~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn), @@ -170,6 +190,7 @@ module T = Dist({ let minX = shapeFn(XYShape.T.minX) let maxX = shapeFn(XYShape.T.maxX) let mapY = mapY + let mapYResult = mapYResult let updateIntegralCache = updateIntegralCache let toDiscreteProbabilityMassFraction = _ => 0.0 let toPointSetDist = (t: t): PointSetTypes.pointSetDist => Continuous(t) diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res index 4037338d..92fdc7f0 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Discrete.res @@ -103,7 +103,26 @@ let combineAlgebraically = (op: Operation.convolutionOperation, t1: t, t2: t): t make(~integralSumCache=combinedIntegralSum, combinedShape) } -let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn, t: t) => +let mapYResult = ( + ~integralSumCacheFn=_ => None, + ~integralCacheFn=_ => None, + ~fn: float => result, + t: t, +): result => + XYShape.T.mapYResult(fn, getShape(t))->E.R2.fmap(x => + make( + ~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn), + ~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn), + x, + ) + ) + +let mapY = ( + ~integralSumCacheFn=_ => None, + ~integralCacheFn=_ => None, + ~fn: float => float, + t: t, +): t => make( ~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn), ~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn), @@ -143,6 +162,7 @@ module T = Dist({ let maxX = shapeFn(XYShape.T.maxX) let toDiscreteProbabilityMassFraction = _ => 1.0 let mapY = mapY + let mapYResult = mapYResult let updateIntegralCache = updateIntegralCache let toPointSetDist = (t: t): PointSetTypes.pointSetDist => Discrete(t) let toContinuous = _ => None diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Distributions.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Distributions.res index 0821611c..407eae85 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Distributions.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Distributions.res @@ -9,6 +9,12 @@ module type dist = { ~fn: float => float, t, ) => t + let mapYResult: ( + ~integralSumCacheFn: float => option=?, + ~integralCacheFn: PointSetTypes.continuousShape => option=?, + ~fn: float => result, + t, + ) => result let xToY: (float, t) => PointSetTypes.mixedPoint let toPointSetDist: t => PointSetTypes.pointSetDist let toContinuous: t => option @@ -37,6 +43,7 @@ module Dist = (T: dist) => { let integral = T.integral let xTotalRange = (t: t) => maxX(t) -. minX(t) let mapY = T.mapY + let mapYResult = T.mapYResult let xToY = T.xToY let downsample = T.downsample let toPointSetDist = T.toPointSetDist diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Mixed.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Mixed.res index 223c55e2..4d441a9a 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Mixed.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/Mixed.res @@ -164,7 +164,12 @@ module T = Dist({ // This pipes all ys (continuous and discrete) through fn. // If mapY is a linear operation, we might be able to update the integralSumCaches as well; // if not, they'll be set to None. - let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn, t: t): t => { + let mapY = ( + ~integralSumCacheFn=_ => None, + ~integralCacheFn=_ => None, + ~fn: float => float, + t: t, + ): t => { let yMappedDiscrete: PointSetTypes.discreteShape = t.discrete |> Discrete.T.mapY(~fn) @@ -187,6 +192,41 @@ module T = Dist({ } } + let mapYResult = ( + ~integralSumCacheFn=_ => None, + ~integralCacheFn=_ => None, + ~fn: float => result, + t: t, + ): result => { + E.R.merge( + Discrete.T.mapYResult(~fn, t.discrete), + Continuous.T.mapYResult(~fn, t.continuous), + )->E.R2.fmap(((discreteMapped, continuousMapped)) => { + let yMappedDiscrete: PointSetTypes.discreteShape = + discreteMapped + |> Discrete.updateIntegralSumCache( + E.O.bind(t.discrete.integralSumCache, integralSumCacheFn), + ) + |> Discrete.updateIntegralCache(E.O.bind(t.discrete.integralCache, integralCacheFn)) + + let yMappedContinuous: PointSetTypes.continuousShape = + continuousMapped + |> Continuous.updateIntegralSumCache( + E.O.bind(t.continuous.integralSumCache, integralSumCacheFn), + ) + |> Continuous.updateIntegralCache(E.O.bind(t.continuous.integralCache, integralCacheFn)) + + ( + { + discrete: yMappedDiscrete, + continuous: yMappedContinuous, + integralSumCache: E.O.bind(t.integralSumCache, integralSumCacheFn), + integralCache: E.O.bind(t.integralCache, integralCacheFn), + }: t + ) + }) + } + let mean = ({discrete, continuous}: t): float => { let discreteMean = Discrete.T.mean(discrete) let continuousMean = Continuous.T.mean(continuous) diff --git a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res index 0035d646..b654c890 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/PointSetDist/PointSetDist.res @@ -16,6 +16,13 @@ let fmap = ((fn1, fn2, fn3), t: t): t => | Continuous(m) => Continuous(fn3(m)) } +let fmapResult = ((fn1, fn2, fn3), t: t): result => + switch t { + | Mixed(m) => fn1(m)->E.R2.fmap(x => PointSetTypes.Mixed(x)) + | Discrete(m) => fn2(m)->E.R2.fmap(x => PointSetTypes.Discrete(x)) + | Continuous(m) => fn3(m)->E.R2.fmap(x => PointSetTypes.Continuous(x)) + } + let toMixed = mapToAll(( m => m, d => @@ -130,13 +137,26 @@ module T = Dist({ let integralYtoX = f => mapToAll((Mixed.T.Integral.yToX(f), Discrete.T.Integral.yToX(f), Continuous.T.Integral.yToX(f))) let maxX = mapToAll((Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX)) - let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn) => + let mapY = (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn: float => float): ( + t => t + ) => fmap(( Mixed.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn), Discrete.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn), Continuous.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn), )) + let mapYResult = ( + ~integralSumCacheFn=_ => None, + ~integralCacheFn=_ => None, + ~fn: float => result, + ): (t => result) => + fmapResult(( + Mixed.T.mapYResult(~integralSumCacheFn, ~integralCacheFn, ~fn), + Discrete.T.mapYResult(~integralSumCacheFn, ~integralCacheFn, ~fn), + Continuous.T.mapYResult(~integralSumCacheFn, ~integralCacheFn, ~fn), + )) + let mean = (t: t): float => switch t { | Mixed(m) => Mixed.T.mean(m) @@ -195,8 +215,16 @@ let operate = (distToFloatOp: Operation.distToFloatOperation, s): float => | #Mean => T.mean(s) } -let toSparkline = (t: t, bucketCount) => +@genType +type sparklineError = CannotSparklineDiscrete + +let sparklineErrorToString = (err: sparklineError): string => + switch err { + | CannotSparklineDiscrete => "Cannot find the sparkline of a discrete distribution" + } + +let toSparkline = (t: t, bucketCount): result => T.toContinuous(t) ->E.O2.fmap(Continuous.downsampleEquallyOverX(bucketCount)) - ->E.O2.toResult("toContinous Error: Could not convert into continuous distribution") + ->E.O2.toResult(CannotSparklineDiscrete) ->E.R2.fmap(r => Continuous.getShape(r).ys->Sparklines.create()) diff --git a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist.res b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist.res index fcd4055d..b775c8ac 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist.res @@ -1,3 +1,11 @@ +@genType +type sampleSetError = TooFewSamples + +let sampleSetErrorToString = (err: sampleSetError): string => + switch err { + | TooFewSamples => "Too few samples when constructing sample set" + } + /* This is used as a smart constructor. The only way to create a SampleSetDist.t is to call this constructor. @@ -8,7 +16,7 @@ module T: { //When we get a good functional library in TS, we could refactor that out. @genType type t = array - let make: array => result + let make: array => result let get: t => array } = { type t = array @@ -16,7 +24,7 @@ module T: { if E.A.length(a) > 5 { Ok(a) } else { - Error("too small") + Error(TooFewSamples) } let get = (a: t) => a } @@ -25,19 +33,27 @@ include T let length = (t: t) => get(t)->E.A.length +@genType +type pointsetConversionError = TooFewSamplesForConversionToPointSet + +let pointsetConversionErrorToString = (err: pointsetConversionError) => + switch err { + | TooFewSamplesForConversionToPointSet => "Too Few Samples to convert to point set" + } + /* TODO: Refactor to get a more precise estimate. Also, this code is just fairly messy, could use some refactoring. */ let toPointSetDist = (~samples: t, ~samplingInputs: SamplingInputs.samplingInputs): result< PointSetTypes.pointSetDist, - string, + pointsetConversionError, > => SampleSetDist_ToPointSet.toPointSetDist( ~samples=get(samples), ~samplingInputs, (), - ).pointSetDist->E.O2.toResult("Failed to convert to PointSetDist") + ).pointSetDist->E.O2.toResult(TooFewSamplesForConversionToPointSet) //Randomly get one sample from the distribution let sample = (t: t): float => { @@ -62,7 +78,19 @@ let sampleN = (t: t, n) => { } //TODO: Figure out what to do if distributions are different lengths. ``zip`` is kind of inelegant for this. -let map2 = (~fn: (float, float) => float, ~t1: t, ~t2: t) => { +let map2 = ( + ~fn: (float, float) => result, + ~t1: t, + ~t2: t, +): result => { let samples = Belt.Array.zip(get(t1), get(t2))->E.A2.fmap(((a, b)) => fn(a, b)) - make(samples) + + // This assertion should never be reached. In order for it to be reached, one + // of the input parameters would need to be a sample set distribution with less + // than 6 samples. Which should be impossible due to the smart constructor. + // I could prove this to the type system (say, creating a {first: float, second: float, ..., fifth: float, rest: array} + // But doing so would take too much time, so I'll leave it as an assertion + E.A.R.firstErrorOrOpen(samples)->E.R2.fmap(x => + E.R.assertOk("Input of samples should be larger than 5", make(x)) + ) } diff --git a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res index 59b4fa46..3916e6fe 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res +++ b/packages/squiggle-lang/src/rescript/Distributions/SampleSetDist/SampleSetDist_ToPointSet.res @@ -83,7 +83,7 @@ let toPointSetDist = ( ~samples: Internals.T.t, ~samplingInputs: SamplingInputs.samplingInputs, (), -) => { +): Internals.Types.outputs => { Array.fast_sort(compare, samples) let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples) let length = samples |> E.A.length |> float_of_int diff --git a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res index a6eda12b..7ce95721 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res +++ b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDist.res @@ -379,7 +379,7 @@ module T = { ): analyticalSimplificationResult => switch (d1, d2) { | (#Float(v1), #Float(v2)) => - switch Operation.Algebraic.applyFn(op, v1, v2) { + switch Operation.Algebraic.toFn(op, v1, v2) { | Ok(r) => #AnalyticalSolution(#Float(r)) | Error(n) => #Error(n) } diff --git a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDistTypes.res b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDistTypes.res index 4625867d..121ff0b9 100644 --- a/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDistTypes.res +++ b/packages/squiggle-lang/src/rescript/Distributions/SymbolicDist/SymbolicDistTypes.res @@ -45,6 +45,6 @@ type symbolicDist = [ type analyticalSimplificationResult = [ | #AnalyticalSolution(symbolicDist) - | #Error(string) + | #Error(Operation.invalidOperationError) | #NoSolution ] diff --git a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res index 4ad60732..48fae58c 100644 --- a/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res +++ b/packages/squiggle-lang/src/rescript/Reducer/Reducer_ErrorValue.res @@ -9,6 +9,7 @@ type errorValue = | RERecordPropertyNotFound(string, string) | RESymbolNotFound(string) | RESyntaxError(string) + | REDistributionError(DistributionTypes.error) | RETodo(string) // To do type t = errorValue @@ -20,6 +21,7 @@ let errorToString = err => | REAssignmentExpected => "Assignment expected" | REExpressionExpected => "Expression expected" | REFunctionExpected(msg) => `Function expected: ${msg}` + | REDistributionError(err) => `Math Error: ${DistributionTypes.Error.toString(err)}` | REJavaScriptExn(omsg, oname) => { let answer = "JS Exception:" let answer = switch oname { diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res index 2a8dc2e1..0470543d 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_ExpressionValue.res @@ -10,7 +10,7 @@ type rec expressionValue = | EvArray(array) | EvBool(bool) | EvCall(string) // External function call - | EvDistribution(GenericDist_Types.genericDist) + | EvDistribution(DistributionTypes.genericDist) | EvNumber(float) | EvRecord(Js.Dict.t) | EvString(string) diff --git a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res index f21010e2..68ac7393 100644 --- a/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res +++ b/packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res @@ -29,8 +29,8 @@ module Helpers = { } let catchAndConvertTwoArgsToDists = (args: array): option<( - GenericDist_Types.genericDist, - GenericDist_Types.genericDist, + DistributionTypes.genericDist, + DistributionTypes.genericDist, )> => { switch args { | [EvDistribution(a), EvDistribution(b)] => Some((a, b)) @@ -41,33 +41,41 @@ module Helpers = { } let toFloatFn = ( - fnCall: GenericDist_Types.Operation.toFloat, - dist: GenericDist_Types.genericDist, + fnCall: DistributionTypes.Operation.toFloat, + dist: DistributionTypes.genericDist, ) => { - FromDist(GenericDist_Types.Operation.ToFloat(fnCall), dist)->runGenericOperation->Some + FromDist(DistributionTypes.DistributionOperation.ToFloat(fnCall), dist) + ->runGenericOperation + ->Some } let toStringFn = ( - fnCall: GenericDist_Types.Operation.toString, - dist: GenericDist_Types.genericDist, + fnCall: DistributionTypes.DistributionOperation.toString, + dist: DistributionTypes.genericDist, ) => { - FromDist(GenericDist_Types.Operation.ToString(fnCall), dist)->runGenericOperation->Some + FromDist(DistributionTypes.DistributionOperation.ToString(fnCall), dist) + ->runGenericOperation + ->Some } let toBoolFn = ( - fnCall: GenericDist_Types.Operation.toBool, - dist: GenericDist_Types.genericDist, + fnCall: DistributionTypes.DistributionOperation.toBool, + dist: DistributionTypes.genericDist, ) => { - FromDist(GenericDist_Types.Operation.ToBool(fnCall), dist)->runGenericOperation->Some + FromDist(DistributionTypes.DistributionOperation.ToBool(fnCall), dist) + ->runGenericOperation + ->Some } - let toDistFn = (fnCall: GenericDist_Types.Operation.toDist, dist) => { - FromDist(GenericDist_Types.Operation.ToDist(fnCall), dist)->runGenericOperation->Some + let toDistFn = (fnCall: DistributionTypes.DistributionOperation.toDist, dist) => { + FromDist(DistributionTypes.DistributionOperation.ToDist(fnCall), dist) + ->runGenericOperation + ->Some } let twoDiststoDistFn = (direction, arithmetic, dist1, dist2) => { FromDist( - GenericDist_Types.Operation.ToDistCombination( + DistributionTypes.DistributionOperation.ToDistCombination( direction, arithmeticMap(arithmetic), #Dist(dist2), @@ -84,7 +92,7 @@ module Helpers = { let parseNumberArray = (ags: array): Belt.Result.t, string> => E.A.fmap(parseNumber, ags) |> E.A.R.firstErrorOrOpen - let parseDist = (args: expressionValue): Belt.Result.t => + let parseDist = (args: expressionValue): Belt.Result.t => switch args { | EvDistribution(x) => Ok(x) | EvNumber(x) => Ok(GenericDist.fromFloat(x)) @@ -92,12 +100,12 @@ module Helpers = { } let parseDistributionArray = (ags: array): Belt.Result.t< - array, + array, string, > => E.A.fmap(parseDist, ags) |> E.A.R.firstErrorOrOpen let mixtureWithGivenWeights = ( - distributions: array, + distributions: array, weights: array, ): DistributionOperation.outputType => E.A.length(distributions) == E.A.length(weights) @@ -107,7 +115,7 @@ module Helpers = { ) let mixtureWithDefaultWeights = ( - distributions: array, + distributions: array, ): DistributionOperation.outputType => { let length = E.A.length(distributions) let weights = Belt.Array.make(length, 1.0 /. Belt.Int.toFloat(length)) @@ -259,12 +267,7 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result< | Float(d) => Ok(EvNumber(d)) | String(d) => Ok(EvString(d)) | Bool(d) => Ok(EvBool(d)) - | GenDistError(NotYetImplemented) => Error(RETodo("Function not yet implemented")) - | GenDistError(Unreachable) => Error(RETodo("Unreachable")) - | GenDistError(DistributionVerticalShiftIsInvalid) => - Error(RETodo("Distribution Vertical Shift Is Invalid")) - | GenDistError(ArgumentError(err)) => Error(RETodo("Argument Error: " ++ err)) - | GenDistError(Other(s)) => Error(RETodo(s)) + | GenDistError(err) => Error(REDistributionError(err)) } let dispatch = call => { diff --git a/packages/squiggle-lang/src/rescript/TypescriptInterface.res b/packages/squiggle-lang/src/rescript/TypescriptInterface.res index 6a0b7bcf..8704bf5e 100644 --- a/packages/squiggle-lang/src/rescript/TypescriptInterface.res +++ b/packages/squiggle-lang/src/rescript/TypescriptInterface.res @@ -53,4 +53,4 @@ type continuousShape = PointSetTypes.continuousShape let errorValueToString = Reducer_ErrorValue.errorToString @genType -let distributionErrorToString = GenericDist_Types.Error.toString +let distributionErrorToString = DistributionTypes.Error.toString diff --git a/packages/squiggle-lang/src/rescript/Utility/E.res b/packages/squiggle-lang/src/rescript/Utility/E.res index 8c6c4511..779ab03a 100644 --- a/packages/squiggle-lang/src/rescript/Utility/E.res +++ b/packages/squiggle-lang/src/rescript/Utility/E.res @@ -152,6 +152,8 @@ module I = { let toString = Js.Int.toString } +exception Assertion(string) + /* R for Result */ module R = { let result = Rationale.Result.result @@ -159,6 +161,11 @@ module R = { let fmap = Rationale.Result.fmap let bind = Rationale.Result.bind let toExn = Belt.Result.getExn + let assertOk = (message: string, x: result<'a, 'b>): 'a => + switch x { + | Ok(r) => r + | Error(_) => raise(Assertion(message)) + } let default = (default, res: Belt.Result.t<'a, 'b>) => switch res { | Ok(r) => r @@ -210,10 +217,10 @@ module R2 = { let bind = (a, b) => R.bind(b, a) //Converts result type to change error type only - let errMap = (a, map) => + let errMap = (a: result<'a, 'b>, map: 'b => 'c): result<'a, 'c> => switch a { | Ok(r) => Ok(r) - | Error(e) => map(e) + | Error(e) => Error(map(e)) } let fmap2 = (xR, f) => @@ -436,6 +443,7 @@ module A = { r |> Belt.Array.map(_, r => Belt.Result.getExn(r)) bringErrorUp |> Belt.Result.map(_, forceOpen) } + let filterOk = (x: array>): array<'a> => fmap(R.toOption, x)->O.concatSomes } module Sorted = { diff --git a/packages/squiggle-lang/src/rescript/Utility/Operation.res b/packages/squiggle-lang/src/rescript/Utility/Operation.res index f78a432c..587f2e31 100644 --- a/packages/squiggle-lang/src/rescript/Utility/Operation.res +++ b/packages/squiggle-lang/src/rescript/Utility/Operation.res @@ -37,22 +37,42 @@ module Convolution = { } } +@genType +type invalidOperationError = + | DivisionByZeroError + | ComplexNumberError + +let invalidOperationErrorToString = (err: invalidOperationError): string => + switch err { + | DivisionByZeroError => "Cannot divide by zero" + | ComplexNumberError => "Operation returned complex result" + } + module Algebraic = { type t = algebraicOperation - let toFn: (t, float, float) => float = x => + let toFn: (t, float, float) => result = (x, a, b) => switch x { - | #Add => \"+." - | #Subtract => \"-." - | #Multiply => \"*." - | #Power => \"**" - | #Divide => \"/." - | #Logarithm => (a, b) => log(a) /. log(b) - } - - let applyFn = (t, f1, f2) => - switch (t, f1, f2) { - | (#Divide, _, 0.) => Error("Cannot divide $v1 by zero.") - | _ => Ok(toFn(t, f1, f2)) + | #Add => Ok(a +. b) + | #Subtract => Ok(a -. b) + | #Multiply => Ok(a *. b) + | #Power => + if a > 0.0 { + Ok(a ** b) + } else { + Error(ComplexNumberError) + } + | #Divide => + if b != 0.0 { + Ok(a /. b) + } else { + Error(DivisionByZeroError) + } + | #Logarithm => + if a > 0.0 && b > 0.0 { + Ok(log(a) /. log(b)) + } else { + Error(ComplexNumberError) + } } let toString = x => @@ -96,12 +116,27 @@ module DistToFloat = { // Note that different logarithms don't really do anything. module Scale = { type t = scaleOperation - let toFn = x => + let toFn = (x: t, a: float, b: float): result => switch x { - | #Multiply => \"*." - | #Divide => \"/." - | #Power => \"**" - | #Logarithm => (a, b) => log(a) /. log(b) + | #Multiply => Ok(a *. b) + | #Divide => + if b != 0.0 { + Ok(a /. b) + } else { + Error(DivisionByZeroError) + } + | #Power => + if a > 0.0 { + Ok(a ** b) + } else { + Error(ComplexNumberError) + } + | #Logarithm => + if a > 0.0 && b > 0.0 { + Ok(log(a) /. log(b)) + } else { + Error(DivisionByZeroError) + } } let format = (operation: t, value, scaleBy) => diff --git a/packages/squiggle-lang/src/rescript/Utility/XYShape.res b/packages/squiggle-lang/src/rescript/Utility/XYShape.res index 020acd42..d792f70e 100644 --- a/packages/squiggle-lang/src/rescript/Utility/XYShape.res +++ b/packages/squiggle-lang/src/rescript/Utility/XYShape.res @@ -43,6 +43,10 @@ module T = { let xTotalRange = (t: t) => maxX(t) -. minX(t) let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys} let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)} + let mapYResult = (fn: float => result, t: t): result => { + let mappedYs = E.A.fmap(fn, t.ys) + E.A.R.firstErrorOrOpen(mappedYs)->E.R2.fmap(y => {xs: t.xs, ys: y}) + } let square = mapX(x => x ** 2.0) let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys) let fromArray = ((xs, ys)): t => {xs: xs, ys: ys}