diff --git a/showcase/entries/Continuous.re b/showcase/entries/Continuous.re index 2d0d2e4d..5ff0567b 100644 --- a/showcase/entries/Continuous.re +++ b/showcase/entries/Continuous.re @@ -15,12 +15,8 @@ let timeDist = ); let setup = dist => - dist - |> DistPlusIngredients.toDistPlus( - ~sampleCount=2000, - ~outputXYPoints=1000, - ~truncateTo=Some(1000), - ) + RenderTypes.DistPlus.make(~distPlusIngredients=dist,()) + |> DistPlusIngredients.toDistPlus |> E.O.React.fmapOrNull(distPlus => ); let simpleExample = (name, guesstimatorString) => diff --git a/src/Samples.re b/src/Samples.re index 360f8279..093335dc 100644 --- a/src/Samples.re +++ b/src/Samples.re @@ -22,6 +22,7 @@ module KDE = { |> JS.jsToDist; }; + // Note: This was an experiment, but it didn't actually work that well. let inGroups = (samples, outputXYPoints, kernelWidth, ~cuttoff=0.9, ()) => { let partitionAt = samples @@ -88,33 +89,94 @@ module T = { (continuous, discrete); }; - let kde = (~samples, ~outputXYPoints) => { - let width = Bandwidth.nrd0(samples); + let xWidthToUnitWidth = (samples, outputXYPoints, xWidth) => { let xyPointRange = E.A.Sorted.range(samples) |> E.O.default(0.0); let xyPointWidth = xyPointRange /. float_of_int(outputXYPoints); - let kernelWidth = int_of_float(Jstat.max([|(width /. xyPointWidth), 1.0 |])); - KDE.normalSampling(samples, outputXYPoints, kernelWidth); + xWidth /. xyPointWidth; + }; + + let formatUnitWidth = w => Jstat.max([|w, 1.0|]) |> int_of_float; + + let suggestedUnitWidth = (samples, outputXYPoints) => { + let suggestedXWidth = Bandwidth.nrd0(samples); + xWidthToUnitWidth(samples, outputXYPoints, suggestedXWidth); + }; + + let kde = (~samples, ~outputXYPoints, width) => { + KDE.normalSampling(samples, outputXYPoints, width); }; // todo: Figure out some way of doing this without having to integrate so many times. - let toShape = (~samples: t, ~outputXYPoints=3000, ~kernelWidth=10, ()) => { + let toShape = + (~samples: t, ~samplingInputs: RenderTypes.Sampling.Inputs.fInputs, ()) => { Array.fast_sort(compare, samples); let (continuousPart, discretePart) = E.A.Sorted.Floats.split(samples); - let length = samples |> E.A.length; - let lengthFloat = float_of_int(length); + let length = samples |> E.A.length |> float_of_int; let discrete: DistTypes.xyShape = discretePart - |> E.FloatFloatMap.fmap(r => r /. lengthFloat) + |> E.FloatFloatMap.fmap(r => r /. length) |> E.FloatFloatMap.toArray |> XYShape.T.fromZippedArray; - let pdf: DistTypes.xyShape = + + let pdf = continuousPart |> E.A.length > 5 ? { - continuousPart |> kde(~samples=_, ~outputXYPoints) + let _suggestedXWidth = Bandwidth.nrd0(continuousPart); + let _suggestedUnitWidth = + suggestedUnitWidth(continuousPart, samplingInputs.outputXYPoints); + let usedWidth = + samplingInputs.kernelWidth |> E.O.default(_suggestedXWidth); + let usedUnitWidth = + xWidthToUnitWidth( + samples, + samplingInputs.outputXYPoints, + usedWidth, + ); + let foo: RenderTypes.Sampling.samplingStats = { + sampleCount: samplingInputs.sampleCount, + outputXYPoints: samplingInputs.outputXYPoints, + bandwidthXSuggested: _suggestedXWidth, + bandwidthUnitSuggested: _suggestedUnitWidth, + bandwidthXImplemented: usedWidth, + bandwidthUnitImplemented: usedUnitWidth, + }; + continuousPart + |> kde( + ~samples=_, + ~outputXYPoints=samplingInputs.outputXYPoints, + formatUnitWidth(usedUnitWidth), + ) + |> Distributions.Continuous.make(`Linear) + |> (r => Some((r, foo))); } - : {xs: [||], ys: [||]}; - let continuous = pdf |> Distributions.Continuous.make(`Linear); - let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete); - shape; + : None; + let shape = + MixedShapeBuilder.buildSimple( + ~continuous=pdf |> E.O.fmap(fst), + ~discrete, + ); + let samplesParse: RenderTypes.Sampling.outputs = { + continuousParseParams: pdf |> E.O.fmap(snd), + shape, + }; + samplesParse; + }; + + let fromGuesstimatorString = + ( + ~guesstimatorString, + ~samplingInputs=RenderTypes.Sampling.Inputs.empty, + (), + ) => { + let hasValidSamples = + Guesstimator.stringToSamples(guesstimatorString, 10) |> E.A.length > 0; + let samplingInputs = RenderTypes.Sampling.Inputs.toF(samplingInputs); + switch (hasValidSamples) { + | false => None + | true => + let samples = + Guesstimator.stringToSamples(guesstimatorString, samplingInputs.sampleCount); + Some(toShape(~samples, ~samplingInputs, ())); + }; }; }; \ No newline at end of file diff --git a/src/components/DistBuilder.re b/src/components/DistBuilder.re index bfcbcb51..0ab50e95 100644 --- a/src/components/DistBuilder.re +++ b/src/components/DistBuilder.re @@ -26,7 +26,7 @@ type options = { sampleCount: int, outputXYPoints: int, truncateTo: option(int), - kernelWidth: int, + kernelWidth: option(float), }; module Form = ReForm.Make(FormConfig); @@ -111,6 +111,13 @@ module Styles = { ]); }; +type inputs = { + samplingInputs: RenderTypes.Sampling.inputs, + guesstimatorString: string, + length: int, + shouldTruncateSampledDistribution: int, +}; + module DemoDist = { [@react.component] let make = (~guesstimatorString, ~domain, ~unit, ~options) => { @@ -119,14 +126,24 @@ module DemoDist = {
{switch (domain, unit, options) { | (Some(domain), Some(unit), Some(options)) => - let distPlus = - DistPlusIngredients.make(~guesstimatorString, ~domain, ~unit, ()) - |> DistPlusIngredients.toDistPlus( - ~sampleCount=options.sampleCount, - ~outputXYPoints=options.outputXYPoints, - ~truncateTo=options.truncateTo, - ~kernelWidth=options.kernelWidth, - ); + let distPlusIngredients = + DistPlusIngredients.make( + ~guesstimatorString, + ~domain, + ~unit, + (), + ); + let inputs = + RenderTypes.DistPlus.make( + ~samplingInputs={ + sampleCount: Some(options.sampleCount), + outputXYPoints: Some(options.outputXYPoints), + kernelWidth: options.kernelWidth, + }, + ~distPlusIngredients, + (), + ); + let distPlus = DistPlusIngredients.toDistPlus(inputs); switch (distPlus) { | Some(distPlus) => | _ => @@ -160,9 +177,9 @@ let make = () => { unitType: "UnspecifiedDistribution", zero: MomentRe.momentNow(), unit: "days", - sampleCount: "10000", - outputXYPoints: "500", - truncateTo: "100", + sampleCount: "30000", + outputXYPoints: "10000", + truncateTo: "1000", kernelWidth: "5", }, (), @@ -246,7 +263,7 @@ let make = () => { truncateTo: int_of_float(truncateTo) > 0 ? Some(int_of_float(truncateTo)) : None, - kernelWidth: kernelWidth |> int_of_float, + kernelWidth: kernelWidth == 0.0 ? None : Some(kernelWidth), }) | _ => None }; diff --git a/src/distribution/DistPlusIngredients.re b/src/distribution/DistPlusIngredients.re index 36a7e663..098128a5 100644 --- a/src/distribution/DistPlusIngredients.re +++ b/src/distribution/DistPlusIngredients.re @@ -15,41 +15,34 @@ let applyTruncation = (truncateTo, distPlus) => | _ => None }; + // ~samplingInputs=RenderTypes.Sampling.Inputs.empty, + // ~truncateTo: option(int), + // t: distPlusIngredients, +//Make truncation optional let toDistPlus = ( - ~sampleCount=2000, - ~outputXYPoints=1500, - ~truncateTo=Some(300), - ~kernelWidth=5, - t: distPlusIngredients, + inputs:RenderTypes.DistPlus.inputs ) : option(distPlus) => { let toDist = shape => Distributions.DistPlus.make( ~shape, - ~domain=t.domain, - ~unit=t.unit, - ~guesstimatorString=Some(t.guesstimatorString), + ~domain=inputs.distPlusIngredients.domain, + ~unit=inputs.distPlusIngredients.unit, + ~guesstimatorString=Some(inputs.distPlusIngredients.guesstimatorString), (), ) |> Distributions.DistPlus.T.scaleToIntegralSum(~intendedSum=1.0); - let parsed1 = MathJsParser.fromString(t.guesstimatorString); let shape = - switch (parsed1) { - | Ok(r) => - let shape = SymbolicDist.toShape(truncateTo |> E.O.default(10000), r); - Some(shape |> toDist); - | _ => - let fewSamples = Guesstimator.stringToSamples(t.guesstimatorString, 10); - if (fewSamples |> E.A.length > 0) { - let samples = - Guesstimator.stringToSamples(t.guesstimatorString, sampleCount); - let shape = - Samples.T.toShape(~samples, ~outputXYPoints, ~kernelWidth, ()); - shape |> E.O.fmap(toDist) |> applyTruncation(truncateTo); - } else { - None; - }; - }; - shape; + GuesstimatorToShape.run( + ~renderingInputs={ + guesstimatorString: inputs.distPlusIngredients.guesstimatorString, + shapeLength: inputs.recommendedLength, + }, + ~samplingInputs=inputs.samplingInputs, + ) + |> GuesstimatorToShape.getShape; + //TODO: Apply truncation + let foo = shape |> E.O.fmap(toDist); + foo; }; \ No newline at end of file diff --git a/src/distribution/GuesstimatorToShape.re b/src/distribution/GuesstimatorToShape.re new file mode 100644 index 00000000..360fb787 --- /dev/null +++ b/src/distribution/GuesstimatorToShape.re @@ -0,0 +1,32 @@ +let runSymbolic = + (renderingInputs: RenderTypes.primaryInputs) =>{ + let graph = MathJsParser.fromString(renderingInputs.guesstimatorString); + graph |> E.R.fmap(g => RenderTypes.Symbolic.make(g, SymbolicDist.toShape(renderingInputs.shapeLength,g))) + } + +let getShape = (r: RenderTypes.Combined.outputs) => + switch (r.symbolic, r.sampling) { + | (Some(Ok({shape})), _) => Some(shape) + | (_, Some({shape})) => shape + | _ => None + }; + +let run = + ( + ~renderingInputs: RenderTypes.primaryInputs, + ~samplingInputs: RenderTypes.Sampling.inputs, + ) + : RenderTypes.Combined.outputs => { + let symbolic = runSymbolic(renderingInputs); + let sampling = + switch (symbolic) { + | Ok(r) => None + | Error(r) => + Samples.T.fromGuesstimatorString( + ~guesstimatorString=renderingInputs.guesstimatorString, + ~samplingInputs, + (), + ) + }; + {symbolic: Some(symbolic), sampling}; +}; \ No newline at end of file diff --git a/src/distribution/MixedShapeBuilder.re b/src/distribution/MixedShapeBuilder.re index cde9251c..c8195568 100644 --- a/src/distribution/MixedShapeBuilder.re +++ b/src/distribution/MixedShapeBuilder.re @@ -8,7 +8,8 @@ type assumptions = { discreteProbabilityMass: option(float), }; -let buildSimple = (~continuous, ~discrete): option(DistTypes.shape) => { +let buildSimple = (~continuous: option(DistTypes.continuousShape), ~discrete): option(DistTypes.shape) => { + let continuous = continuous |> E.O.default(Distributions.Continuous.make(`Linear, {xs: [||], ys: [||]})) let cLength = continuous |> Distributions.Continuous.getShape diff --git a/src/distribution/RenderTypes.re b/src/distribution/RenderTypes.re new file mode 100644 index 00000000..8f4b9882 --- /dev/null +++ b/src/distribution/RenderTypes.re @@ -0,0 +1,86 @@ +type primaryInputs = { + guesstimatorString: string, + shapeLength: int, +}; + +module Sampling = { + type inputs = { + sampleCount: option(int), + outputXYPoints: option(int), + kernelWidth: option(float), + }; + type samplingStats = { + sampleCount: int, + outputXYPoints: int, + bandwidthXSuggested: float, + bandwidthUnitSuggested: float, + bandwidthXImplemented: float, + bandwidthUnitImplemented: float, + }; + type outputs = { + continuousParseParams: option(samplingStats), + shape: option(ProbExample.DistTypes.shape), + }; + module Inputs = { + let defaultSampleCount = 5000; + let defaultOutputXYPoints = 10000; + let empty = {sampleCount: None, outputXYPoints: None, kernelWidth: None}; + + type fInputs = { + sampleCount: int, + outputXYPoints: int, + kernelWidth: option(float), + }; + let toF = (i: inputs): fInputs => { + sampleCount: i.sampleCount |> E.O.default(defaultSampleCount), + outputXYPoints: i.outputXYPoints |> E.O.default(defaultOutputXYPoints), + kernelWidth: i.kernelWidth, + }; + }; +}; + +module Symbolic = { + type inputs = {length: int}; + type outputs = { + graph: ProbExample.SymbolicDist.bigDist, + shape: ProbExample.DistTypes.shape, + }; + let make = (graph, shape) => {graph, shape}; +}; + +module Combined = { + type inputs = { + samplingInputs: Sampling.inputs, + symbolicInputs: Symbolic.inputs, + guesstimatorString: string, + }; + type outputs = { + symbolic: option(Belt.Result.t(Symbolic.outputs, string)), + sampling: option(Sampling.outputs), + }; +}; + +module DistPlus = { + let defaultRecommendedLength = 10000; + let defaultShouldTruncate = true; + type inputs = { + distPlusIngredients: DistTypes.distPlusIngredients, + samplingInputs: Sampling.inputs, + recommendedLength: int, + shouldTruncate: bool, + }; + let make = + ( + ~samplingInputs=Sampling.Inputs.empty, + ~recommendedLength=defaultRecommendedLength, + ~shouldTruncate=defaultShouldTruncate, + ~distPlusIngredients, + () + ) + : inputs => { + distPlusIngredients, + samplingInputs, + recommendedLength, + shouldTruncate, + }; +}; \ No newline at end of file diff --git a/src/interface/FormBuilder.re b/src/interface/FormBuilder.re index 94e7b4cb..7280155b 100644 --- a/src/interface/FormBuilder.re +++ b/src/interface/FormBuilder.re @@ -19,12 +19,8 @@ let propValue = (t: Prop.Value.t) => { | ConditionalArray(r) => "Array" |> ReasonReact.string | DistPlusIngredients((r: DistTypes.distPlusIngredients)) => let newDistribution = - DistPlusIngredients.toDistPlus( - ~sampleCount=1000, - ~outputXYPoints=2000, - ~truncateTo=Some(500), - r, - ); + RenderTypes.DistPlus.make(~distPlusIngredients=r, ~recommendedLength=1000, ~shouldTruncate=true,()) + |> DistPlusIngredients.toDistPlus switch (newDistribution) { | Some(distribution) =>
diff --git a/src/models/EAFunds.re b/src/models/EAFunds.re index aeacb1b2..bd42d96e 100644 --- a/src/models/EAFunds.re +++ b/src/models/EAFunds.re @@ -110,6 +110,7 @@ module Model = { // TODO: Fixe number that integral is calculated for let getGlobalCatastropheChance = dateTime => { GlobalCatastrophe.makeI(MomentRe.momentNow()) + |> RenderTypes.DistPlus.make(~distPlusIngredients=_, ()) |> DistPlusIngredients.toDistPlus |> E.O.bind(_, Distributions.DistPlusTime.Integral.xToY(Time(dateTime))); }; diff --git a/src/symbolic/MathJsParser.re b/src/symbolic/MathJsParser.re index 4f6225ff..c5bae5bd 100644 --- a/src/symbolic/MathJsParser.re +++ b/src/symbolic/MathJsParser.re @@ -245,6 +245,5 @@ let fromString = str => { } ); let value = E.R.bind(mathJsParse, MathAdtToDistDst.run); - Js.log4("fromString", mathJsToJson, mathJsParse, value); value; }; \ No newline at end of file diff --git a/src/symbolic/SymbolicDist.re b/src/symbolic/SymbolicDist.re index a4c33baf..cec8e1d6 100644 --- a/src/symbolic/SymbolicDist.re +++ b/src/symbolic/SymbolicDist.re @@ -296,7 +296,7 @@ module PointwiseAddDistributionsWeighted = { let normalized = normalizeWeights(dists); let continuous = normalized |> E.A.filter(((r,_)) => GenericSimple.contType(r) == `Continuous) |> continuousShape(_, sampleCount); let discrete = normalized |> E.A.filter(((r,_)) => GenericSimple.contType(r) == `Discrete) |> discreteShape(_, sampleCount); - let shape = MixedShapeBuilder.buildSimple(~continuous, ~discrete); + let shape = MixedShapeBuilder.buildSimple(~continuous=Some(continuous), ~discrete); shape |> E.O.toExt("") };