Merge pull request #70 from foretold-app/adding-victory
Adding a simple chart for functions
This commit is contained in:
		
						commit
						1421c6a9b1
					
				
							
								
								
									
										57
									
								
								__tests__/Hardcoded__Test.re
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								__tests__/Hardcoded__Test.re
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
open Jest;
 | 
			
		||||
open Expect;
 | 
			
		||||
 | 
			
		||||
let makeTest = (~only=false, str, item1, item2) =>
 | 
			
		||||
  only
 | 
			
		||||
    ? Only.test(str, () =>
 | 
			
		||||
        expect(item1) |> toEqual(item2)
 | 
			
		||||
      )
 | 
			
		||||
    : test(str, () =>
 | 
			
		||||
        expect(item1) |> toEqual(item2)
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
let evalParams: ExpressionTypes.ExpressionTree.evaluationParams = {
 | 
			
		||||
  samplingInputs: {
 | 
			
		||||
    sampleCount: 1000,
 | 
			
		||||
    outputXYPoints: 10000,
 | 
			
		||||
    kernelWidth: None,
 | 
			
		||||
    shapeLength: 1000,
 | 
			
		||||
  },
 | 
			
		||||
  environment:
 | 
			
		||||
    [|
 | 
			
		||||
      ("K", `SymbolicDist(`Float(1000.0))),
 | 
			
		||||
      ("M", `SymbolicDist(`Float(1000000.0))),
 | 
			
		||||
      ("B", `SymbolicDist(`Float(1000000000.0))),
 | 
			
		||||
      ("T", `SymbolicDist(`Float(1000000000000.0))),
 | 
			
		||||
    |]
 | 
			
		||||
    ->Belt.Map.String.fromArray,
 | 
			
		||||
  evaluateNode: ExpressionTreeEvaluator.toLeaf,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let shape1: DistTypes.xyShape = {xs: [|1., 4., 8.|], ys: [|0.2, 0.4, 0.8|]};
 | 
			
		||||
 | 
			
		||||
describe("XYShapes", () => {
 | 
			
		||||
  describe("logScorePoint", () => {
 | 
			
		||||
    makeTest(
 | 
			
		||||
      "When identical",
 | 
			
		||||
      {
 | 
			
		||||
        let foo =
 | 
			
		||||
          HardcodedFunctions.(
 | 
			
		||||
            makeRenderedDistFloat("scaleMultiply", (dist, float) =>
 | 
			
		||||
              verticalScaling(`Multiply, dist, float)
 | 
			
		||||
            )
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        TypeSystem.Function.T.run(
 | 
			
		||||
          evalParams,
 | 
			
		||||
          [|
 | 
			
		||||
            `SymbolicDist(`Float(100.0)),
 | 
			
		||||
            `SymbolicDist(`Float(1.0)),
 | 
			
		||||
          |],
 | 
			
		||||
          foo,
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      Error("Sad"),
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -42,9 +42,7 @@
 | 
			
		|||
    "bs-css",
 | 
			
		||||
    "rationale",
 | 
			
		||||
    "bs-moment",
 | 
			
		||||
    "reschema",
 | 
			
		||||
    "bs-webapi",
 | 
			
		||||
    "bs-fetch"
 | 
			
		||||
    "reschema"
 | 
			
		||||
  ],
 | 
			
		||||
  "refmt": 3,
 | 
			
		||||
  "ppx-flags": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -28,16 +28,15 @@
 | 
			
		|||
  "dependencies": {
 | 
			
		||||
    "@foretold/components": "0.0.6",
 | 
			
		||||
    "@glennsl/bs-json": "^5.0.2",
 | 
			
		||||
    "ace-builds": "^1.4.12",
 | 
			
		||||
    "antd": "3.17.0",
 | 
			
		||||
    "autoprefixer": "9.7.4",
 | 
			
		||||
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
 | 
			
		||||
    "binary-search-tree": "0.2.6",
 | 
			
		||||
    "bs-ant-design-alt": "2.0.0-alpha.33",
 | 
			
		||||
    "bs-css": "11.0.0",
 | 
			
		||||
    "bs-fetch": "^0.5.2",
 | 
			
		||||
    "bs-moment": "0.4.5",
 | 
			
		||||
    "bs-reform": "9.7.1",
 | 
			
		||||
    "bs-webapi": "^0.15.9",
 | 
			
		||||
    "bsb-js": "1.1.7",
 | 
			
		||||
    "d3": "5.15.0",
 | 
			
		||||
    "gh-pages": "2.2.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -52,12 +51,17 @@
 | 
			
		|||
    "pdfast": "^0.2.0",
 | 
			
		||||
    "postcss-cli": "7.1.0",
 | 
			
		||||
    "rationale": "0.2.0",
 | 
			
		||||
    "react": "^16.8.0",
 | 
			
		||||
    "react-dom": "^16.8.0",
 | 
			
		||||
    "react": "^16.10.0",
 | 
			
		||||
    "react-ace": "^9.2.0",
 | 
			
		||||
    "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0",
 | 
			
		||||
    "react-use": "^13.27.0",
 | 
			
		||||
    "react-vega": "^7.4.1",
 | 
			
		||||
    "reason-react": ">=0.7.0",
 | 
			
		||||
    "reschema": "1.3.0",
 | 
			
		||||
    "tailwindcss": "1.2.0"
 | 
			
		||||
    "tailwindcss": "1.2.0",
 | 
			
		||||
    "vega": "*",
 | 
			
		||||
    "vega-embed": "6.6.0",
 | 
			
		||||
    "vega-lite": "*"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@glennsl/bs-jest": "^0.5.1",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
let entries = EntryTypes.[Continuous2.entry,ExpressionTreeExamples.entry];
 | 
			
		||||
let entries = EntryTypes.[ExpressionTreeExamples.entry];
 | 
			
		||||
| 
						 | 
				
			
			@ -5,84 +5,84 @@
 | 
			
		|||
// floor(3 to 4)
 | 
			
		||||
// uniform(0,1) > 0.3 ? lognormal(6.652, -0.41): 0
 | 
			
		||||
 | 
			
		||||
let timeDist ={
 | 
			
		||||
  let ingredients = DistPlusRenderer.Inputs.Ingredients.make(
 | 
			
		||||
    ~guesstimatorString="(floor(10 to 15))",
 | 
			
		||||
    ~domain=RightLimited({xPoint: 50.0, excludingProbabilityMass: 0.3}),
 | 
			
		||||
    ~unit=
 | 
			
		||||
      DistTypes.TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
 | 
			
		||||
    ());
 | 
			
		||||
  let inputs = DistPlusRenderer.Inputs.make(~distPlusIngredients=ingredients,())
 | 
			
		||||
  inputs |> DistPlusRenderer.run
 | 
			
		||||
}
 | 
			
		||||
// let timeDist ={
 | 
			
		||||
//   let ingredients = DistPlusRenderer.Inputs.Ingredients.make(
 | 
			
		||||
//     ~guesstimatorString="(floor(10 to 15))",
 | 
			
		||||
//     ~domain=RightLimited({xPoint: 50.0, excludingProbabilityMass: 0.3}),
 | 
			
		||||
//     ~unit=
 | 
			
		||||
//       DistTypes.TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
 | 
			
		||||
//     ());
 | 
			
		||||
//   let inputs = DistPlusRenderer.Inputs.make(~distPlusIngredients=ingredients,())
 | 
			
		||||
//   inputs |> DistPlusRenderer.run
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
let setup = dist =>
 | 
			
		||||
  DistPlusRenderer.Inputs.make(~distPlusIngredients=dist,())
 | 
			
		||||
  |> DistPlusRenderer.run
 | 
			
		||||
  |> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
 | 
			
		||||
  |> E.R.toOption
 | 
			
		||||
  |> E.O.toExn("")
 | 
			
		||||
// let setup = dist =>
 | 
			
		||||
//   DistPlusRenderer.Inputs.make(~distPlusIngredients=dist,())
 | 
			
		||||
//   |> DistPlusRenderer.run
 | 
			
		||||
//   |> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
 | 
			
		||||
//   |> E.R.toOption
 | 
			
		||||
//   |> E.O.toExn("")
 | 
			
		||||
 | 
			
		||||
let simpleExample = (name, guesstimatorString) =>
 | 
			
		||||
  <>
 | 
			
		||||
    <h3 className="text-gray-600 text-lg font-bold">
 | 
			
		||||
      {name |> ReasonReact.string}
 | 
			
		||||
    </h3>
 | 
			
		||||
    {setup(DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()))}
 | 
			
		||||
  </>;
 | 
			
		||||
// let simpleExample = (name, guesstimatorString) =>
 | 
			
		||||
//   <>
 | 
			
		||||
//     <h3 className="text-gray-600 text-lg font-bold">
 | 
			
		||||
//       {name |> ReasonReact.string}
 | 
			
		||||
//     </h3>
 | 
			
		||||
//     {setup(DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()))}
 | 
			
		||||
//   </>;
 | 
			
		||||
 | 
			
		||||
let timeExample = (name, guesstimatorString) =>
 | 
			
		||||
  <>
 | 
			
		||||
    <h3 className="text-gray-600 text-lg font-bold">
 | 
			
		||||
      {name |> ReasonReact.string}
 | 
			
		||||
    </h3>
 | 
			
		||||
    {setup(
 | 
			
		||||
       DistPlusRenderer.Inputs.Ingredients.make(
 | 
			
		||||
         ~guesstimatorString,
 | 
			
		||||
         ~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
 | 
			
		||||
         (),
 | 
			
		||||
       ),
 | 
			
		||||
     )}
 | 
			
		||||
  </>;
 | 
			
		||||
// let timeExample = (name, guesstimatorString) =>
 | 
			
		||||
//   <>
 | 
			
		||||
//     <h3 className="text-gray-600 text-lg font-bold">
 | 
			
		||||
//       {name |> ReasonReact.string}
 | 
			
		||||
//     </h3>
 | 
			
		||||
//     {setup(
 | 
			
		||||
//        DistPlusRenderer.Inputs.Ingredients.make(
 | 
			
		||||
//          ~guesstimatorString,
 | 
			
		||||
//          ~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
 | 
			
		||||
//          (),
 | 
			
		||||
//        ),
 | 
			
		||||
//      )}
 | 
			
		||||
//   </>;
 | 
			
		||||
 | 
			
		||||
let distributions = () =>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div>
 | 
			
		||||
      <h2 className="text-gray-800 text-xl font-bold">
 | 
			
		||||
        {"Initial Section" |> ReasonReact.string}
 | 
			
		||||
      </h2>
 | 
			
		||||
        {simpleExample("Continuous", "5 to 20")}
 | 
			
		||||
        {simpleExample("Continuous, wide range", "1 to 1000000")}
 | 
			
		||||
        {simpleExample("Continuous, tiny values", "0.000000001 to 0.00000001")}
 | 
			
		||||
        {simpleExample(
 | 
			
		||||
           "Continuous large values",
 | 
			
		||||
           "50000000000000 to 200000000000000000",
 | 
			
		||||
         )}
 | 
			
		||||
        {simpleExample("Discrete", "floor(10 to 20)")}
 | 
			
		||||
        {simpleExample(
 | 
			
		||||
           "Discrete and below 0, normal(10,30)",
 | 
			
		||||
           "floor(normal(10,30))",
 | 
			
		||||
         )}
 | 
			
		||||
        {simpleExample("Discrete, wide range", "floor(10 to 200000)")}
 | 
			
		||||
        {simpleExample("Mixed", "mm(5 to 20, floor(20 to 30), [.5,.5])")}
 | 
			
		||||
        {simpleExample("Mixed, Early-Discrete Point", "mm(1, 5 to 20, [.5,.5])")}
 | 
			
		||||
        {simpleExample(
 | 
			
		||||
           "Mixed, Two-Discrete Points",
 | 
			
		||||
           "mm(0,10, 5 to 20, [.5,.5,.5])",
 | 
			
		||||
         )}
 | 
			
		||||
        <h2 className="text-gray-800 text-xl font-bold">
 | 
			
		||||
          {"Over Time" |> ReasonReact.string}
 | 
			
		||||
        </h2>
 | 
			
		||||
        {timeExample("Continuous", "5 to 20")}
 | 
			
		||||
        {timeExample("Continuous Over Long Period", "500 to 200000")}
 | 
			
		||||
        {timeExample("Continuous Over Short Period", "0.0001 to 0.001")}
 | 
			
		||||
        {timeExample(
 | 
			
		||||
           "Continuous Over Very Long Period",
 | 
			
		||||
           "500 to 20000000000000",
 | 
			
		||||
         )}
 | 
			
		||||
        {timeExample("Discrete", "floor(5 to 20)")}
 | 
			
		||||
        {timeExample("Mixed", "mm(5 to 20, floor(5 to 20), [.5,.5])")}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>;
 | 
			
		||||
// let distributions = () =>
 | 
			
		||||
//   <div>
 | 
			
		||||
//     <div>
 | 
			
		||||
//       <h2 className="text-gray-800 text-xl font-bold">
 | 
			
		||||
//         {"Initial Section" |> ReasonReact.string}
 | 
			
		||||
//       </h2>
 | 
			
		||||
//         {simpleExample("Continuous", "5 to 20")}
 | 
			
		||||
//         {simpleExample("Continuous, wide range", "1 to 1000000")}
 | 
			
		||||
//         {simpleExample("Continuous, tiny values", "0.000000001 to 0.00000001")}
 | 
			
		||||
//         {simpleExample(
 | 
			
		||||
//            "Continuous large values",
 | 
			
		||||
//            "50000000000000 to 200000000000000000",
 | 
			
		||||
//          )}
 | 
			
		||||
//         {simpleExample("Discrete", "floor(10 to 20)")}
 | 
			
		||||
//         {simpleExample(
 | 
			
		||||
//            "Discrete and below 0, normal(10,30)",
 | 
			
		||||
//            "floor(normal(10,30))",
 | 
			
		||||
//          )}
 | 
			
		||||
//         {simpleExample("Discrete, wide range", "floor(10 to 200000)")}
 | 
			
		||||
//         {simpleExample("Mixed", "mm(5 to 20, floor(20 to 30), [.5,.5])")}
 | 
			
		||||
//         {simpleExample("Mixed, Early-Discrete Point", "mm(1, 5 to 20, [.5,.5])")}
 | 
			
		||||
//         {simpleExample(
 | 
			
		||||
//            "Mixed, Two-Discrete Points",
 | 
			
		||||
//            "mm(0,10, 5 to 20, [.5,.5,.5])",
 | 
			
		||||
//          )}
 | 
			
		||||
//         <h2 className="text-gray-800 text-xl font-bold">
 | 
			
		||||
//           {"Over Time" |> ReasonReact.string}
 | 
			
		||||
//         </h2>
 | 
			
		||||
//         {timeExample("Continuous", "5 to 20")}
 | 
			
		||||
//         {timeExample("Continuous Over Long Period", "500 to 200000")}
 | 
			
		||||
//         {timeExample("Continuous Over Short Period", "0.0001 to 0.001")}
 | 
			
		||||
//         {timeExample(
 | 
			
		||||
//            "Continuous Over Very Long Period",
 | 
			
		||||
//            "500 to 20000000000000",
 | 
			
		||||
//          )}
 | 
			
		||||
//         {timeExample("Discrete", "floor(5 to 20)")}
 | 
			
		||||
//         {timeExample("Mixed", "mm(5 to 20, floor(5 to 20), [.5,.5])")}
 | 
			
		||||
//     </div>
 | 
			
		||||
//   </div>;
 | 
			
		||||
 | 
			
		||||
let entry = EntryTypes.(entry(~title="Mixed Distributions", ~render=distributions));
 | 
			
		||||
// let entry = EntryTypes.(entry(~title="Mixed Distributions", ~render=distributions));
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +1,18 @@
 | 
			
		|||
let setup = dist =>
 | 
			
		||||
  DistPlusRenderer.Inputs.make(~distPlusIngredients=dist, ())
 | 
			
		||||
  |> DistPlusRenderer.run
 | 
			
		||||
  |> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
 | 
			
		||||
  |> E.R.toOption
 | 
			
		||||
  |> E.O.toExn("")
 | 
			
		||||
// let setup = dist =>
 | 
			
		||||
//   DistPlusRenderer.Inputs.make(~distPlusIngredients=dist, ())
 | 
			
		||||
//   |> DistPlusRenderer.run
 | 
			
		||||
//   |> E.R.fmap(distPlus => <DistPlusPlot distPlus />)
 | 
			
		||||
//   |> E.R.toOption
 | 
			
		||||
//   |> E.O.toExn("")
 | 
			
		||||
 | 
			
		||||
let simpleExample = (guesstimatorString, ~problem="", ()) =>
 | 
			
		||||
  <>
 | 
			
		||||
    <p> {guesstimatorString |> ReasonReact.string} </p>
 | 
			
		||||
    <p> {problem |> (e => "problem: " ++ e) |> ReasonReact.string} </p>
 | 
			
		||||
    {setup(
 | 
			
		||||
       DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()),
 | 
			
		||||
     )}
 | 
			
		||||
  </>;
 | 
			
		||||
// let simpleExample = (guesstimatorString, ~problem="", ()) =>
 | 
			
		||||
//   <>
 | 
			
		||||
//     <p> {guesstimatorString |> ReasonReact.string} </p>
 | 
			
		||||
//     <p> {problem |> (e => "problem: " ++ e) |> ReasonReact.string} </p>
 | 
			
		||||
//     {setup(
 | 
			
		||||
//        DistPlusRenderer.Inputs.Ingredients.make(~guesstimatorString, ()),
 | 
			
		||||
//      )}
 | 
			
		||||
//   </>;
 | 
			
		||||
 | 
			
		||||
let distributions = () =>
 | 
			
		||||
  <div>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,51 +20,51 @@ let distributions = () =>
 | 
			
		|||
      <h2 className="text-gray-800 text-xl font-bold">
 | 
			
		||||
        {"Initial Section" |> ReasonReact.string}
 | 
			
		||||
      </h2>
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
         "normal(-1, 1) + normal(5, 2)",
 | 
			
		||||
         ~problem="Tails look too flat",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
         "mm(normal(4,2), normal(10,1))",
 | 
			
		||||
         ~problem="Tails look too flat",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
         "normal(-1, 1) * normal(5, 2)",
 | 
			
		||||
         ~problem="This looks really weird",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
         "normal(1,2) * normal(2,2) * normal(3,1)",
 | 
			
		||||
         ~problem="Seems like important parts are cut off",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
         "mm(uniform(0, 1) , normal(3,2))",
 | 
			
		||||
         ~problem="Uniform distribution seems to break multimodal",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
        "truncate(mm(1 to 10, 10 to 30), 10, 20)",
 | 
			
		||||
         ~problem="Truncate seems to have no effect",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
        "normal(5,2)*(10^3)",
 | 
			
		||||
         ~problem="Multiplied items should be evaluated.",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
        "normal(5,10*3)",
 | 
			
		||||
         ~problem="At least simple operations in the distributions should be evaluated.",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      {simpleExample(
 | 
			
		||||
        "normal(5,10)^3",
 | 
			
		||||
         ~problem="Exponentiation not yet supported",
 | 
			
		||||
         (),
 | 
			
		||||
       )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //    "normal(-1, 1) + normal(5, 2)",
 | 
			
		||||
      //    ~problem="Tails look too flat",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //    "mm(normal(4,2), normal(10,1))",
 | 
			
		||||
      //    ~problem="Tails look too flat",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //    "normal(-1, 1) * normal(5, 2)",
 | 
			
		||||
      //    ~problem="This looks really weird",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //    "normal(1,2) * normal(2,2) * normal(3,1)",
 | 
			
		||||
      //    ~problem="Seems like important parts are cut off",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //    "mm(uniform(0, 1) , normal(3,2))",
 | 
			
		||||
      //    ~problem="Uniform distribution seems to break multimodal",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //   "truncate(mm(1 to 10, 10 to 30), 10, 20)",
 | 
			
		||||
      //    ~problem="Truncate seems to have no effect",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //   "normal(5,2)*(10^3)",
 | 
			
		||||
      //    ~problem="Multiplied items should be evaluated.",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //   "normal(5,10*3)",
 | 
			
		||||
      //    ~problem="At least simple operations in the distributions should be evaluated.",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
      // {simpleExample(
 | 
			
		||||
      //   "normal(5,10)^3",
 | 
			
		||||
      //    ~problem="Exponentiation not yet supported",
 | 
			
		||||
      //    (),
 | 
			
		||||
      //  )}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								src/App.re
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/App.re
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
type route =
 | 
			
		||||
  | Model(string)
 | 
			
		||||
  | DistBuilder
 | 
			
		||||
  | Drawer
 | 
			
		||||
  | Home
 | 
			
		||||
  | NotFound;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +8,6 @@ let routeToPath = route =>
 | 
			
		|||
  switch (route) {
 | 
			
		||||
  | Model(modelId) => "/m/" ++ modelId
 | 
			
		||||
  | DistBuilder => "/dist-builder"
 | 
			
		||||
  | Drawer => "/drawer"
 | 
			
		||||
  | Home => "/"
 | 
			
		||||
  | _ => "/"
 | 
			
		||||
  };
 | 
			
		||||
| 
						 | 
				
			
			@ -71,14 +69,13 @@ module Menu = {
 | 
			
		|||
      <Item href={routeToPath(DistBuilder)} key="dist-builder">
 | 
			
		||||
        {"Dist Builder" |> R.ste}
 | 
			
		||||
      </Item>
 | 
			
		||||
      <Item href={routeToPath(Drawer)} key="drawer">
 | 
			
		||||
        {"Drawer" |> R.ste}
 | 
			
		||||
      </Item>
 | 
			
		||||
 | 
			
		||||
    </div>;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let fixedLength = r =>
 | 
			
		||||
  <div className="w-full max-w-screen-xl mx-auto px-6"> r </div>;
 | 
			
		||||
 | 
			
		||||
[@react.component]
 | 
			
		||||
let make = () => {
 | 
			
		||||
  let url = ReasonReactRouter.useUrl();
 | 
			
		||||
| 
						 | 
				
			
			@ -87,23 +84,24 @@ let make = () => {
 | 
			
		|||
    switch (url.path) {
 | 
			
		||||
    | ["m", modelId] => Model(modelId)
 | 
			
		||||
    | ["dist-builder"] => DistBuilder
 | 
			
		||||
    | ["drawer"] => Drawer
 | 
			
		||||
    | [] => Home
 | 
			
		||||
    | _ => NotFound
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  <div className="w-full max-w-screen-xl mx-auto px-6">
 | 
			
		||||
  <>
 | 
			
		||||
    <Menu />
 | 
			
		||||
    {switch (routing) {
 | 
			
		||||
     | Model(id) =>
 | 
			
		||||
       switch (Models.getById(id)) {
 | 
			
		||||
       | Some(model) => <FormBuilder.ModelForm model key=id />
 | 
			
		||||
       | None => <div> {"Page is not found" |> R.ste} </div>
 | 
			
		||||
       }
 | 
			
		||||
       (
 | 
			
		||||
         switch (Models.getById(id)) {
 | 
			
		||||
         | Some(model) => <FormBuilder.ModelForm model key=id />
 | 
			
		||||
         | None => <div> {"Page is not found" |> R.ste} </div>
 | 
			
		||||
         }
 | 
			
		||||
       )
 | 
			
		||||
       |> fixedLength
 | 
			
		||||
     | DistBuilder => <DistBuilder />
 | 
			
		||||
     | Drawer => <Drawer />
 | 
			
		||||
     | Home => <Home />
 | 
			
		||||
     | _ => <div> {"Page is not found" |> R.ste} </div>
 | 
			
		||||
     | _ => fixedLength({"Page is not found" |> R.ste})
 | 
			
		||||
     }}
 | 
			
		||||
  </div>;
 | 
			
		||||
};
 | 
			
		||||
  </>;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										42
									
								
								src/components/CodeEditor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/components/CodeEditor.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
import React from "react";
 | 
			
		||||
import AceEditor from "react-ace";
 | 
			
		||||
 | 
			
		||||
import "ace-builds/src-noconflict/mode-golang";
 | 
			
		||||
import "ace-builds/src-noconflict/theme-github";
 | 
			
		||||
import "ace-builds/src-noconflict/ext-language_tools";
 | 
			
		||||
import "ace-builds/src-noconflict/keybinding-vim";
 | 
			
		||||
 | 
			
		||||
function onChange(newValue) {
 | 
			
		||||
  console.log("change", newValue);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CodeEditor extends React.Component {
 | 
			
		||||
  constructor(props) {
 | 
			
		||||
    super(props);
 | 
			
		||||
  }
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <AceEditor
 | 
			
		||||
        value={this.props.value}
 | 
			
		||||
        mode="golang"
 | 
			
		||||
        height="400px"
 | 
			
		||||
        width="100%"
 | 
			
		||||
        keyboardHandler="vim"
 | 
			
		||||
        theme="github"
 | 
			
		||||
        showGutter={false}
 | 
			
		||||
        highlightActiveLine={false}
 | 
			
		||||
        showPrintMargin={false}
 | 
			
		||||
        onChange={this.props.onChange}
 | 
			
		||||
        name="UNIQUE_ID_OF_DIV"
 | 
			
		||||
        editorProps={{
 | 
			
		||||
          $blockScrolling: true,
 | 
			
		||||
        }}
 | 
			
		||||
        setOptions={{
 | 
			
		||||
          enableBasicAutocompletion: false,
 | 
			
		||||
          enableLiveAutocompletion: true,
 | 
			
		||||
          enableSnippets: true,
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/components/CodeEditor.re
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/components/CodeEditor.re
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
[@bs.module "./CodeEditor.js"]
 | 
			
		||||
external codeEditor: ReasonReact.reactClass = "CodeEditor";
 | 
			
		||||
 | 
			
		||||
[@react.component]
 | 
			
		||||
let make = (~value="", ~onChange=(_:string) => (), ~children=ReasonReact.null) =>
 | 
			
		||||
  ReasonReact.wrapJsForReason(~reactClass=codeEditor, ~props={
 | 
			
		||||
      "value": value,
 | 
			
		||||
      "onChange": onChange
 | 
			
		||||
  }, children)
 | 
			
		||||
  |> ReasonReact.element;
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +19,9 @@ module FormConfig = [%lenses
 | 
			
		|||
    outputXYPoints: string,
 | 
			
		||||
    downsampleTo: string,
 | 
			
		||||
    kernelWidth: string,
 | 
			
		||||
    diagramStart: string,
 | 
			
		||||
    diagramStop: string,
 | 
			
		||||
    diagramCount: string,
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +30,9 @@ type options = {
 | 
			
		|||
  outputXYPoints: int,
 | 
			
		||||
  downsampleTo: option(int),
 | 
			
		||||
  kernelWidth: option(float),
 | 
			
		||||
  diagramStart: float,
 | 
			
		||||
  diagramStop: float,
 | 
			
		||||
  diagramCount: int,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Form = ReForm.Make(FormConfig);
 | 
			
		||||
| 
						 | 
				
			
			@ -36,18 +42,14 @@ let schema = Form.Validation.Schema([||]);
 | 
			
		|||
module FieldText = {
 | 
			
		||||
  [@react.component]
 | 
			
		||||
  let make = (~field, ~label) => {
 | 
			
		||||
    <Form.Field
 | 
			
		||||
      field
 | 
			
		||||
      render={({handleChange, error, value, validate}) =>
 | 
			
		||||
        <Antd.Form.Item label={label |> R.ste}>
 | 
			
		||||
          <Antd.Input.TextArea
 | 
			
		||||
            value
 | 
			
		||||
            onChange={BsReform.Helpers.handleChange(handleChange)}
 | 
			
		||||
            onBlur={_ => validate()}
 | 
			
		||||
          />
 | 
			
		||||
        </Antd.Form.Item>
 | 
			
		||||
      }
 | 
			
		||||
    />;
 | 
			
		||||
    <>
 | 
			
		||||
      <Form.Field
 | 
			
		||||
        field
 | 
			
		||||
        render={({handleChange, error, value, validate}) =>
 | 
			
		||||
          <CodeEditor value onChange={r => handleChange(r)} />
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
    </>;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
module FieldString = {
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +134,6 @@ module DemoDist = {
 | 
			
		|||
  [@react.component]
 | 
			
		||||
  let make = (~guesstimatorString, ~domain, ~unit, ~options) => {
 | 
			
		||||
    <Antd.Card title={"Distribution" |> R.ste}>
 | 
			
		||||
      <div className=Styles.spacer />
 | 
			
		||||
      <div>
 | 
			
		||||
        {switch (domain, unit, options) {
 | 
			
		||||
         | (Some(domain), Some(unit), Some(options)) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -149,22 +150,57 @@ module DemoDist = {
 | 
			
		|||
                 sampleCount: Some(options.sampleCount),
 | 
			
		||||
                 outputXYPoints: Some(options.outputXYPoints),
 | 
			
		||||
                 kernelWidth: options.kernelWidth,
 | 
			
		||||
                 shapeLength: Some(options.downsampleTo |> E.O.default(1000))
 | 
			
		||||
                 shapeLength:
 | 
			
		||||
                   Some(options.downsampleTo |> E.O.default(1000)),
 | 
			
		||||
               },
 | 
			
		||||
               ~distPlusIngredients,
 | 
			
		||||
               ~environment=
 | 
			
		||||
                 [|("p", `SymbolicDist(`Float(1.0)))|]
 | 
			
		||||
                 [|
 | 
			
		||||
                   ("K", `SymbolicDist(`Float(1000.0))),
 | 
			
		||||
                   ("M", `SymbolicDist(`Float(1000000.0))),
 | 
			
		||||
                   ("B", `SymbolicDist(`Float(1000000000.0))),
 | 
			
		||||
                   ("T", `SymbolicDist(`Float(1000000000000.0))),
 | 
			
		||||
                 |]
 | 
			
		||||
                 ->Belt.Map.String.fromArray,
 | 
			
		||||
               (),
 | 
			
		||||
             );
 | 
			
		||||
 | 
			
		||||
           let response1 = DistPlusRenderer.run(inputs1);
 | 
			
		||||
           let response1 = DistPlusRenderer.run2(inputs1);
 | 
			
		||||
           switch (response1) {
 | 
			
		||||
           | (Ok(distPlus1)) =>
 | 
			
		||||
             <>
 | 
			
		||||
               <DistPlusPlot distPlus={DistPlus.T.normalize(distPlus1)} />
 | 
			
		||||
             </>
 | 
			
		||||
           | (Error(r)) => r |> R.ste
 | 
			
		||||
           | Ok(`DistPlus(distPlus1)) =>
 | 
			
		||||
             <DistPlusPlot distPlus={DistPlus.T.normalize(distPlus1)} />
 | 
			
		||||
           | Ok(`Float(f)) =>
 | 
			
		||||
             <ForetoldComponents.NumberShower number=f precision=3 />
 | 
			
		||||
           | Ok(`Function((f, a), env)) =>
 | 
			
		||||
             //  Problem: When it gets the function, it doesn't save state about previous commands
 | 
			
		||||
             let foo: DistPlusRenderer.Inputs.inputs = {
 | 
			
		||||
               distPlusIngredients: inputs1.distPlusIngredients,
 | 
			
		||||
               samplingInputs: inputs1.samplingInputs,
 | 
			
		||||
               environment: env,
 | 
			
		||||
             };
 | 
			
		||||
             let results =
 | 
			
		||||
               E.A.Floats.range(options.diagramStart, options.diagramStop, options.diagramCount)
 | 
			
		||||
               |> E.A.fmap(r =>
 | 
			
		||||
                    DistPlusRenderer.runFunction(
 | 
			
		||||
                      foo,
 | 
			
		||||
                      (f, a),
 | 
			
		||||
                      [|`SymbolicDist(`Float(r))|],
 | 
			
		||||
                    )
 | 
			
		||||
                    |> E.R.bind(_, a =>
 | 
			
		||||
                         switch (a) {
 | 
			
		||||
                         | `DistPlus(d) => Ok((r, DistPlus.T.normalize(d)))
 | 
			
		||||
                         | n =>
 | 
			
		||||
                           Js.log2("Error here", n);
 | 
			
		||||
                           Error("wrong type");
 | 
			
		||||
                         }
 | 
			
		||||
                       )
 | 
			
		||||
                  )
 | 
			
		||||
               |> E.A.R.firstErrorOrOpen;
 | 
			
		||||
             switch (results) {
 | 
			
		||||
             | Ok(dists) => <PercentilesChart dists />
 | 
			
		||||
             | Error(r) => r |> R.ste
 | 
			
		||||
             };
 | 
			
		||||
           | Error(r) => r |> R.ste
 | 
			
		||||
           };
 | 
			
		||||
         | _ =>
 | 
			
		||||
           "Nothing to show. Try to change the distribution description."
 | 
			
		||||
| 
						 | 
				
			
			@ -175,9 +211,21 @@ module DemoDist = {
 | 
			
		|||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//         guesstimatorString: "
 | 
			
		||||
//         us_economy_2018 = (10.5 to 10.6)T
 | 
			
		||||
// growth_rate = 1.08 to 1.2
 | 
			
		||||
// us_economy(t) = us_economy_2018 * (growth_rate^t)
 | 
			
		||||
 | 
			
		||||
// us_population_2019 = 320M to 330M
 | 
			
		||||
// us_population_growth_rate = 1.01 to 1.02
 | 
			
		||||
// us_population(t) = us_population_2019 * (us_population_growth_rate^t)
 | 
			
		||||
 | 
			
		||||
// gdp_per_person(t) = us_economy(t)/us_population(t)
 | 
			
		||||
// gdp_per_person
 | 
			
		||||
// ",
 | 
			
		||||
[@react.component]
 | 
			
		||||
let make = () => {
 | 
			
		||||
  let (reloader, setRealoader) = React.useState(() => 1);
 | 
			
		||||
  let (reloader, setReloader) = React.useState(() => 1);
 | 
			
		||||
  let reform =
 | 
			
		||||
    Form.use(
 | 
			
		||||
      ~validationStrategy=OnDemand,
 | 
			
		||||
| 
						 | 
				
			
			@ -185,7 +233,7 @@ let make = () => {
 | 
			
		|||
      ~onSubmit=({state}) => {None},
 | 
			
		||||
      ~initialState={
 | 
			
		||||
        //guesstimatorString: "mm(normal(-10, 2), uniform(18, 25), lognormal({mean: 10, stdev: 8}), triangular(31,40,50))",
 | 
			
		||||
        guesstimatorString: "mm(1, 2, 3, normal(2, 1))", // , triangular(30, 40, 60)
 | 
			
		||||
         guesstimatorString: "mm(3)",
 | 
			
		||||
        domainType: "Complete",
 | 
			
		||||
        xPoint: "50.0",
 | 
			
		||||
        xPoint2: "60.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -194,10 +242,13 @@ let make = () => {
 | 
			
		|||
        unitType: "UnspecifiedDistribution",
 | 
			
		||||
        zero: MomentRe.momentNow(),
 | 
			
		||||
        unit: "days",
 | 
			
		||||
        sampleCount: "30000",
 | 
			
		||||
        sampleCount: "1000",
 | 
			
		||||
        outputXYPoints: "1000",
 | 
			
		||||
        downsampleTo: "",
 | 
			
		||||
        kernelWidth: "",
 | 
			
		||||
        diagramStart: "0",
 | 
			
		||||
        diagramStop: "10",
 | 
			
		||||
        diagramCount: "20",
 | 
			
		||||
      },
 | 
			
		||||
      (),
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -226,6 +277,9 @@ let make = () => {
 | 
			
		|||
    reform.state.values.outputXYPoints |> Js.Float.fromString;
 | 
			
		||||
  let downsampleTo = reform.state.values.downsampleTo |> Js.Float.fromString;
 | 
			
		||||
  let kernelWidth = reform.state.values.kernelWidth |> Js.Float.fromString;
 | 
			
		||||
  let diagramStart = reform.state.values.diagramStart |> Js.Float.fromString;
 | 
			
		||||
  let diagramStop = reform.state.values.diagramStop |> Js.Float.fromString;
 | 
			
		||||
  let diagramCount = reform.state.values.diagramCount |> Js.Float.fromString;
 | 
			
		||||
 | 
			
		||||
  let domain =
 | 
			
		||||
    switch (domainType) {
 | 
			
		||||
| 
						 | 
				
			
			@ -281,6 +335,9 @@ let make = () => {
 | 
			
		|||
          int_of_float(downsampleTo) > 0
 | 
			
		||||
            ? Some(int_of_float(downsampleTo)) : None,
 | 
			
		||||
        kernelWidth: kernelWidth == 0.0 ? None : Some(kernelWidth),
 | 
			
		||||
        diagramStart: diagramStart,
 | 
			
		||||
        diagramStop: diagramStop,
 | 
			
		||||
        diagramCount: diagramCount |> int_of_float,
 | 
			
		||||
      })
 | 
			
		||||
    | _ => None
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -303,214 +360,86 @@ let make = () => {
 | 
			
		|||
        reform.state.values.outputXYPoints,
 | 
			
		||||
        reform.state.values.downsampleTo,
 | 
			
		||||
        reform.state.values.kernelWidth,
 | 
			
		||||
        reform.state.values.diagramStart,
 | 
			
		||||
        reform.state.values.diagramStop,
 | 
			
		||||
        reform.state.values.diagramCount,
 | 
			
		||||
        reloader |> string_of_int,
 | 
			
		||||
      |],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  let onRealod = _ => {
 | 
			
		||||
    setRealoader(_ => reloader + 1);
 | 
			
		||||
  let onReload = _ => {
 | 
			
		||||
    setReloader(_ => reloader + 1);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  <div className=Styles.parent>
 | 
			
		||||
    <div className=Styles.spacer />
 | 
			
		||||
    demoDist
 | 
			
		||||
    <div className=Styles.spacer />
 | 
			
		||||
    <Antd.Card
 | 
			
		||||
      title={"Distribution Form" |> R.ste}
 | 
			
		||||
      extra={
 | 
			
		||||
        <Antd.Button
 | 
			
		||||
          icon=Antd.IconName.reload
 | 
			
		||||
          shape=`circle
 | 
			
		||||
          onClick=onRealod
 | 
			
		||||
        />
 | 
			
		||||
      }>
 | 
			
		||||
      <Form.Provider value=reform>
 | 
			
		||||
        <Antd.Form onSubmit>
 | 
			
		||||
          <Row _type=`flex className=Styles.rows>
 | 
			
		||||
            <Col span=24>
 | 
			
		||||
              <FieldText
 | 
			
		||||
                field=FormConfig.GuesstimatorString
 | 
			
		||||
                label="Guesstimator String"
 | 
			
		||||
              />
 | 
			
		||||
            </Col>
 | 
			
		||||
          </Row>
 | 
			
		||||
          <Row _type=`flex className=Styles.rows>
 | 
			
		||||
            <Col span=4>
 | 
			
		||||
              <Form.Field
 | 
			
		||||
                field=FormConfig.DomainType
 | 
			
		||||
                render={({handleChange, value}) =>
 | 
			
		||||
                  <Antd.Form.Item label={"Domain Type" |> R.ste}>
 | 
			
		||||
                    <Antd.Select value onChange={e => e |> handleChange}>
 | 
			
		||||
                      <Antd.Select.Option value="Complete">
 | 
			
		||||
                        {"Complete" |> R.ste}
 | 
			
		||||
                      </Antd.Select.Option>
 | 
			
		||||
                      <Antd.Select.Option value="LeftLimited">
 | 
			
		||||
                        {"Left Limited" |> R.ste}
 | 
			
		||||
                      </Antd.Select.Option>
 | 
			
		||||
                      <Antd.Select.Option value="RightLimited">
 | 
			
		||||
                        {"Right Limited" |> R.ste}
 | 
			
		||||
                      </Antd.Select.Option>
 | 
			
		||||
                      <Antd.Select.Option value="LeftAndRightLimited">
 | 
			
		||||
                        {"Left And Right Limited" |> R.ste}
 | 
			
		||||
                      </Antd.Select.Option>
 | 
			
		||||
                    </Antd.Select>
 | 
			
		||||
                  </Antd.Form.Item>
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
            </Col>
 | 
			
		||||
            {<>
 | 
			
		||||
               <Col span=4>
 | 
			
		||||
                 <FieldFloat
 | 
			
		||||
                   field=FormConfig.XPoint
 | 
			
		||||
                   label="Left X-point"
 | 
			
		||||
                   className=Styles.groupA
 | 
			
		||||
                 />
 | 
			
		||||
               </Col>
 | 
			
		||||
               <Col span=4>
 | 
			
		||||
                 <FieldFloat
 | 
			
		||||
                   field=FormConfig.ExcludingProbabilityMass
 | 
			
		||||
                   label="Left Excluding Probability Mass"
 | 
			
		||||
                   className=Styles.groupA
 | 
			
		||||
                 />
 | 
			
		||||
               </Col>
 | 
			
		||||
             </>
 | 
			
		||||
             |> R.showIf(
 | 
			
		||||
                  E.L.contains(
 | 
			
		||||
                    reform.state.values.domainType,
 | 
			
		||||
                    ["LeftLimited", "LeftAndRightLimited"],
 | 
			
		||||
                  ),
 | 
			
		||||
                )}
 | 
			
		||||
            {<>
 | 
			
		||||
               <Col span=4>
 | 
			
		||||
                 <FieldFloat
 | 
			
		||||
                   field=FormConfig.XPoint2
 | 
			
		||||
                   label="Right X-point"
 | 
			
		||||
                   className=Styles.groupB
 | 
			
		||||
                 />
 | 
			
		||||
               </Col>
 | 
			
		||||
               <Col span=4>
 | 
			
		||||
                 <FieldFloat
 | 
			
		||||
                   field=FormConfig.ExcludingProbabilityMass2
 | 
			
		||||
                   label="Right Excluding Probability Mass"
 | 
			
		||||
                   className=Styles.groupB
 | 
			
		||||
                 />
 | 
			
		||||
               </Col>
 | 
			
		||||
             </>
 | 
			
		||||
             |> R.showIf(
 | 
			
		||||
                  E.L.contains(
 | 
			
		||||
                    reform.state.values.domainType,
 | 
			
		||||
                    ["RightLimited", "LeftAndRightLimited"],
 | 
			
		||||
                  ),
 | 
			
		||||
                )}
 | 
			
		||||
          </Row>
 | 
			
		||||
          <Row _type=`flex className=Styles.rows>
 | 
			
		||||
            <Col span=4>
 | 
			
		||||
              <Form.Field
 | 
			
		||||
                field=FormConfig.UnitType
 | 
			
		||||
                render={({handleChange, value}) =>
 | 
			
		||||
                  <Antd.Form.Item label={"Unit Type" |> R.ste}>
 | 
			
		||||
                    <Antd.Select value onChange={e => e |> handleChange}>
 | 
			
		||||
                      <Antd.Select.Option value="UnspecifiedDistribution">
 | 
			
		||||
                        {"Unspecified Distribution" |> R.ste}
 | 
			
		||||
                      </Antd.Select.Option>
 | 
			
		||||
                      <Antd.Select.Option value="TimeDistribution">
 | 
			
		||||
                        {"Time Distribution" |> R.ste}
 | 
			
		||||
                      </Antd.Select.Option>
 | 
			
		||||
                    </Antd.Select>
 | 
			
		||||
                  </Antd.Form.Item>
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
            </Col>
 | 
			
		||||
            {<>
 | 
			
		||||
               <Col span=4>
 | 
			
		||||
                 <Form.Field
 | 
			
		||||
                   field=FormConfig.Zero
 | 
			
		||||
                   render={({handleChange, value}) =>
 | 
			
		||||
                     <Antd.Form.Item label={"Zero Point" |> R.ste}>
 | 
			
		||||
                       <Antd_DatePicker
 | 
			
		||||
                         value
 | 
			
		||||
                         onChange={e => {
 | 
			
		||||
                           e |> handleChange;
 | 
			
		||||
                           _ => ();
 | 
			
		||||
                         }}
 | 
			
		||||
                       />
 | 
			
		||||
                     </Antd.Form.Item>
 | 
			
		||||
                   }
 | 
			
		||||
                 />
 | 
			
		||||
               </Col>
 | 
			
		||||
               <Col span=4>
 | 
			
		||||
                 <Form.Field
 | 
			
		||||
                   field=FormConfig.Unit
 | 
			
		||||
                   render={({handleChange, value}) =>
 | 
			
		||||
                     <Antd.Form.Item label={"Unit" |> R.ste}>
 | 
			
		||||
                       <Antd.Select value onChange={e => e |> handleChange}>
 | 
			
		||||
                         <Antd.Select.Option value="days">
 | 
			
		||||
                           {"Days" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="hours">
 | 
			
		||||
                           {"Hours" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="milliseconds">
 | 
			
		||||
                           {"Milliseconds" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="minutes">
 | 
			
		||||
                           {"Minutes" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="months">
 | 
			
		||||
                           {"Months" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="quarters">
 | 
			
		||||
                           {"Quarters" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="seconds">
 | 
			
		||||
                           {"Seconds" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="weeks">
 | 
			
		||||
                           {"Weeks" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                         <Antd.Select.Option value="years">
 | 
			
		||||
                           {"Years" |> R.ste}
 | 
			
		||||
                         </Antd.Select.Option>
 | 
			
		||||
                       </Antd.Select>
 | 
			
		||||
                     </Antd.Form.Item>
 | 
			
		||||
                   }
 | 
			
		||||
                 />
 | 
			
		||||
               </Col>
 | 
			
		||||
             </>
 | 
			
		||||
             |> R.showIf(
 | 
			
		||||
                  E.L.contains(
 | 
			
		||||
                    reform.state.values.unitType,
 | 
			
		||||
                    ["TimeDistribution"],
 | 
			
		||||
                  ),
 | 
			
		||||
                )}
 | 
			
		||||
          </Row>
 | 
			
		||||
          <Row _type=`flex className=Styles.rows>
 | 
			
		||||
            <Col span=4>
 | 
			
		||||
              <FieldFloat field=FormConfig.SampleCount label="Sample Count" />
 | 
			
		||||
            </Col>
 | 
			
		||||
            <Col span=4>
 | 
			
		||||
              <FieldFloat
 | 
			
		||||
                field=FormConfig.OutputXYPoints
 | 
			
		||||
                label="Output XY-points"
 | 
			
		||||
              />
 | 
			
		||||
            </Col>
 | 
			
		||||
            <Col span=4>
 | 
			
		||||
              <FieldFloat
 | 
			
		||||
                field=FormConfig.DownsampleTo
 | 
			
		||||
                label="Downsample To"
 | 
			
		||||
              />
 | 
			
		||||
            </Col>
 | 
			
		||||
            <Col span=4>
 | 
			
		||||
              <FieldFloat field=FormConfig.KernelWidth label="Kernel Width" />
 | 
			
		||||
            </Col>
 | 
			
		||||
          </Row>
 | 
			
		||||
  <div className="grid grid-cols-2 gap-4">
 | 
			
		||||
    <div>
 | 
			
		||||
      <Antd.Card
 | 
			
		||||
        title={"Distribution Form" |> R.ste}
 | 
			
		||||
        extra={
 | 
			
		||||
          <Antd.Button
 | 
			
		||||
            _type=`primary icon=Antd.IconName.reload onClick=onRealod>
 | 
			
		||||
            {"Update Distribution" |> R.ste}
 | 
			
		||||
          </Antd.Button>
 | 
			
		||||
        </Antd.Form>
 | 
			
		||||
      </Form.Provider>
 | 
			
		||||
    </Antd.Card>
 | 
			
		||||
    <div className=Styles.spacer />
 | 
			
		||||
            icon=Antd.IconName.reload
 | 
			
		||||
            shape=`circle
 | 
			
		||||
            onClick=onReload
 | 
			
		||||
          />
 | 
			
		||||
        }>
 | 
			
		||||
        <Form.Provider value=reform>
 | 
			
		||||
          <Antd.Form onSubmit>
 | 
			
		||||
            <Row _type=`flex className=Styles.rows>
 | 
			
		||||
              <Col span=24>
 | 
			
		||||
                <FieldText
 | 
			
		||||
                  field=FormConfig.GuesstimatorString
 | 
			
		||||
                  label="Program"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
            </Row>
 | 
			
		||||
            <Row _type=`flex className=Styles.rows>
 | 
			
		||||
              <Col span=12>
 | 
			
		||||
                <FieldFloat
 | 
			
		||||
                  field=FormConfig.SampleCount
 | 
			
		||||
                  label="Sample Count"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col span=12>
 | 
			
		||||
                <FieldFloat
 | 
			
		||||
                  field=FormConfig.OutputXYPoints
 | 
			
		||||
                  label="Output XY-points"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col span=12>
 | 
			
		||||
                <FieldFloat
 | 
			
		||||
                  field=FormConfig.DownsampleTo
 | 
			
		||||
                  label="Downsample To"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col span=12>
 | 
			
		||||
                <FieldFloat
 | 
			
		||||
                  field=FormConfig.KernelWidth
 | 
			
		||||
                  label="Kernel Width"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col span=12>
 | 
			
		||||
                <FieldFloat
 | 
			
		||||
                  field=FormConfig.DiagramStart
 | 
			
		||||
                  label="Diagram Start"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col span=12>
 | 
			
		||||
                <FieldFloat
 | 
			
		||||
                  field=FormConfig.DiagramStop
 | 
			
		||||
                  label="Diagram Stop"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col span=12>
 | 
			
		||||
                <FieldFloat
 | 
			
		||||
                  field=FormConfig.DiagramCount
 | 
			
		||||
                  label="Diagram Count"
 | 
			
		||||
                />
 | 
			
		||||
              </Col>
 | 
			
		||||
            </Row>
 | 
			
		||||
          </Antd.Form>
 | 
			
		||||
        </Form.Provider>
 | 
			
		||||
      </Antd.Card>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div> demoDist </div>
 | 
			
		||||
  </div>;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,992 +0,0 @@
 | 
			
		|||
module Types = {
 | 
			
		||||
  type rectangle = {
 | 
			
		||||
    // Ref: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
 | 
			
		||||
    left: int,
 | 
			
		||||
    top: int,
 | 
			
		||||
    right: int,
 | 
			
		||||
    bottom: int,
 | 
			
		||||
    x: int,
 | 
			
		||||
    y: int,
 | 
			
		||||
    width: int,
 | 
			
		||||
    height: int,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type webapi = Webapi.Canvas.Canvas2d.t;
 | 
			
		||||
 | 
			
		||||
  type xyShape = DistTypes.xyShape; /* {
 | 
			
		||||
    xs: array(float),
 | 
			
		||||
    ys: array(float),
 | 
			
		||||
  };*/
 | 
			
		||||
 | 
			
		||||
  type continuousShape = DistTypes.continuousShape; /*{
 | 
			
		||||
    xyShape,
 | 
			
		||||
    interpolation: [ | `Stepwise | `Linear],
 | 
			
		||||
  };*/
 | 
			
		||||
 | 
			
		||||
  type canvasPoint = {
 | 
			
		||||
    w: float,
 | 
			
		||||
    h: float,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type canvasShape = {
 | 
			
		||||
    ws: array(float),
 | 
			
		||||
    hs: array(float),
 | 
			
		||||
    xValues: array(float),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type foretoldFormElements = {
 | 
			
		||||
    measurableId: string,
 | 
			
		||||
    token: string,
 | 
			
		||||
    comment: string,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type distributionLimits = {
 | 
			
		||||
    lower: float,
 | 
			
		||||
    upper: float,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type canvasState = {
 | 
			
		||||
    canvasShape: option(canvasShape),
 | 
			
		||||
    lastMousePosition: option(canvasPoint),
 | 
			
		||||
    isMouseDown: bool,
 | 
			
		||||
    readyToRender: bool,
 | 
			
		||||
    hasJustBeenSentToForetold: bool,
 | 
			
		||||
    limitsHaveJustBeenUpdated: bool,
 | 
			
		||||
    foretoldFormElements,
 | 
			
		||||
    distributionLimits,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module CanvasContext = {
 | 
			
		||||
  type t = Types.webapi;
 | 
			
		||||
 | 
			
		||||
  /* Externals */
 | 
			
		||||
  [@bs.send]
 | 
			
		||||
  external getBoundingClientRect: Dom.element => Types.rectangle =
 | 
			
		||||
    "getBoundingClientRect";
 | 
			
		||||
  [@bs.send] external setLineDash: (t, array(int)) => unit = "setLineDash";
 | 
			
		||||
 | 
			
		||||
  /* Webapi functions */
 | 
			
		||||
  // Ref: https://github.com/reasonml-community/bs-webapi-incubator/blob/master/src/Webapi/Webapi__Canvas/Webapi__Canvas__Canvas2d.re
 | 
			
		||||
  let getContext2d: Dom.element => t = Webapi.Canvas.CanvasElement.getContext2d;
 | 
			
		||||
  module Canvas2d = Webapi.Canvas.Canvas2d;
 | 
			
		||||
  let clearRect = Canvas2d.clearRect;
 | 
			
		||||
  let setFillStyle = Canvas2d.setFillStyle;
 | 
			
		||||
  let fillRect = Canvas2d.fillRect;
 | 
			
		||||
  let beginPath = Canvas2d.beginPath;
 | 
			
		||||
  let closePath = Canvas2d.closePath;
 | 
			
		||||
  let setStrokeStyle = Canvas2d.setStrokeStyle;
 | 
			
		||||
  let lineWidth = Canvas2d.lineWidth;
 | 
			
		||||
  let moveTo = Canvas2d.moveTo;
 | 
			
		||||
  let lineTo = Canvas2d.lineTo;
 | 
			
		||||
  let stroke = Canvas2d.stroke;
 | 
			
		||||
  let font = Canvas2d.font;
 | 
			
		||||
  let textAlign = Canvas2d.textAlign;
 | 
			
		||||
  let strokeText = Canvas2d.strokeText;
 | 
			
		||||
  let fillText = Canvas2d.fillText;
 | 
			
		||||
 | 
			
		||||
  /* Padding */
 | 
			
		||||
  let paddingRatioX = 0.9;
 | 
			
		||||
  let paddingRatioY = 0.9;
 | 
			
		||||
 | 
			
		||||
  let paddingFactorX = (rectangleWidth: int) =>
 | 
			
		||||
    (1. -. paddingRatioX) *. float_of_int(rectangleWidth) /. 2.0;
 | 
			
		||||
  let paddingFactorY = (rectangleHeight: int) =>
 | 
			
		||||
    (1. -. paddingRatioY) *. float_of_int(rectangleHeight) /. 2.0;
 | 
			
		||||
 | 
			
		||||
  let translatePointToInside = (canvasElement: Dom.element) => {
 | 
			
		||||
    let rectangle: Types.rectangle = getBoundingClientRect(canvasElement);
 | 
			
		||||
    let translate = (p: Types.canvasPoint): Types.canvasPoint => {
 | 
			
		||||
      let w = p.w -. float_of_int(rectangle.x);
 | 
			
		||||
      let h = p.h -. float_of_int(rectangle.y);
 | 
			
		||||
      {w, h};
 | 
			
		||||
    };
 | 
			
		||||
    translate;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Convert = {
 | 
			
		||||
  /*
 | 
			
		||||
   - In this module, the fundamental unit for the canvas shape is the distance vector from the (0,0) point at the upper leftmost corner of the screen.
 | 
			
		||||
     - For some drawing functions, this is instead from the (0,0) point at the upper leftmost corner of the canvas element. This is irrelevant in this module.
 | 
			
		||||
   - The fundamental unit for a probability distribution is an x coordinate and its corresponding y probability density
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
  let xyShapeToCanvasShape =
 | 
			
		||||
      (~xyShape: Types.xyShape, ~canvasElement: Dom.element) => {
 | 
			
		||||
    let xs = xyShape.xs;
 | 
			
		||||
    let ys = xyShape.ys;
 | 
			
		||||
    let rectangle: Types.rectangle =
 | 
			
		||||
      CanvasContext.getBoundingClientRect(canvasElement);
 | 
			
		||||
    let lengthX = E.A.length(xs);
 | 
			
		||||
 | 
			
		||||
    let minX = xs[0];
 | 
			
		||||
    let maxX = xs[lengthX - 1];
 | 
			
		||||
    let ratioXs =
 | 
			
		||||
      float_of_int(rectangle.width)
 | 
			
		||||
      *. CanvasContext.paddingRatioX
 | 
			
		||||
      /. (maxX -. minX);
 | 
			
		||||
    let ws =
 | 
			
		||||
      E.A.fmap(
 | 
			
		||||
        x =>
 | 
			
		||||
          (x -. minX)
 | 
			
		||||
          *. ratioXs
 | 
			
		||||
          +. float_of_int(rectangle.left)
 | 
			
		||||
          +. (1. -. CanvasContext.paddingRatioX)
 | 
			
		||||
          *. float_of_int(rectangle.width)
 | 
			
		||||
          /. 2.0,
 | 
			
		||||
        xs,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    let minY = 0.;
 | 
			
		||||
    let maxY = E.A.reduce(ys, 0., (x, y) => x > y ? x : y);
 | 
			
		||||
    let ratioYs =
 | 
			
		||||
      float_of_int(rectangle.height)
 | 
			
		||||
      *. CanvasContext.paddingRatioY
 | 
			
		||||
      /. (maxY -. minY);
 | 
			
		||||
    let hs =
 | 
			
		||||
      E.A.fmap(
 | 
			
		||||
        y =>
 | 
			
		||||
          float_of_int(rectangle.bottom)
 | 
			
		||||
          -. y
 | 
			
		||||
          *. ratioYs
 | 
			
		||||
          -. CanvasContext.paddingFactorY(rectangle.height),
 | 
			
		||||
        ys,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    let canvasShape: Types.canvasShape = {ws, hs, xValues: xs};
 | 
			
		||||
    canvasShape;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let canvasShapeToContinuousShape =
 | 
			
		||||
      (~canvasShape: Types.canvasShape, ~canvasElement: Dom.element)
 | 
			
		||||
      : Types.continuousShape => {
 | 
			
		||||
    let xs = canvasShape.xValues;
 | 
			
		||||
    let hs = canvasShape.hs;
 | 
			
		||||
    let rectangle: Types.rectangle =
 | 
			
		||||
      CanvasContext.getBoundingClientRect(canvasElement);
 | 
			
		||||
    let bottom = float_of_int(rectangle.bottom);
 | 
			
		||||
    let paddingFactorY = CanvasContext.paddingFactorX(rectangle.height);
 | 
			
		||||
    let windowScrollY: float = [%raw "window.scrollY"];
 | 
			
		||||
 | 
			
		||||
    let y0Line = bottom +. windowScrollY -. paddingFactorY;
 | 
			
		||||
    let ys = E.A.fmap(h => y0Line -. h, hs);
 | 
			
		||||
 | 
			
		||||
    let xyShape: Types.xyShape = {xs, ys};
 | 
			
		||||
    let continuousShape: Types.continuousShape = {
 | 
			
		||||
      xyShape,
 | 
			
		||||
      interpolation: `Linear,
 | 
			
		||||
      integralSumCache: None,
 | 
			
		||||
      integralCache: None,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let integral = XYShape.Analysis.integrateContinuousShape(continuousShape);
 | 
			
		||||
    let ys = E.A.fmap(y => y /. integral, ys);
 | 
			
		||||
 | 
			
		||||
    let continuousShape: Types.continuousShape = {
 | 
			
		||||
      xyShape: {
 | 
			
		||||
        xs,
 | 
			
		||||
        ys,
 | 
			
		||||
      },
 | 
			
		||||
      interpolation: `Linear,
 | 
			
		||||
      integralSumCache: Some(1.0),
 | 
			
		||||
      integralCache: None,
 | 
			
		||||
    };
 | 
			
		||||
    continuousShape;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /* Misc helper functions */
 | 
			
		||||
  let log2 = x => log(x) /. log(2.0);
 | 
			
		||||
  let findClosestInOrderedArrayDangerously = (x: float, xs: array(float)) => {
 | 
			
		||||
    let l = Array.length(xs);
 | 
			
		||||
    let a = ref(0);
 | 
			
		||||
    let b = ref(l - 1);
 | 
			
		||||
    let numSteps = int_of_float(log2(float_of_int(l))) + 1;
 | 
			
		||||
    for (_ in 0 to numSteps) {
 | 
			
		||||
      let c = (a^ + b^) / 2;
 | 
			
		||||
      xs[c] > x ? b := c : a := c;
 | 
			
		||||
    };
 | 
			
		||||
    b^;
 | 
			
		||||
  };
 | 
			
		||||
  let getPoint = (canvasShape: Types.canvasShape, n: int): Types.canvasPoint => {
 | 
			
		||||
    let point: Types.canvasPoint = {
 | 
			
		||||
      w: canvasShape.ws[n],
 | 
			
		||||
      h: canvasShape.hs[n],
 | 
			
		||||
    };
 | 
			
		||||
    point;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Draw = {
 | 
			
		||||
  let line =
 | 
			
		||||
      (
 | 
			
		||||
        canvasElement: Dom.element,
 | 
			
		||||
        ~point0: Types.canvasPoint,
 | 
			
		||||
        ~point1: Types.canvasPoint,
 | 
			
		||||
      )
 | 
			
		||||
      : unit => {
 | 
			
		||||
    let translator = CanvasContext.translatePointToInside(canvasElement);
 | 
			
		||||
    let point0 = translator(point0);
 | 
			
		||||
    let point1 = translator(point1);
 | 
			
		||||
 | 
			
		||||
    let context = CanvasContext.getContext2d(canvasElement);
 | 
			
		||||
    CanvasContext.beginPath(context);
 | 
			
		||||
    CanvasContext.moveTo(context, ~x=point0.w, ~y=point0.h);
 | 
			
		||||
    CanvasContext.lineTo(context, ~x=point1.w, ~y=point1.h);
 | 
			
		||||
    CanvasContext.stroke(context);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let canvasPlot =
 | 
			
		||||
      (canvasElement: Dom.element, canvasShape: Types.canvasShape) => {
 | 
			
		||||
    let context = CanvasContext.getContext2d(canvasElement);
 | 
			
		||||
    let rectangle: Types.rectangle =
 | 
			
		||||
      CanvasContext.getBoundingClientRect(canvasElement);
 | 
			
		||||
 | 
			
		||||
    /* Some useful reference points */
 | 
			
		||||
    let paddingFactorX = CanvasContext.paddingFactorX(rectangle.width);
 | 
			
		||||
    let paddingFactorY = CanvasContext.paddingFactorX(rectangle.height);
 | 
			
		||||
 | 
			
		||||
    let p00: Types.canvasPoint = {
 | 
			
		||||
      w: float_of_int(rectangle.left) +. paddingFactorX,
 | 
			
		||||
      h: float_of_int(rectangle.bottom) -. paddingFactorY,
 | 
			
		||||
    };
 | 
			
		||||
    let p01: Types.canvasPoint = {
 | 
			
		||||
      w: float_of_int(rectangle.left) +. paddingFactorX,
 | 
			
		||||
      h: float_of_int(rectangle.top) +. paddingFactorY,
 | 
			
		||||
    };
 | 
			
		||||
    let p10: Types.canvasPoint = {
 | 
			
		||||
      w: float_of_int(rectangle.right) -. paddingFactorX,
 | 
			
		||||
      h: float_of_int(rectangle.bottom) -. paddingFactorY,
 | 
			
		||||
    };
 | 
			
		||||
    let p11: Types.canvasPoint = {
 | 
			
		||||
      w: float_of_int(rectangle.right) -. paddingFactorX,
 | 
			
		||||
      h: float_of_int(rectangle.top) +. paddingFactorY,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /* Clear the canvas with new white sheet */
 | 
			
		||||
    CanvasContext.clearRect(
 | 
			
		||||
      context,
 | 
			
		||||
      ~x=0.,
 | 
			
		||||
      ~y=0.,
 | 
			
		||||
      ~w=float_of_int(rectangle.width),
 | 
			
		||||
      ~h=float_of_int(rectangle.height),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /* Draw a line between every two adjacent points */
 | 
			
		||||
    let length = Array.length(canvasShape.ws);
 | 
			
		||||
    let windowScrollY: float = [%raw "window.scrollY"];
 | 
			
		||||
    CanvasContext.setStrokeStyle(context, String, "#5680cc");
 | 
			
		||||
    CanvasContext.lineWidth(context, 4.);
 | 
			
		||||
 | 
			
		||||
    for (i in 1 to length - 1) {
 | 
			
		||||
      let point0 = Convert.getPoint(canvasShape, i - 1);
 | 
			
		||||
      let point1 = Convert.getPoint(canvasShape, i);
 | 
			
		||||
 | 
			
		||||
      let point0 = {...point0, h: point0.h -. windowScrollY};
 | 
			
		||||
      let point1 = {...point1, h: point1.h -. windowScrollY};
 | 
			
		||||
      line(canvasElement, ~point0, ~point1);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /* Draws the expected value line */
 | 
			
		||||
    // Removed on the grounds that it didn't play nice with changes in limits.
 | 
			
		||||
    /*
 | 
			
		||||
          let continuousShape =
 | 
			
		||||
            Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement);
 | 
			
		||||
          let mean = Continuous.T.mean(continuousShape);
 | 
			
		||||
          let variance = Continuous.T.variance(continuousShape);
 | 
			
		||||
          let meanLocation =
 | 
			
		||||
            Convert.findClosestInOrderedArrayDangerously(mean, canvasShape.xValues);
 | 
			
		||||
          let meanLocationCanvasX = canvasShape.ws[meanLocation];
 | 
			
		||||
          let meanLocationCanvasY = canvasShape.hs[meanLocation];
 | 
			
		||||
          CanvasContext.beginPath(context);
 | 
			
		||||
          CanvasContext.setStrokeStyle(context, String, "#5680cc");
 | 
			
		||||
          CanvasContext.setLineDash(context, [|5, 10|]);
 | 
			
		||||
 | 
			
		||||
          line(
 | 
			
		||||
            canvasElement,
 | 
			
		||||
            ~point0={w: meanLocationCanvasX, h: p00.h},
 | 
			
		||||
            ~point1={
 | 
			
		||||
              w: meanLocationCanvasX,
 | 
			
		||||
              h: meanLocationCanvasY -. windowScrollY,
 | 
			
		||||
            },
 | 
			
		||||
          );
 | 
			
		||||
          CanvasContext.stroke(context);
 | 
			
		||||
          CanvasContext.setLineDash(context, [||]);
 | 
			
		||||
     */
 | 
			
		||||
    /* draws lines parallel to x axis + factors to help w/ precise drawing. */
 | 
			
		||||
    CanvasContext.beginPath(context);
 | 
			
		||||
    CanvasContext.setStrokeStyle(context, String, "#CCC");
 | 
			
		||||
    CanvasContext.lineWidth(context, 2.);
 | 
			
		||||
    CanvasContext.font(context, "18px Roboto");
 | 
			
		||||
    CanvasContext.textAlign(context, "center");
 | 
			
		||||
 | 
			
		||||
    let numLines = 8;
 | 
			
		||||
    let height =
 | 
			
		||||
      float_of_int(rectangle.height)
 | 
			
		||||
      *. CanvasContext.paddingRatioX
 | 
			
		||||
      /. float_of_int(numLines);
 | 
			
		||||
 | 
			
		||||
    for (i in 0 to numLines - 1) {
 | 
			
		||||
      let pLeft = {...p00, h: p00.h -. height *. float_of_int(i)};
 | 
			
		||||
      let pRight = {...p10, h: p10.h -. height *. float_of_int(i)};
 | 
			
		||||
      line(canvasElement, ~point0=pLeft, ~point1=pRight);
 | 
			
		||||
      CanvasContext.fillText(
 | 
			
		||||
        string_of_int(i) ++ "x",
 | 
			
		||||
        ~x=pLeft.w -. float_of_int(rectangle.left) +. 15.0,
 | 
			
		||||
        ~y=pLeft.h -. float_of_int(rectangle.top) -. 5.0,
 | 
			
		||||
        context,
 | 
			
		||||
      );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /* Draw a frame around the drawable area */
 | 
			
		||||
    CanvasContext.lineWidth(context, 2.0);
 | 
			
		||||
    CanvasContext.setStrokeStyle(context, String, "#000");
 | 
			
		||||
    line(canvasElement, ~point0=p00, ~point1=p01);
 | 
			
		||||
    line(canvasElement, ~point0=p01, ~point1=p11);
 | 
			
		||||
    line(canvasElement, ~point0=p11, ~point1=p10);
 | 
			
		||||
    line(canvasElement, ~point0=p10, ~point1=p00);
 | 
			
		||||
 | 
			
		||||
    /* draw units along the x axis */
 | 
			
		||||
    CanvasContext.font(context, "16px Roboto");
 | 
			
		||||
    CanvasContext.lineWidth(context, 2.0);
 | 
			
		||||
 | 
			
		||||
    let numIntervals = 10;
 | 
			
		||||
    let width = float_of_int(rectangle.width);
 | 
			
		||||
    let height = float_of_int(rectangle.height);
 | 
			
		||||
    let xMin = canvasShape.xValues[0];
 | 
			
		||||
    let xMax = canvasShape.xValues[length - 1];
 | 
			
		||||
    let xSpan = (xMax -. xMin) /. float_of_int(numIntervals - 1);
 | 
			
		||||
 | 
			
		||||
    for (i in 0 to numIntervals - 1) {
 | 
			
		||||
      let x =
 | 
			
		||||
        float_of_int(rectangle.left)
 | 
			
		||||
        +. width
 | 
			
		||||
        *. float_of_int(i)
 | 
			
		||||
        /. float_of_int(numIntervals);
 | 
			
		||||
      let dashValue = xMin +. xSpan *. float_of_int(i);
 | 
			
		||||
      CanvasContext.fillText(
 | 
			
		||||
        Js.Float.toFixedWithPrecision(dashValue, ~digits=2),
 | 
			
		||||
        ~x,
 | 
			
		||||
        ~y=height,
 | 
			
		||||
        context,
 | 
			
		||||
      );
 | 
			
		||||
      line(
 | 
			
		||||
        canvasElement,
 | 
			
		||||
        ~point0={
 | 
			
		||||
          w: x +. CanvasContext.paddingFactorX(rectangle.width),
 | 
			
		||||
          h: p00.h,
 | 
			
		||||
        },
 | 
			
		||||
        ~point1={
 | 
			
		||||
          w: x +. CanvasContext.paddingFactorX(rectangle.width),
 | 
			
		||||
          h: p00.h +. 10.0,
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let initialDistribution = (canvasElement: Dom.element, setState) => {
 | 
			
		||||
    let mean = 100.0;
 | 
			
		||||
    let stdev = 15.0;
 | 
			
		||||
    let numSamples = 3000;
 | 
			
		||||
 | 
			
		||||
    let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
 | 
			
		||||
    let normalShape =
 | 
			
		||||
      ExpressionTree.toShape(
 | 
			
		||||
        {sampleCount: 10000, outputXYPoints: 10000, kernelWidth: None, shapeLength:numSamples},
 | 
			
		||||
        ExpressionTypes.ExpressionTree.Environment.empty,
 | 
			
		||||
        `SymbolicDist(normal),
 | 
			
		||||
      ) |> E.R.toExn;
 | 
			
		||||
    let xyShape: Types.xyShape =
 | 
			
		||||
      switch (normalShape) {
 | 
			
		||||
      | Mixed(_) => {xs: [||], ys: [||]}
 | 
			
		||||
      | Discrete(_) => {xs: [||], ys: [||]}
 | 
			
		||||
      | Continuous(m) => Continuous.getShape(m)
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
    /* // To use a lognormal instead:
 | 
			
		||||
       let lognormal = SymbolicTypes.Lognormal.fromMeanAndStdev(mean, stdev);
 | 
			
		||||
       let lognormalShape =
 | 
			
		||||
         SymbolicTypes.GenericSimple.toShape(lognormal, numSamples);
 | 
			
		||||
       let lognormalXYShape: Types.xyShape =
 | 
			
		||||
         switch (lognormalShape) {
 | 
			
		||||
         | Mixed(_) => {xs: [||], ys: [||]}
 | 
			
		||||
         | Discrete(_) => {xs: [||], ys: [||]}
 | 
			
		||||
         | Continuous(m) => Continuous.getShape(m)
 | 
			
		||||
         };
 | 
			
		||||
       */
 | 
			
		||||
 | 
			
		||||
    let canvasShape = Convert.xyShapeToCanvasShape(~xyShape, ~canvasElement);
 | 
			
		||||
    /* let continuousShapeBack =
 | 
			
		||||
         Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement);
 | 
			
		||||
       */
 | 
			
		||||
 | 
			
		||||
    let windowScrollY: float = [%raw "window.scrollY"];
 | 
			
		||||
    let canvasShape = {
 | 
			
		||||
      ...canvasShape,
 | 
			
		||||
      hs: E.A.fmap(h => h +. windowScrollY, canvasShape.hs),
 | 
			
		||||
    };
 | 
			
		||||
    setState((state: Types.canvasState) => {
 | 
			
		||||
      {...state, canvasShape: Some(canvasShape)}
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    canvasPlot(canvasElement, canvasShape);
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module ForetoldAPI = {
 | 
			
		||||
  let predict = (~measurableId, ~token, ~xs, ~ys, ~comment) => {
 | 
			
		||||
    let payload = Js.Dict.empty();
 | 
			
		||||
    let xsString = Js.Array.toString(xs);
 | 
			
		||||
    let ysString = Js.Array.toString(ys);
 | 
			
		||||
 | 
			
		||||
    let query = {j|mutation {
 | 
			
		||||
      measurementCreate(input: {
 | 
			
		||||
          value: {
 | 
			
		||||
          floatCdf: {
 | 
			
		||||
              xs: [$xsString]
 | 
			
		||||
              ys: [$ysString]
 | 
			
		||||
          }
 | 
			
		||||
          }
 | 
			
		||||
          valueText: "Drawn by hand."
 | 
			
		||||
          description: "$comment"
 | 
			
		||||
          competitorType: COMPETITIVE
 | 
			
		||||
          measurableId: "$measurableId"
 | 
			
		||||
      }) {
 | 
			
		||||
          id
 | 
			
		||||
      }
 | 
			
		||||
      }|j};
 | 
			
		||||
    Js.Dict.set(payload, "query", Js.Json.string(query));
 | 
			
		||||
 | 
			
		||||
    Js.Promise.(
 | 
			
		||||
      Fetch.fetchWithInit(
 | 
			
		||||
        "https://api.foretold.io/graphql?token=" ++ token,
 | 
			
		||||
        Fetch.RequestInit.make(
 | 
			
		||||
          ~method_=Post,
 | 
			
		||||
          ~body=
 | 
			
		||||
            Fetch.BodyInit.make(
 | 
			
		||||
              Js.Json.stringify(Js.Json.object_(payload)),
 | 
			
		||||
            ),
 | 
			
		||||
          ~headers=
 | 
			
		||||
            Fetch.HeadersInit.make({
 | 
			
		||||
              "Accept-Encoding": "gzip, deflate, br",
 | 
			
		||||
              "Content-Type": "application/json",
 | 
			
		||||
              "Accept": "application/json",
 | 
			
		||||
              "Connection": "keep-alive",
 | 
			
		||||
              "DNT": "1",
 | 
			
		||||
              "Origin": "https://api.foretold.io",
 | 
			
		||||
            }),
 | 
			
		||||
          (),
 | 
			
		||||
        ),
 | 
			
		||||
      )
 | 
			
		||||
      |> then_(Fetch.Response.json)
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module State = {
 | 
			
		||||
  type t = Types.canvasState;
 | 
			
		||||
 | 
			
		||||
  let initialState: t = {
 | 
			
		||||
    canvasShape: None,
 | 
			
		||||
    lastMousePosition: None,
 | 
			
		||||
    isMouseDown: false,
 | 
			
		||||
    readyToRender: false,
 | 
			
		||||
    hasJustBeenSentToForetold: false,
 | 
			
		||||
    limitsHaveJustBeenUpdated: false,
 | 
			
		||||
    foretoldFormElements: {
 | 
			
		||||
      measurableId: "",
 | 
			
		||||
      token: "",
 | 
			
		||||
      comment: "",
 | 
			
		||||
    },
 | 
			
		||||
    distributionLimits: {
 | 
			
		||||
      lower: 0.0,
 | 
			
		||||
      upper: 1000.0,
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let updateMousePosition = (~point: Types.canvasPoint, ~setState) => {
 | 
			
		||||
    setState((state: t) => {...state, lastMousePosition: Some(point)});
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let onMouseMovement =
 | 
			
		||||
      (
 | 
			
		||||
        ~event: ReactEvent.Mouse.t,
 | 
			
		||||
        ~potentialCanvas: option(Dom.element),
 | 
			
		||||
        ~state: t,
 | 
			
		||||
        ~setState,
 | 
			
		||||
      ) => {
 | 
			
		||||
    /* Helper functions and objects*/
 | 
			
		||||
    let x = ReactEvent.Mouse.clientX(event);
 | 
			
		||||
    let y = ReactEvent.Mouse.clientY(event);
 | 
			
		||||
 | 
			
		||||
    let windowScrollY: float = [%raw "window.scrollY"];
 | 
			
		||||
 | 
			
		||||
    let point1: Types.canvasPoint = {
 | 
			
		||||
      w: float_of_int(x),
 | 
			
		||||
      h: float_of_int(y) +. windowScrollY,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let pointIsInBetween =
 | 
			
		||||
        (a: Types.canvasPoint, b: Types.canvasPoint, c: Types.canvasPoint)
 | 
			
		||||
        : bool => {
 | 
			
		||||
      let x0 = a.w;
 | 
			
		||||
      let x1 = b.w;
 | 
			
		||||
      let x2 = c.w;
 | 
			
		||||
      x0 < x2 && x2 < x1 || x1 < x2 && x2 < x0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /* If all conditions are met, update the distribution */
 | 
			
		||||
    let updateDistWithMouseMovement =
 | 
			
		||||
        (
 | 
			
		||||
          ~point0: Types.canvasPoint,
 | 
			
		||||
          ~point1: Types.canvasPoint,
 | 
			
		||||
          ~canvasShape: Types.canvasShape,
 | 
			
		||||
        ) => {
 | 
			
		||||
      /*
 | 
			
		||||
       The mouse moves across the screen, and we get a series of (x,y) positions.
 | 
			
		||||
       We know where the mouse last was
 | 
			
		||||
       we update everything between the last (x,y) position and the new (x,y), using linear interpolation
 | 
			
		||||
       Note that we only want to update & iterate over the parts of the canvas which are changed by the mouse movement
 | 
			
		||||
       (otherwise, things might be too slow)
 | 
			
		||||
       */
 | 
			
		||||
 | 
			
		||||
      let slope = (point1.h -. point0.h) /. (point1.w -. point0.w);
 | 
			
		||||
      let pos0 =
 | 
			
		||||
        Convert.findClosestInOrderedArrayDangerously(
 | 
			
		||||
          point0.w,
 | 
			
		||||
          canvasShape.ws,
 | 
			
		||||
        );
 | 
			
		||||
      let pos1 =
 | 
			
		||||
        Convert.findClosestInOrderedArrayDangerously(
 | 
			
		||||
          point1.w,
 | 
			
		||||
          canvasShape.ws,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
      // Mouse is moving to the right
 | 
			
		||||
      for (i in pos0 to pos1) {
 | 
			
		||||
        let pointN = Convert.getPoint(canvasShape, i);
 | 
			
		||||
        switch (pointIsInBetween(point0, point1, pointN)) {
 | 
			
		||||
        | false => ()
 | 
			
		||||
        | true =>
 | 
			
		||||
          canvasShape.hs[i] = point0.h +. slope *. (pointN.w -. point0.w)
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      // Mouse is moving to the left
 | 
			
		||||
      for (i in pos0 downto pos1) {
 | 
			
		||||
        let pointN = Convert.getPoint(canvasShape, i);
 | 
			
		||||
        switch (pointIsInBetween(point0, point1, pointN)) {
 | 
			
		||||
        | false => ()
 | 
			
		||||
        | true =>
 | 
			
		||||
          canvasShape.hs[i] = point0.h +. slope *. (pointN.w -. point0.w)
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
      canvasShape;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /* Check that the mouse movement was within the paddding box. */
 | 
			
		||||
    let validateYCoordinates =
 | 
			
		||||
        (~point: Types.canvasPoint, ~rectangle: Types.rectangle) => {
 | 
			
		||||
      switch (
 | 
			
		||||
        /*
 | 
			
		||||
         - If we also validate the xs, this produces a jaded user experience around the edges.
 | 
			
		||||
         - Instead, we will also update the first and last points in the updateDistWithMouseMovement, with the findClosestInOrderedArrayDangerously function, even when the x is outside the padding zone
 | 
			
		||||
         - When we send the distribution to foretold, we'll get rid of the first and last points.
 | 
			
		||||
         */
 | 
			
		||||
        /*
 | 
			
		||||
         point.w >= float_of_int(rectangle.left)
 | 
			
		||||
         +. CanvasContext.paddingFactorX(rectangle.width),
 | 
			
		||||
         point.w <= float_of_int(rectangle.right)
 | 
			
		||||
         -. CanvasContext.paddingFactorX(rectangle.width),
 | 
			
		||||
         */
 | 
			
		||||
        point.h
 | 
			
		||||
        -. windowScrollY >= float_of_int(rectangle.top)
 | 
			
		||||
        +. CanvasContext.paddingFactorY(rectangle.height),
 | 
			
		||||
        point.h
 | 
			
		||||
        -. windowScrollY <= float_of_int(rectangle.bottom)
 | 
			
		||||
        -. CanvasContext.paddingFactorY(rectangle.height),
 | 
			
		||||
      ) {
 | 
			
		||||
      | (true, true) => true
 | 
			
		||||
      | _ => false
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let decideWithCanvas = (~canvasElement, ~canvasShape, ~point0) => {
 | 
			
		||||
      let rectangle = CanvasContext.getBoundingClientRect(canvasElement);
 | 
			
		||||
      switch (
 | 
			
		||||
        validateYCoordinates(~point=point0, ~rectangle),
 | 
			
		||||
        validateYCoordinates(~point=point1, ~rectangle),
 | 
			
		||||
      ) {
 | 
			
		||||
      | (true, true) =>
 | 
			
		||||
        let newCanvasShape =
 | 
			
		||||
          updateDistWithMouseMovement(~point0, ~point1, ~canvasShape);
 | 
			
		||||
        setState((state: t) => {
 | 
			
		||||
          {
 | 
			
		||||
            ...state,
 | 
			
		||||
            lastMousePosition: Some(point1),
 | 
			
		||||
            canvasShape: Some(newCanvasShape),
 | 
			
		||||
            readyToRender: false,
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        state.readyToRender
 | 
			
		||||
          ? Draw.canvasPlot(canvasElement, newCanvasShape) : ();
 | 
			
		||||
 | 
			
		||||
      | (false, true) => updateMousePosition(~point=point1, ~setState)
 | 
			
		||||
      | (_, false) => ()
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    switch (
 | 
			
		||||
      potentialCanvas,
 | 
			
		||||
      state.canvasShape,
 | 
			
		||||
      state.isMouseDown,
 | 
			
		||||
      state.lastMousePosition,
 | 
			
		||||
    ) {
 | 
			
		||||
    | (Some(canvasElement), Some(canvasShape), true, Some(point0)) =>
 | 
			
		||||
      decideWithCanvas(~canvasElement, ~canvasShape, ~point0)
 | 
			
		||||
    | (Some(canvasElement), _, true, None) =>
 | 
			
		||||
      let rectangle = CanvasContext.getBoundingClientRect(canvasElement);
 | 
			
		||||
      validateYCoordinates(~point=point1, ~rectangle)
 | 
			
		||||
        ? updateMousePosition(~point=point1, ~setState) : ();
 | 
			
		||||
    | _ => ()
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let onMouseClick = (~setState, ~state) => {
 | 
			
		||||
    setState((state: t) => {
 | 
			
		||||
      {...state, isMouseDown: !state.isMouseDown, lastMousePosition: None}
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let onSubmitForetoldForm =
 | 
			
		||||
      (
 | 
			
		||||
        ~state: Types.canvasState,
 | 
			
		||||
        ~potentialCanvasElement: option(Dom.element),
 | 
			
		||||
        ~setState,
 | 
			
		||||
      ) => {
 | 
			
		||||
    let potentialCanvasShape = state.canvasShape;
 | 
			
		||||
 | 
			
		||||
    switch (potentialCanvasShape, potentialCanvasElement) {
 | 
			
		||||
    | (None, _) => ()
 | 
			
		||||
    | (_, None) => ()
 | 
			
		||||
    | (Some(canvasShape), Some(canvasElement)) =>
 | 
			
		||||
      let pdf =
 | 
			
		||||
        Convert.canvasShapeToContinuousShape(~canvasShape, ~canvasElement);
 | 
			
		||||
 | 
			
		||||
      /* create a cdf from a pdf */
 | 
			
		||||
      let _pdf = Continuous.T.normalize(pdf);
 | 
			
		||||
 | 
			
		||||
      let cdf = Continuous.T.integral(_pdf);
 | 
			
		||||
      let xs = [||];
 | 
			
		||||
      let ys = [||];
 | 
			
		||||
      for (i in 1 to 999) {
 | 
			
		||||
        /*
 | 
			
		||||
         - see comment in validateYCoordinates as to why this starts at 1.
 | 
			
		||||
         - foretold accepts distributions with up to 1000 points.
 | 
			
		||||
         */
 | 
			
		||||
        let j = i * 3;
 | 
			
		||||
        Js.Array.push(cdf.xyShape.xs[j], xs);
 | 
			
		||||
        Js.Array.push(cdf.xyShape.ys[j], ys);
 | 
			
		||||
 | 
			
		||||
        ();
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      ForetoldAPI.predict(
 | 
			
		||||
        ~measurableId=state.foretoldFormElements.measurableId,
 | 
			
		||||
        ~token=state.foretoldFormElements.token,
 | 
			
		||||
        ~comment=state.foretoldFormElements.comment,
 | 
			
		||||
        ~xs,
 | 
			
		||||
        ~ys,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      setState((state: t) => {...state, hasJustBeenSentToForetold: true});
 | 
			
		||||
      Js.Global.setTimeout(
 | 
			
		||||
        () => {
 | 
			
		||||
          setState((state: t) =>
 | 
			
		||||
            {...state, hasJustBeenSentToForetold: false}
 | 
			
		||||
          )
 | 
			
		||||
        },
 | 
			
		||||
        5000,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      ();
 | 
			
		||||
    };
 | 
			
		||||
    ();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let onSubmitLimitsForm =
 | 
			
		||||
      (
 | 
			
		||||
        ~state: Types.canvasState,
 | 
			
		||||
        ~potentialCanvasElement: option(Dom.element),
 | 
			
		||||
        ~setState,
 | 
			
		||||
      ) => {
 | 
			
		||||
    let potentialCanvasShape = state.canvasShape;
 | 
			
		||||
 | 
			
		||||
    switch (potentialCanvasShape, potentialCanvasElement) {
 | 
			
		||||
    | (None, _) => ()
 | 
			
		||||
    | (_, None) => ()
 | 
			
		||||
    | (Some(canvasShape), Some(canvasElement)) =>
 | 
			
		||||
      let xValues = canvasShape.xValues;
 | 
			
		||||
      let length = Array.length(xValues);
 | 
			
		||||
      let xMin = xValues[0];
 | 
			
		||||
      let xMax = xValues[length - 1];
 | 
			
		||||
      let lower = state.distributionLimits.lower;
 | 
			
		||||
      let upper = state.distributionLimits.upper;
 | 
			
		||||
 | 
			
		||||
      let slope = (upper -. lower) /. (xMax -. xMin);
 | 
			
		||||
      let delta = lower -. slope *. xMin;
 | 
			
		||||
 | 
			
		||||
      let xValues = E.A.fmap(x => delta +. x *. slope, xValues);
 | 
			
		||||
      let newCanvasShape = {...canvasShape, xValues};
 | 
			
		||||
      setState((state: t) =>
 | 
			
		||||
        {
 | 
			
		||||
          ...state,
 | 
			
		||||
          canvasShape: Some(newCanvasShape),
 | 
			
		||||
          limitsHaveJustBeenUpdated: true,
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
      Draw.canvasPlot(canvasElement, newCanvasShape);
 | 
			
		||||
 | 
			
		||||
      Js.Global.setTimeout(
 | 
			
		||||
        () => {
 | 
			
		||||
          setState((state: t) =>
 | 
			
		||||
            {...state, limitsHaveJustBeenUpdated: false}
 | 
			
		||||
          )
 | 
			
		||||
        },
 | 
			
		||||
        5000,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      ();
 | 
			
		||||
    };
 | 
			
		||||
    ();
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Styles = {
 | 
			
		||||
  open Css;
 | 
			
		||||
  let dist = style([padding(em(1.))]);
 | 
			
		||||
  let spacer = style([marginTop(em(1.))]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
[@react.component]
 | 
			
		||||
let make = () => {
 | 
			
		||||
  let canvasRef: React.Ref.t(option(Dom.element)) = React.useRef(None); // should morally live inside the state, but this is tricky.
 | 
			
		||||
  let (state, setState) = React.useState(() => State.initialState);
 | 
			
		||||
 | 
			
		||||
  /* Draw the initial distribution */
 | 
			
		||||
  React.useEffect0(() => {
 | 
			
		||||
    let potentialCanvas = React.Ref.current(canvasRef);
 | 
			
		||||
    (
 | 
			
		||||
      switch (potentialCanvas) {
 | 
			
		||||
      | Some(canvasElement) =>
 | 
			
		||||
        Draw.initialDistribution(canvasElement, setState)
 | 
			
		||||
      | None => ()
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
    |> ignore;
 | 
			
		||||
    None;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /* Render the current distribution every 30ms, while the mouse is moving and changing it */
 | 
			
		||||
  React.useEffect0(() => {
 | 
			
		||||
    let runningInterval =
 | 
			
		||||
      Js.Global.setInterval(
 | 
			
		||||
        () => {
 | 
			
		||||
          setState((state: Types.canvasState) => {
 | 
			
		||||
            {...state, readyToRender: true}
 | 
			
		||||
          })
 | 
			
		||||
        },
 | 
			
		||||
        30,
 | 
			
		||||
      );
 | 
			
		||||
    Some(() => Js.Global.clearInterval(runningInterval));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  <Antd.Card title={"Distribution Drawer" |> R.ste}>
 | 
			
		||||
    <div className=Styles.spacer />
 | 
			
		||||
    <p> {"Click to begin drawing, click to stop drawing" |> R.ste} </p>
 | 
			
		||||
    <canvas
 | 
			
		||||
      width="1000"
 | 
			
		||||
      height="700"
 | 
			
		||||
      ref={ReactDOMRe.Ref.callbackDomRef(elem =>
 | 
			
		||||
        React.Ref.setCurrent(canvasRef, Js.Nullable.toOption(elem))
 | 
			
		||||
      )}
 | 
			
		||||
      onMouseMove={event =>
 | 
			
		||||
        State.onMouseMovement(
 | 
			
		||||
          ~event,
 | 
			
		||||
          ~potentialCanvas=React.Ref.current(canvasRef),
 | 
			
		||||
          ~state,
 | 
			
		||||
          ~setState,
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      onClick={_e => State.onMouseClick(~setState, ~state)}
 | 
			
		||||
    />
 | 
			
		||||
    <div className=Styles.spacer />
 | 
			
		||||
    <div className=Styles.spacer />
 | 
			
		||||
    <br />
 | 
			
		||||
    <br />
 | 
			
		||||
    <br />
 | 
			
		||||
    <Antd.Card title={"Update upper and lower limits" |> R.ste}>
 | 
			
		||||
      <form
 | 
			
		||||
        id="update-limits"
 | 
			
		||||
        onSubmit={(e: ReactEvent.Form.t): unit => {
 | 
			
		||||
          ReactEvent.Form.preventDefault(e);
 | 
			
		||||
          /* code to run on submit */
 | 
			
		||||
          State.onSubmitLimitsForm(
 | 
			
		||||
            ~state,
 | 
			
		||||
            ~potentialCanvasElement=React.Ref.current(canvasRef),
 | 
			
		||||
            ~setState,
 | 
			
		||||
          );
 | 
			
		||||
          ();
 | 
			
		||||
        }}>
 | 
			
		||||
        <div>
 | 
			
		||||
          <label> {"Lower:  " |> R.ste} </label>
 | 
			
		||||
          <input
 | 
			
		||||
            type_="number"
 | 
			
		||||
            id="lowerlimit"
 | 
			
		||||
            name="lowerlimit"
 | 
			
		||||
            value={Js.Float.toString(state.distributionLimits.lower)}
 | 
			
		||||
            placeholder="a number. f.ex., 0"
 | 
			
		||||
            required=true
 | 
			
		||||
            step=0.001
 | 
			
		||||
            onChange={event => {
 | 
			
		||||
              let value = ReactEvent.Form.target(event)##value;
 | 
			
		||||
              setState((state: Types.canvasState) => {
 | 
			
		||||
                {
 | 
			
		||||
                  ...state,
 | 
			
		||||
                  distributionLimits: {
 | 
			
		||||
                    ...state.distributionLimits,
 | 
			
		||||
                    lower: value,
 | 
			
		||||
                  },
 | 
			
		||||
                }
 | 
			
		||||
              });
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <br />
 | 
			
		||||
        <div>
 | 
			
		||||
          <label> {"Upper:  " |> R.ste} </label>
 | 
			
		||||
          <input
 | 
			
		||||
            type_="number"
 | 
			
		||||
            id="upperlimit"
 | 
			
		||||
            name="upperlimit"
 | 
			
		||||
            value={Js.Float.toString(state.distributionLimits.upper)}
 | 
			
		||||
            placeholder="a number. f.ex., 100"
 | 
			
		||||
            required=true
 | 
			
		||||
            step=0.001
 | 
			
		||||
            onChange={event => {
 | 
			
		||||
              let value = ReactEvent.Form.target(event)##value;
 | 
			
		||||
              setState((state: Types.canvasState) => {
 | 
			
		||||
                {
 | 
			
		||||
                  ...state,
 | 
			
		||||
                  distributionLimits: {
 | 
			
		||||
                    ...state.distributionLimits,
 | 
			
		||||
                    upper: value,
 | 
			
		||||
                  },
 | 
			
		||||
                }
 | 
			
		||||
              });
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <br />
 | 
			
		||||
        <button type_="submit" id="updatelimits">
 | 
			
		||||
          {"Update limits" |> R.ste}
 | 
			
		||||
        </button>
 | 
			
		||||
        <br />
 | 
			
		||||
        <p hidden={!state.limitsHaveJustBeenUpdated}>
 | 
			
		||||
          {"Updated!" |> R.ste}
 | 
			
		||||
        </p>
 | 
			
		||||
      </form>
 | 
			
		||||
    </Antd.Card>
 | 
			
		||||
    <br />
 | 
			
		||||
    <br />
 | 
			
		||||
    <br />
 | 
			
		||||
    <Antd.Card title={"Send to foretold" |> R.ste}>
 | 
			
		||||
      <form
 | 
			
		||||
        id="send-to-foretold"
 | 
			
		||||
        onSubmit={(e: ReactEvent.Form.t): unit => {
 | 
			
		||||
          ReactEvent.Form.preventDefault(e);
 | 
			
		||||
          /* code to run on submit */
 | 
			
		||||
          State.onSubmitForetoldForm(
 | 
			
		||||
            ~state,
 | 
			
		||||
            ~potentialCanvasElement=React.Ref.current(canvasRef),
 | 
			
		||||
            ~setState,
 | 
			
		||||
          );
 | 
			
		||||
          ();
 | 
			
		||||
        }}>
 | 
			
		||||
        <div>
 | 
			
		||||
          <label> {"MeasurableId:  " |> R.ste} </label>
 | 
			
		||||
          <input
 | 
			
		||||
            type_="text"
 | 
			
		||||
            id="measurableId"
 | 
			
		||||
            name="measurableId"
 | 
			
		||||
            value={state.foretoldFormElements.measurableId}
 | 
			
		||||
            placeholder="The last bit in the url, after the m"
 | 
			
		||||
            required=true
 | 
			
		||||
            onChange={event => {
 | 
			
		||||
              let value = ReactEvent.Form.target(event)##value;
 | 
			
		||||
              setState((state: Types.canvasState) => {
 | 
			
		||||
                {
 | 
			
		||||
                  ...state,
 | 
			
		||||
                  foretoldFormElements: {
 | 
			
		||||
                    ...state.foretoldFormElements,
 | 
			
		||||
                    measurableId: value,
 | 
			
		||||
                  },
 | 
			
		||||
                }
 | 
			
		||||
              });
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <br />
 | 
			
		||||
        <div>
 | 
			
		||||
          <label> {"Foretold bot token: " |> R.ste} </label>
 | 
			
		||||
          <input
 | 
			
		||||
            type_="text"
 | 
			
		||||
            id="foretoldToken"
 | 
			
		||||
            name="foretoldToken"
 | 
			
		||||
            value={state.foretoldFormElements.token}
 | 
			
		||||
            placeholder="Profile -> Bots -> (New Bot) -> Token"
 | 
			
		||||
            required=true
 | 
			
		||||
            onChange={event => {
 | 
			
		||||
              let value = ReactEvent.Form.target(event)##value;
 | 
			
		||||
              setState((state: Types.canvasState) => {
 | 
			
		||||
                {
 | 
			
		||||
                  ...state,
 | 
			
		||||
                  foretoldFormElements: {
 | 
			
		||||
                    ...state.foretoldFormElements,
 | 
			
		||||
                    token: value,
 | 
			
		||||
                  },
 | 
			
		||||
                }
 | 
			
		||||
              });
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <br />
 | 
			
		||||
        <textarea
 | 
			
		||||
          id="comment"
 | 
			
		||||
          name="comment"
 | 
			
		||||
          rows=20
 | 
			
		||||
          cols=70
 | 
			
		||||
          placeholder="Explain a little bit what this distribution is about"
 | 
			
		||||
          onChange={event => {
 | 
			
		||||
            let value = ReactEvent.Form.target(event)##value;
 | 
			
		||||
            setState((state: Types.canvasState) => {
 | 
			
		||||
              {
 | 
			
		||||
                ...state,
 | 
			
		||||
                foretoldFormElements: {
 | 
			
		||||
                  ...state.foretoldFormElements,
 | 
			
		||||
                  comment: value,
 | 
			
		||||
                },
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <br />
 | 
			
		||||
        <button type_="submit" id="sendToForetoldButton">
 | 
			
		||||
          {"Send to foretold" |> R.ste}
 | 
			
		||||
        </button>
 | 
			
		||||
        <br />
 | 
			
		||||
        <p hidden={!state.hasJustBeenSentToForetold}> {"Sent!" |> R.ste} </p>
 | 
			
		||||
      </form>
 | 
			
		||||
    </Antd.Card>
 | 
			
		||||
  </Antd.Card>;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -259,7 +259,6 @@ module DistPlusChart = {
 | 
			
		|||
module IntegralChart = {
 | 
			
		||||
  [@react.component]
 | 
			
		||||
  let make = (~distPlus: DistTypes.distPlus, ~config: chartConfig, ~onHover) => {
 | 
			
		||||
    open DistPlus;
 | 
			
		||||
    let integral = distPlus.integralCache;
 | 
			
		||||
    let continuous =
 | 
			
		||||
      integral
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -104,8 +104,9 @@ let reducer = (state: state, action: action) =>
 | 
			
		|||
let init = {
 | 
			
		||||
  showStats: false,
 | 
			
		||||
  showParams: false,
 | 
			
		||||
  showPercentiles: true,
 | 
			
		||||
  showPercentiles: false,
 | 
			
		||||
  distributions: [
 | 
			
		||||
    {yLog: false, xLog: false, isCumulative: false, height: 1},
 | 
			
		||||
    {yLog: false, xLog: false, isCumulative: false, height: 4},
 | 
			
		||||
    {yLog: false, xLog: false, isCumulative: true, height: 1},
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										10
									
								
								src/components/charts/DistributionPlot/PercentilesChart.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/components/charts/DistributionPlot/PercentilesChart.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
import * as _ from "lodash";
 | 
			
		||||
import { createClassFromSpec } from "react-vega";
 | 
			
		||||
import spec from "./spec-percentiles";
 | 
			
		||||
 | 
			
		||||
const PercentilesChart = createClassFromSpec({
 | 
			
		||||
  spec,
 | 
			
		||||
  style: "width: 100%",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export { PercentilesChart };
 | 
			
		||||
							
								
								
									
										41
									
								
								src/components/charts/DistributionPlot/PercentilesChart.re
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/components/charts/DistributionPlot/PercentilesChart.re
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
[@bs.module "./PercentilesChart.js"]
 | 
			
		||||
external percentilesChart: ReasonReact.reactClass = "PercentilesChart";
 | 
			
		||||
 | 
			
		||||
module Internal = {
 | 
			
		||||
  [@react.component]
 | 
			
		||||
  let make = (~data, ~signalListeners, ~children=ReasonReact.null) =>
 | 
			
		||||
    ReasonReact.wrapJsForReason(
 | 
			
		||||
      ~reactClass=percentilesChart,
 | 
			
		||||
      ~props={"data": data, "signalListeners": signalListeners},
 | 
			
		||||
      children,
 | 
			
		||||
    )
 | 
			
		||||
    |> ReasonReact.element;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
[@react.component]
 | 
			
		||||
let make =
 | 
			
		||||
    (~dists: array((float, DistTypes.distPlus)), ~children=ReasonReact.null) => {
 | 
			
		||||
  let data =
 | 
			
		||||
    dists
 | 
			
		||||
    |> E.A.fmap(((x, r)) => {
 | 
			
		||||
         {
 | 
			
		||||
           "x": x,
 | 
			
		||||
           "p1": r |> DistPlus.T.Integral.yToX(0.01),
 | 
			
		||||
           "p5": r |> DistPlus.T.Integral.yToX(0.05),
 | 
			
		||||
           "p10": r |> DistPlus.T.Integral.yToX(0.1),
 | 
			
		||||
           "p20": r |> DistPlus.T.Integral.yToX(0.2),
 | 
			
		||||
           "p30": r |> DistPlus.T.Integral.yToX(0.3),
 | 
			
		||||
           "p40": r |> DistPlus.T.Integral.yToX(0.4),
 | 
			
		||||
           "p50": r |> DistPlus.T.Integral.yToX(0.5),
 | 
			
		||||
           "p60": r |> DistPlus.T.Integral.yToX(0.6),
 | 
			
		||||
           "p70": r |> DistPlus.T.Integral.yToX(0.7),
 | 
			
		||||
           "p80": r |> DistPlus.T.Integral.yToX(0.8),
 | 
			
		||||
           "p90": r |> DistPlus.T.Integral.yToX(0.9),
 | 
			
		||||
           "p95": r |> DistPlus.T.Integral.yToX(0.95),
 | 
			
		||||
           "p99": r |> DistPlus.T.Integral.yToX(0.99),
 | 
			
		||||
         }
 | 
			
		||||
       });
 | 
			
		||||
       Js.log3("Data", dists, data);
 | 
			
		||||
  let da = {"facet": data};
 | 
			
		||||
  <Internal data=da signalListeners={}/>;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										208
									
								
								src/components/charts/DistributionPlot/spec-percentiles.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								src/components/charts/DistributionPlot/spec-percentiles.json
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,208 @@
 | 
			
		|||
{
 | 
			
		||||
  "$schema": "https://vega.github.io/schema/vega/v5.json",
 | 
			
		||||
  "width": 500,
 | 
			
		||||
  "height": 400,
 | 
			
		||||
  "padding": 5,
 | 
			
		||||
  "data": [
 | 
			
		||||
    {
 | 
			
		||||
      "name": "facet",
 | 
			
		||||
      "values": [],
 | 
			
		||||
      "format": { "type": "json", "parse": { "timestamp": "date" } }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "table",
 | 
			
		||||
      "source": "facet",
 | 
			
		||||
      "transform": [
 | 
			
		||||
        {
 | 
			
		||||
          "type": "aggregate",
 | 
			
		||||
          "groupby": ["x"],
 | 
			
		||||
          "ops": [
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean",
 | 
			
		||||
            "mean"
 | 
			
		||||
          ],
 | 
			
		||||
          "fields": [
 | 
			
		||||
            "p1",
 | 
			
		||||
            "p5",
 | 
			
		||||
            "p10",
 | 
			
		||||
            "p20",
 | 
			
		||||
            "p30",
 | 
			
		||||
            "p40",
 | 
			
		||||
            "p50",
 | 
			
		||||
            "p60",
 | 
			
		||||
            "p70",
 | 
			
		||||
            "p80",
 | 
			
		||||
            "p90",
 | 
			
		||||
            "p95",
 | 
			
		||||
            "p99"
 | 
			
		||||
          ],
 | 
			
		||||
          "as": [
 | 
			
		||||
            "p1",
 | 
			
		||||
            "p5",
 | 
			
		||||
            "p10",
 | 
			
		||||
            "p20",
 | 
			
		||||
            "p30",
 | 
			
		||||
            "p40",
 | 
			
		||||
            "p50",
 | 
			
		||||
            "p60",
 | 
			
		||||
            "p70",
 | 
			
		||||
            "p80",
 | 
			
		||||
            "p90",
 | 
			
		||||
            "p95",
 | 
			
		||||
            "p99"
 | 
			
		||||
          ]
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "scales": [
 | 
			
		||||
    {
 | 
			
		||||
      "name": "xscale",
 | 
			
		||||
      "type": "linear",
 | 
			
		||||
      "nice": true,
 | 
			
		||||
      "domain": { "data": "facet", "field": "x" },
 | 
			
		||||
      "range": "width"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "name": "yscale",
 | 
			
		||||
      "type": "linear",
 | 
			
		||||
      "range": "height",
 | 
			
		||||
      "nice": true,
 | 
			
		||||
      "zero": true,
 | 
			
		||||
      "domain": { "data": "facet", "field": "p99" }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "axes": [
 | 
			
		||||
    {
 | 
			
		||||
      "orient": "bottom",
 | 
			
		||||
      "scale": "xscale",
 | 
			
		||||
      "grid": false,
 | 
			
		||||
      "tickSize": 2,
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "grid": { "enter": { "stroke": { "value": "#ccc" } } },
 | 
			
		||||
        "ticks": { "enter": { "stroke": { "value": "#ccc" } } }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "orient": "left",
 | 
			
		||||
      "scale": "yscale",
 | 
			
		||||
      "grid": false,
 | 
			
		||||
      "domain": false,
 | 
			
		||||
      "tickSize": 2,
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "grid": { "enter": { "stroke": { "value": "#ccc" } } },
 | 
			
		||||
        "ticks": { "enter": { "stroke": { "value": "#ccc" } } }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "marks": [
 | 
			
		||||
    {
 | 
			
		||||
      "type": "area",
 | 
			
		||||
      "from": { "data": "table" },
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "enter": { "fill": { "value": "#4C78A8" } },
 | 
			
		||||
        "update": {
 | 
			
		||||
          "interpolate": { "value": "monotone" },
 | 
			
		||||
          "x": { "scale": "xscale", "field": "x" },
 | 
			
		||||
          "y": { "scale": "yscale", "field": "p1" },
 | 
			
		||||
          "y2": { "scale": "yscale", "field": "p99" },
 | 
			
		||||
          "opacity": { "value": 0.05 }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "type": "area",
 | 
			
		||||
      "from": { "data": "table" },
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "enter": { "fill": { "value": "#4C78A8" } },
 | 
			
		||||
        "update": {
 | 
			
		||||
          "interpolate": { "value": "monotone" },
 | 
			
		||||
          "x": { "scale": "xscale", "field": "x" },
 | 
			
		||||
          "y": { "scale": "yscale", "field": "p5" },
 | 
			
		||||
          "y2": { "scale": "yscale", "field": "p95" },
 | 
			
		||||
          "opacity": { "value": 0.1 }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "type": "area",
 | 
			
		||||
      "from": { "data": "table" },
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "enter": { "fill": { "value": "#4C78A8" } },
 | 
			
		||||
        "update": {
 | 
			
		||||
          "interpolate": { "value": "monotone" },
 | 
			
		||||
          "x": { "scale": "xscale", "field": "x" },
 | 
			
		||||
          "y": { "scale": "yscale", "field": "p10" },
 | 
			
		||||
          "y2": { "scale": "yscale", "field": "p90" },
 | 
			
		||||
          "opacity": { "value": 0.15 }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "type": "area",
 | 
			
		||||
      "from": { "data": "table" },
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "enter": { "fill": { "value": "#4C78A8" } },
 | 
			
		||||
        "update": {
 | 
			
		||||
          "interpolate": { "value": "monotone" },
 | 
			
		||||
          "x": { "scale": "xscale", "field": "x" },
 | 
			
		||||
          "y": { "scale": "yscale", "field": "p20" },
 | 
			
		||||
          "y2": { "scale": "yscale", "field": "p80" },
 | 
			
		||||
          "opacity": { "value": 0.2 }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "type": "area",
 | 
			
		||||
      "from": { "data": "table" },
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "enter": { "fill": { "value": "#4C78A8" } },
 | 
			
		||||
        "update": {
 | 
			
		||||
          "interpolate": { "value": "monotone" },
 | 
			
		||||
          "x": { "scale": "xscale", "field": "x" },
 | 
			
		||||
          "y": { "scale": "yscale", "field": "p30" },
 | 
			
		||||
          "y2": { "scale": "yscale", "field": "p70" },
 | 
			
		||||
          "opacity": { "value": 0.2 }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "type": "area",
 | 
			
		||||
      "from": { "data": "table" },
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "enter": { "fill": { "value": "#4C78A8" } },
 | 
			
		||||
        "update": {
 | 
			
		||||
          "interpolate": { "value": "monotone" },
 | 
			
		||||
          "x": { "scale": "xscale", "field": "x" },
 | 
			
		||||
          "y": { "scale": "yscale", "field": "p40" },
 | 
			
		||||
          "y2": { "scale": "yscale", "field": "p60" },
 | 
			
		||||
          "opacity": { "value": 0.2 }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "type": "line",
 | 
			
		||||
      "from": { "data": "table" },
 | 
			
		||||
      "encode": {
 | 
			
		||||
        "update": {
 | 
			
		||||
          "interpolate": { "value": "monotone" },
 | 
			
		||||
          "stroke": { "value": "#4C78A8" },
 | 
			
		||||
          "strokeWidth": { "value": 2 },
 | 
			
		||||
          "opacity": { "value": 0.8 },
 | 
			
		||||
          "x": { "scale": "xscale", "field": "x" },
 | 
			
		||||
          "y": { "scale": "yscale", "field": "p50" }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,13 +3,21 @@ open Distributions;
 | 
			
		|||
type t = DistTypes.continuousShape;
 | 
			
		||||
let getShape = (t: t) => t.xyShape;
 | 
			
		||||
let interpolation = (t: t) => t.interpolation;
 | 
			
		||||
let make = (~interpolation=`Linear, ~integralSumCache=None, ~integralCache=None, xyShape): t => {
 | 
			
		||||
let make =
 | 
			
		||||
    (
 | 
			
		||||
      ~interpolation=`Linear,
 | 
			
		||||
      ~integralSumCache=None,
 | 
			
		||||
      ~integralCache=None,
 | 
			
		||||
      xyShape,
 | 
			
		||||
    )
 | 
			
		||||
    : t => {
 | 
			
		||||
  xyShape,
 | 
			
		||||
  interpolation,
 | 
			
		||||
  integralSumCache,
 | 
			
		||||
  integralCache,
 | 
			
		||||
};
 | 
			
		||||
let shapeMap = (fn, {xyShape, interpolation, integralSumCache, integralCache}: t): t => {
 | 
			
		||||
let shapeMap =
 | 
			
		||||
    (fn, {xyShape, interpolation, integralSumCache, integralCache}: t): t => {
 | 
			
		||||
  xyShape: fn(xyShape),
 | 
			
		||||
  interpolation,
 | 
			
		||||
  integralSumCache,
 | 
			
		||||
| 
						 | 
				
			
			@ -19,10 +27,14 @@ let lastY = (t: t) => t |> getShape |> XYShape.T.lastY;
 | 
			
		|||
let oShapeMap =
 | 
			
		||||
    (fn, {xyShape, interpolation, integralSumCache, integralCache}: t)
 | 
			
		||||
    : option(DistTypes.continuousShape) =>
 | 
			
		||||
  fn(xyShape) |> E.O.fmap(make(~interpolation, ~integralSumCache, ~integralCache));
 | 
			
		||||
  fn(xyShape)
 | 
			
		||||
  |> E.O.fmap(make(~interpolation, ~integralSumCache, ~integralCache));
 | 
			
		||||
 | 
			
		||||
let emptyIntegral: DistTypes.continuousShape = {
 | 
			
		||||
  xyShape: {xs: [|neg_infinity|], ys: [|0.0|]},
 | 
			
		||||
  xyShape: {
 | 
			
		||||
    xs: [|neg_infinity|],
 | 
			
		||||
    ys: [|0.0|],
 | 
			
		||||
  },
 | 
			
		||||
  interpolation: `Linear,
 | 
			
		||||
  integralSumCache: Some(0.0),
 | 
			
		||||
  integralCache: None,
 | 
			
		||||
| 
						 | 
				
			
			@ -35,14 +47,18 @@ let empty: DistTypes.continuousShape = {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
let stepwiseToLinear = (t: t): t =>
 | 
			
		||||
  make(~integralSumCache=t.integralSumCache, ~integralCache=t.integralCache, XYShape.Range.stepwiseToLinear(t.xyShape));
 | 
			
		||||
  make(
 | 
			
		||||
    ~integralSumCache=t.integralSumCache,
 | 
			
		||||
    ~integralCache=t.integralCache,
 | 
			
		||||
    XYShape.Range.stepwiseToLinear(t.xyShape),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
// Note: This results in a distribution with as many points as the sum of those in t1 and t2.
 | 
			
		||||
let combinePointwise =
 | 
			
		||||
    (
 | 
			
		||||
      ~integralSumCachesFn=(_, _) => None,
 | 
			
		||||
      ~integralCachesFn: (t, t) => option(t) =(_, _) => None,
 | 
			
		||||
      ~distributionType: DistTypes.distributionType = `PDF,
 | 
			
		||||
      ~integralCachesFn: (t, t) => option(t)=(_, _) => None,
 | 
			
		||||
      ~distributionType: DistTypes.distributionType=`PDF,
 | 
			
		||||
      fn: (float, float) => float,
 | 
			
		||||
      t1: DistTypes.continuousShape,
 | 
			
		||||
      t2: DistTypes.continuousShape,
 | 
			
		||||
| 
						 | 
				
			
			@ -62,19 +78,22 @@ let combinePointwise =
 | 
			
		|||
 | 
			
		||||
  // If combining stepwise and linear, we must convert the stepwise to linear first,
 | 
			
		||||
  // i.e. add a point at the bottom of each step
 | 
			
		||||
  let (t1, t2) = switch (t1.interpolation, t2.interpolation) {
 | 
			
		||||
  | (`Linear, `Linear) => (t1, t2);
 | 
			
		||||
  | (`Stepwise, `Stepwise) => (t1, t2);
 | 
			
		||||
  | (`Linear, `Stepwise) => (t1, stepwiseToLinear(t2));
 | 
			
		||||
  | (`Stepwise, `Linear) => (stepwiseToLinear(t1), t2);
 | 
			
		||||
  };
 | 
			
		||||
  let (t1, t2) =
 | 
			
		||||
    switch (t1.interpolation, t2.interpolation) {
 | 
			
		||||
    | (`Linear, `Linear) => (t1, t2)
 | 
			
		||||
    | (`Stepwise, `Stepwise) => (t1, t2)
 | 
			
		||||
    | (`Linear, `Stepwise) => (t1, stepwiseToLinear(t2))
 | 
			
		||||
    | (`Stepwise, `Linear) => (stepwiseToLinear(t1), t2)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  let extrapolation = switch (distributionType) {
 | 
			
		||||
  | `PDF => `UseZero
 | 
			
		||||
  | `CDF => `UseOutermostPoints
 | 
			
		||||
  };
 | 
			
		||||
  let extrapolation =
 | 
			
		||||
    switch (distributionType) {
 | 
			
		||||
    | `PDF => `UseZero
 | 
			
		||||
    | `CDF => `UseOutermostPoints
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  let interpolator = XYShape.XtoY.continuousInterpolator(t1.interpolation, extrapolation);
 | 
			
		||||
  let interpolator =
 | 
			
		||||
    XYShape.XtoY.continuousInterpolator(t1.interpolation, extrapolation);
 | 
			
		||||
 | 
			
		||||
  make(
 | 
			
		||||
    ~integralSumCache=combinedIntegralSum,
 | 
			
		||||
| 
						 | 
				
			
			@ -103,10 +122,7 @@ let updateIntegralSumCache = (integralSumCache, t: t): t => {
 | 
			
		|||
  integralSumCache,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let updateIntegralCache = (integralCache, t: t): t => {
 | 
			
		||||
  ...t,
 | 
			
		||||
  integralCache,
 | 
			
		||||
};
 | 
			
		||||
let updateIntegralCache = (integralCache, t: t): t => {...t, integralCache};
 | 
			
		||||
 | 
			
		||||
let reduce =
 | 
			
		||||
    (
 | 
			
		||||
| 
						 | 
				
			
			@ -116,11 +132,13 @@ let reduce =
 | 
			
		|||
      continuousShapes,
 | 
			
		||||
    ) =>
 | 
			
		||||
  continuousShapes
 | 
			
		||||
  |> E.A.fold_left(combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn), empty);
 | 
			
		||||
  |> E.A.fold_left(
 | 
			
		||||
       combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn),
 | 
			
		||||
       empty,
 | 
			
		||||
     );
 | 
			
		||||
 | 
			
		||||
let mapY = (~integralSumCacheFn=_ => None,
 | 
			
		||||
            ~integralCacheFn=_ => None,
 | 
			
		||||
            ~fn, t: t) => {
 | 
			
		||||
let mapY =
 | 
			
		||||
    (~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn, t: t) => {
 | 
			
		||||
  make(
 | 
			
		||||
    ~interpolation=t.interpolation,
 | 
			
		||||
    ~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn),
 | 
			
		||||
| 
						 | 
				
			
			@ -130,13 +148,15 @@ let mapY = (~integralSumCacheFn=_ => None,
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
let rec scaleBy = (~scale=1.0, t: t): t => {
 | 
			
		||||
  let scaledIntegralSumCache = E.O.bind(t.integralSumCache, v => Some(scale *. v));
 | 
			
		||||
  let scaledIntegralCache = E.O.bind(t.integralCache, v => Some(scaleBy(~scale, v)));
 | 
			
		||||
  let scaledIntegralSumCache =
 | 
			
		||||
    E.O.bind(t.integralSumCache, v => Some(scale *. v));
 | 
			
		||||
  let scaledIntegralCache =
 | 
			
		||||
    E.O.bind(t.integralCache, v => Some(scaleBy(~scale, v)));
 | 
			
		||||
 | 
			
		||||
  t
 | 
			
		||||
  |> mapY(~fn=(r: float) => r *. scale)
 | 
			
		||||
  |> updateIntegralSumCache(scaledIntegralSumCache)
 | 
			
		||||
  |> updateIntegralCache(scaledIntegralCache)
 | 
			
		||||
  |> updateIntegralCache(scaledIntegralCache);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module T =
 | 
			
		||||
| 
						 | 
				
			
			@ -171,20 +191,22 @@ module T =
 | 
			
		|||
        |> XYShape.Zipped.filterByX(x => x >= lc && x <= rc);
 | 
			
		||||
 | 
			
		||||
      let leftNewPoint =
 | 
			
		||||
        leftCutoff |> E.O.dimap(lc => [|(lc -. epsilon_float, 0.)|], _ => [||]);
 | 
			
		||||
        leftCutoff
 | 
			
		||||
        |> E.O.dimap(lc => [|(lc -. epsilon_float, 0.)|], _ => [||]);
 | 
			
		||||
      let rightNewPoint =
 | 
			
		||||
        rightCutoff |> E.O.dimap(rc => [|(rc +. epsilon_float, 0.)|], _ => [||]);
 | 
			
		||||
        rightCutoff
 | 
			
		||||
        |> E.O.dimap(rc => [|(rc +. epsilon_float, 0.)|], _ => [||]);
 | 
			
		||||
 | 
			
		||||
      let truncatedZippedPairsWithNewPoints =
 | 
			
		||||
        E.A.concatMany([|leftNewPoint, truncatedZippedPairs, rightNewPoint|]);
 | 
			
		||||
      let truncatedShape =
 | 
			
		||||
        XYShape.T.fromZippedArray(truncatedZippedPairsWithNewPoints);
 | 
			
		||||
 | 
			
		||||
      make(truncatedShape)
 | 
			
		||||
      make(truncatedShape);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // TODO: This should work with stepwise plots.
 | 
			
		||||
    let integral = (t) =>
 | 
			
		||||
    let integral = t =>
 | 
			
		||||
      switch (getShape(t) |> XYShape.T.isEmpty, t.integralCache) {
 | 
			
		||||
      | (true, _) => emptyIntegral
 | 
			
		||||
      | (false, Some(cache)) => cache
 | 
			
		||||
| 
						 | 
				
			
			@ -253,22 +275,37 @@ let combineAlgebraicallyWithDiscrete =
 | 
			
		|||
  if (XYShape.T.isEmpty(t1s) || XYShape.T.isEmpty(t2s)) {
 | 
			
		||||
    empty;
 | 
			
		||||
  } else {
 | 
			
		||||
    let continuousAsLinear = switch (t1.interpolation) {
 | 
			
		||||
    | `Linear => t1;
 | 
			
		||||
    | `Stepwise => stepwiseToLinear(t1)
 | 
			
		||||
    };
 | 
			
		||||
    let continuousAsLinear =
 | 
			
		||||
      switch (t1.interpolation) {
 | 
			
		||||
      | `Linear => t1
 | 
			
		||||
      | `Stepwise => stepwiseToLinear(t1)
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
    let combinedShape = AlgebraicShapeCombination.combineShapesContinuousDiscrete(op, continuousAsLinear |> getShape, t2s);
 | 
			
		||||
 | 
			
		||||
    let combinedIntegralSum =
 | 
			
		||||
      Common.combineIntegralSums(
 | 
			
		||||
        (a, b) => Some(a *. b),
 | 
			
		||||
        t1.integralSumCache,
 | 
			
		||||
        t2.integralSumCache,
 | 
			
		||||
    let combinedShape =
 | 
			
		||||
      AlgebraicShapeCombination.combineShapesContinuousDiscrete(
 | 
			
		||||
        op,
 | 
			
		||||
        continuousAsLinear |> getShape,
 | 
			
		||||
        t2s,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    let combinedIntegralSum =
 | 
			
		||||
      switch (op) {
 | 
			
		||||
      | `Multiply
 | 
			
		||||
      | `Divide =>
 | 
			
		||||
        Common.combineIntegralSums(
 | 
			
		||||
          (a, b) => Some(a *. b),
 | 
			
		||||
          t1.integralSumCache,
 | 
			
		||||
          t2.integralSumCache,
 | 
			
		||||
        )
 | 
			
		||||
      | _ => None
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
    // TODO: It could make sense to automatically transform the integrals here (shift or scale)
 | 
			
		||||
    make(~interpolation=t1.interpolation, ~integralSumCache=combinedIntegralSum, combinedShape)
 | 
			
		||||
    make(
 | 
			
		||||
      ~interpolation=t1.interpolation,
 | 
			
		||||
      ~integralSumCache=combinedIntegralSum,
 | 
			
		||||
      combinedShape,
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,7 @@ let toMixed =
 | 
			
		|||
 | 
			
		||||
let combineAlgebraically =
 | 
			
		||||
    (op: ExpressionTypes.algebraicOperation, t1: t, t2: t): t => {
 | 
			
		||||
      
 | 
			
		||||
  switch (t1, t2) {
 | 
			
		||||
  | (Continuous(m1), Continuous(m2)) =>
 | 
			
		||||
    Continuous.combineAlgebraically(op, m1, m2) |> Continuous.T.toShape;
 | 
			
		||||
| 
						 | 
				
			
			@ -171,12 +172,13 @@ module T =
 | 
			
		|||
      ));
 | 
			
		||||
    };
 | 
			
		||||
    let maxX = mapToAll((Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
 | 
			
		||||
    let mapY = (~integralSumCacheFn=previousIntegralSum => None, ~integralCacheFn=previousIntegral=>None, ~fn) =>
 | 
			
		||||
    let mapY = (~integralSumCacheFn=previousIntegralSum => None, ~integralCacheFn=previousIntegral=>None, ~fn) =>{
 | 
			
		||||
      fmap((
 | 
			
		||||
        Mixed.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn),
 | 
			
		||||
        Discrete.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn),
 | 
			
		||||
        Continuous.T.mapY(~integralSumCacheFn, ~integralCacheFn, ~fn),
 | 
			
		||||
      ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mean = (t: t): float =>
 | 
			
		||||
      switch (t) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,27 +1,21 @@
 | 
			
		|||
open ExpressionTypes.ExpressionTree;
 | 
			
		||||
 | 
			
		||||
let toLeaf = (samplingInputs, environment, node: node) => {
 | 
			
		||||
  node
 | 
			
		||||
  |> ExpressionTreeEvaluator.toLeaf({
 | 
			
		||||
       samplingInputs,
 | 
			
		||||
       environment,
 | 
			
		||||
       evaluateNode: ExpressionTreeEvaluator.toLeaf,
 | 
			
		||||
     });
 | 
			
		||||
let toString = ExpressionTreeBasic.toString;
 | 
			
		||||
let envs = (samplingInputs, environment) => {
 | 
			
		||||
  {samplingInputs, environment, evaluateNode: ExpressionTreeEvaluator.toLeaf};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let toLeaf = (samplingInputs, environment, node: node) =>
 | 
			
		||||
  ExpressionTreeEvaluator.toLeaf(envs(samplingInputs, environment), node);
 | 
			
		||||
let toShape = (samplingInputs, environment, node: node) => {
 | 
			
		||||
  let renderResult =
 | 
			
		||||
    `Render(`Normalize(node)) |> toLeaf(samplingInputs, environment);
 | 
			
		||||
 | 
			
		||||
  switch (renderResult) {
 | 
			
		||||
  switch (toLeaf(samplingInputs, environment, node)) {
 | 
			
		||||
  | Ok(`RenderedDist(shape)) => Ok(shape)
 | 
			
		||||
  | Ok(_) => Error("Rendering failed.")
 | 
			
		||||
  | Error(e) => Error(e)
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let rec toString =
 | 
			
		||||
  fun
 | 
			
		||||
  | `SymbolicDist(d) => SymbolicDist.T.toString(d)
 | 
			
		||||
  | `RenderedDist(_) => "[shape]"
 | 
			
		||||
  | op => Operation.T.toString(toString, op);
 | 
			
		||||
let runFunction = (samplingInputs, environment, inputs, fn: PTypes.Function.t) => {
 | 
			
		||||
  let params = envs(samplingInputs, environment);
 | 
			
		||||
  PTypes.Function.run(params, inputs, fn);
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										35
									
								
								src/distPlus/expressionTree/ExpressionTreeBasic.re
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/distPlus/expressionTree/ExpressionTreeBasic.re
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
open ExpressionTypes.ExpressionTree;
 | 
			
		||||
 | 
			
		||||
let rec toString: node => string =
 | 
			
		||||
  fun
 | 
			
		||||
  | `SymbolicDist(d) => SymbolicDist.T.toString(d)
 | 
			
		||||
  | `RenderedDist(_) => "[renderedShape]"
 | 
			
		||||
  | `AlgebraicCombination(op, t1, t2) =>
 | 
			
		||||
    Operation.Algebraic.format(op, toString(t1), toString(t2))
 | 
			
		||||
  | `PointwiseCombination(op, t1, t2) =>
 | 
			
		||||
    Operation.Pointwise.format(op, toString(t1), toString(t2))
 | 
			
		||||
  | `Normalize(t) => "normalize(k" ++ toString(t) ++ ")"
 | 
			
		||||
  | `Truncate(lc, rc, t) =>
 | 
			
		||||
    Operation.T.truncateToString(lc, rc, toString(t))
 | 
			
		||||
  | `Render(t) => toString(t)
 | 
			
		||||
  | `Symbol(t) => "Symbol: " ++ t
 | 
			
		||||
  | `FunctionCall(name, args) =>
 | 
			
		||||
    "[Function call: ("
 | 
			
		||||
    ++ name
 | 
			
		||||
    ++ (args |> E.A.fmap(toString) |> Js.String.concatMany(_, ","))
 | 
			
		||||
    ++ ")]"
 | 
			
		||||
  | `Function(args, internal) =>
 | 
			
		||||
    "[Function: ("
 | 
			
		||||
    ++ (args |> Js.String.concatMany(_, ","))
 | 
			
		||||
    ++ toString(internal)
 | 
			
		||||
    ++ ")]"
 | 
			
		||||
  | `Array(a) =>
 | 
			
		||||
    "[" ++ (a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]"
 | 
			
		||||
  | `Hash(h) =>
 | 
			
		||||
    "{"
 | 
			
		||||
    ++ (
 | 
			
		||||
      h
 | 
			
		||||
      |> E.A.fmap(((name, value)) => name ++ ":" ++ toString(value))
 | 
			
		||||
      |> Js.String.concatMany(_, ",")
 | 
			
		||||
    )
 | 
			
		||||
    ++ "}";
 | 
			
		||||
| 
						 | 
				
			
			@ -49,11 +49,11 @@ module AlgebraicCombination = {
 | 
			
		|||
      (evaluationParams, algebraicOp, t1: node, t2: node)
 | 
			
		||||
      : result(node, string) => {
 | 
			
		||||
    E.R.merge(
 | 
			
		||||
      SamplingDistribution.renderIfIsNotSamplingDistribution(
 | 
			
		||||
      PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(
 | 
			
		||||
        evaluationParams,
 | 
			
		||||
        t1,
 | 
			
		||||
      ),
 | 
			
		||||
      SamplingDistribution.renderIfIsNotSamplingDistribution(
 | 
			
		||||
      PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(
 | 
			
		||||
        evaluationParams,
 | 
			
		||||
        t2,
 | 
			
		||||
      ),
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +61,7 @@ module AlgebraicCombination = {
 | 
			
		|||
    |> E.R.bind(_, ((a, b)) =>
 | 
			
		||||
         switch (choose(a, b)) {
 | 
			
		||||
         | `Sampling =>
 | 
			
		||||
           SamplingDistribution.combineShapesUsingSampling(
 | 
			
		||||
           PTypes.SamplingDistribution.combineShapesUsingSampling(
 | 
			
		||||
             evaluationParams,
 | 
			
		||||
             algebraicOp,
 | 
			
		||||
             a,
 | 
			
		||||
| 
						 | 
				
			
			@ -91,33 +91,6 @@ module AlgebraicCombination = {
 | 
			
		|||
       );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module VerticalScaling = {
 | 
			
		||||
  let operationToLeaf =
 | 
			
		||||
      (evaluationParams: evaluationParams, scaleOp, t, scaleBy) => {
 | 
			
		||||
    // scaleBy has to be a single float, otherwise we'll return an error.
 | 
			
		||||
    let fn = Operation.Scale.toFn(scaleOp);
 | 
			
		||||
    let integralSumCacheFn = Operation.Scale.toIntegralSumCacheFn(scaleOp);
 | 
			
		||||
    let integralCacheFn = Operation.Scale.toIntegralCacheFn(scaleOp);
 | 
			
		||||
    let renderedShape = Render.render(evaluationParams, t);
 | 
			
		||||
 | 
			
		||||
    switch (renderedShape, scaleBy) {
 | 
			
		||||
    | (Ok(`RenderedDist(rs)), `SymbolicDist(`Float(sm))) =>
 | 
			
		||||
      Ok(
 | 
			
		||||
        `RenderedDist(
 | 
			
		||||
          Shape.T.mapY(
 | 
			
		||||
            ~integralSumCacheFn=integralSumCacheFn(sm),
 | 
			
		||||
            ~integralCacheFn=integralCacheFn(sm),
 | 
			
		||||
            ~fn=fn(sm),
 | 
			
		||||
            rs,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      )
 | 
			
		||||
    | (Error(e1), _) => Error(e1)
 | 
			
		||||
    | (_, _) => Error("Can only scale by float values.")
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module PointwiseCombination = {
 | 
			
		||||
  let pointwiseAdd = (evaluationParams: evaluationParams, t1: t, t2: t) => {
 | 
			
		||||
    switch (
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +124,8 @@ module PointwiseCombination = {
 | 
			
		|||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let pointwiseCombine = (fn, evaluationParams: evaluationParams, t1: t, t2: t) => {
 | 
			
		||||
  let pointwiseCombine =
 | 
			
		||||
      (fn, evaluationParams: evaluationParams, t1: t, t2: t) => {
 | 
			
		||||
    // TODO: construct a function that we can easily sample from, to construct
 | 
			
		||||
    // a RenderedDist. Use the xMin and xMax of the rendered shapes to tell the sampling function where to look.
 | 
			
		||||
    // TODO: This should work for symbolic distributions too!
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +134,7 @@ module PointwiseCombination = {
 | 
			
		|||
      Render.render(evaluationParams, t2),
 | 
			
		||||
    ) {
 | 
			
		||||
    | (Ok(`RenderedDist(rs1)), Ok(`RenderedDist(rs2))) =>
 | 
			
		||||
      Ok(`RenderedDist(Shape.combinePointwise(( *. ), rs1, rs2)))
 | 
			
		||||
      Ok(`RenderedDist(Shape.combinePointwise(fn, rs1, rs2)))
 | 
			
		||||
    | (Error(e1), _) => Error(e1)
 | 
			
		||||
    | (_, Error(e2)) => Error(e2)
 | 
			
		||||
    | _ => Error("Pointwise combination: rendering failed.")
 | 
			
		||||
| 
						 | 
				
			
			@ -176,8 +150,8 @@ module PointwiseCombination = {
 | 
			
		|||
      ) => {
 | 
			
		||||
    switch (pointwiseOp) {
 | 
			
		||||
    | `Add => pointwiseAdd(evaluationParams, t1, t2)
 | 
			
		||||
    | `Multiply => pointwiseCombine(( *. ),evaluationParams, t1, t2)
 | 
			
		||||
    | `Exponentiate => pointwiseCombine(( *. ),evaluationParams, t1, t2)
 | 
			
		||||
    | `Multiply => pointwiseCombine(( *. ), evaluationParams, t1, t2)
 | 
			
		||||
    | `Exponentiate => pointwiseCombine(( ** ), evaluationParams, t1, t2)
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -232,6 +206,7 @@ module Truncate = {
 | 
			
		|||
 | 
			
		||||
module Normalize = {
 | 
			
		||||
  let rec operationToLeaf = (evaluationParams, t: node): result(node, string) => {
 | 
			
		||||
    Js.log2("normalize", t);
 | 
			
		||||
    switch (t) {
 | 
			
		||||
    | `RenderedDist(s) => Ok(`RenderedDist(Shape.T.normalize(s)))
 | 
			
		||||
    | `SymbolicDist(_) => Ok(t)
 | 
			
		||||
| 
						 | 
				
			
			@ -240,36 +215,39 @@ module Normalize = {
 | 
			
		|||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module FloatFromDist = {
 | 
			
		||||
  let rec operationToLeaf =
 | 
			
		||||
          (evaluationParams, distToFloatOp: distToFloatOperation, t: node)
 | 
			
		||||
          : result(node, string) => {
 | 
			
		||||
    switch (t) {
 | 
			
		||||
    | `SymbolicDist(s) =>
 | 
			
		||||
      SymbolicDist.T.operate(distToFloatOp, s)
 | 
			
		||||
      |> E.R.bind(_, v => Ok(`SymbolicDist(`Float(v))))
 | 
			
		||||
    | `RenderedDist(rs) =>
 | 
			
		||||
      Shape.operate(distToFloatOp, rs)
 | 
			
		||||
      |> (v => Ok(`SymbolicDist(`Float(v))))
 | 
			
		||||
    | _ =>
 | 
			
		||||
      t
 | 
			
		||||
      |> evaluateAndRetry(evaluationParams, r =>
 | 
			
		||||
           operationToLeaf(r, distToFloatOp)
 | 
			
		||||
         )
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
module FunctionCall = {
 | 
			
		||||
  let _runHardcodedFunction = (name, evaluationParams, args) =>
 | 
			
		||||
    TypeSystem.Function.Ts.findByNameAndRun(
 | 
			
		||||
      HardcodedFunctions.all,
 | 
			
		||||
      name,
 | 
			
		||||
      evaluationParams,
 | 
			
		||||
      args,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
// TODO: This forces things to be floats
 | 
			
		||||
let callableFunction = (evaluationParams, name, args) => {
 | 
			
		||||
  let b =
 | 
			
		||||
  let _runLocalFunction = (name, evaluationParams: evaluationParams, args) => {
 | 
			
		||||
    Environment.getFunction(evaluationParams.environment, name)
 | 
			
		||||
    |> E.R.bind(_, ((argNames, fn)) =>
 | 
			
		||||
         PTypes.Function.run(evaluationParams, args, (argNames, fn))
 | 
			
		||||
       );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let _runWithEvaluatedInputs =
 | 
			
		||||
      (
 | 
			
		||||
        evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
        name,
 | 
			
		||||
        args: array(ExpressionTypes.ExpressionTree.node),
 | 
			
		||||
      ) => {
 | 
			
		||||
    _runHardcodedFunction(name, evaluationParams, args)
 | 
			
		||||
    |> E.O.default(_runLocalFunction(name, evaluationParams, args));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // TODO: This forces things to be floats
 | 
			
		||||
  let run = (evaluationParams, name, args) => {
 | 
			
		||||
    args
 | 
			
		||||
    |> E.A.fmap(a =>
 | 
			
		||||
         Render.render(evaluationParams, a)
 | 
			
		||||
         |> E.R.bind(_, Render.toFloat)
 | 
			
		||||
       )
 | 
			
		||||
    |> E.A.R.firstErrorOrOpen;
 | 
			
		||||
  b |> E.R.bind(_, Functions.fnn(evaluationParams, name));
 | 
			
		||||
    |> E.A.fmap(a => evaluationParams.evaluateNode(evaluationParams, a))
 | 
			
		||||
    |> E.A.R.firstErrorOrOpen
 | 
			
		||||
    |> E.R.bind(_, _runWithEvaluatedInputs(evaluationParams, name));
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Render = {
 | 
			
		||||
| 
						 | 
				
			
			@ -280,7 +258,10 @@ module Render = {
 | 
			
		|||
    | `SymbolicDist(d) =>
 | 
			
		||||
      Ok(
 | 
			
		||||
        `RenderedDist(
 | 
			
		||||
          SymbolicDist.T.toShape(evaluationParams.samplingInputs.shapeLength, d),
 | 
			
		||||
          SymbolicDist.T.toShape(
 | 
			
		||||
            evaluationParams.samplingInputs.shapeLength,
 | 
			
		||||
            d,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      )
 | 
			
		||||
    | `RenderedDist(_) as t => Ok(t) // already a rendered shape, we're done here
 | 
			
		||||
| 
						 | 
				
			
			@ -289,29 +270,28 @@ module Render = {
 | 
			
		|||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let run = (node, fnNode) => {
 | 
			
		||||
  switch (fnNode) {
 | 
			
		||||
  | `Function(r) => Ok(r(node))
 | 
			
		||||
  | _ => Error("Not a function")
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* This function recursively goes through the nodes of the parse tree,
 | 
			
		||||
   replacing each Operation node and its subtree with a Data node.
 | 
			
		||||
   Whenever possible, the replacement produces a new Symbolic Data node,
 | 
			
		||||
   but most often it will produce a RenderedDist.
 | 
			
		||||
   This function is used mainly to turn a parse tree into a single RenderedDist
 | 
			
		||||
   that can then be displayed to the user. */
 | 
			
		||||
let toLeaf =
 | 
			
		||||
    (
 | 
			
		||||
      evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
      node: t,
 | 
			
		||||
    )
 | 
			
		||||
    : result(t, string) => {
 | 
			
		||||
let rec toLeaf =
 | 
			
		||||
        (
 | 
			
		||||
          evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
          node: t,
 | 
			
		||||
        )
 | 
			
		||||
        : result(t, string) => {
 | 
			
		||||
  switch (node) {
 | 
			
		||||
  // Leaf nodes just stay leaf nodes
 | 
			
		||||
  | `SymbolicDist(_)
 | 
			
		||||
  | `Function(_)
 | 
			
		||||
  | `RenderedDist(_) => Ok(node)
 | 
			
		||||
  | `Array(args) =>
 | 
			
		||||
    args
 | 
			
		||||
    |> E.A.fmap(toLeaf(evaluationParams))
 | 
			
		||||
    |> E.A.R.firstErrorOrOpen
 | 
			
		||||
    |> E.R.fmap(r => `Array(r))
 | 
			
		||||
  // Operations nevaluationParamsd to be turned into leaves
 | 
			
		||||
  | `AlgebraicCombination(algebraicOp, t1, t2) =>
 | 
			
		||||
    AlgebraicCombination.operationToLeaf(
 | 
			
		||||
| 
						 | 
				
			
			@ -327,17 +307,26 @@ let toLeaf =
 | 
			
		|||
      t1,
 | 
			
		||||
      t2,
 | 
			
		||||
    )
 | 
			
		||||
  | `VerticalScaling(scaleOp, t, scaleBy) =>
 | 
			
		||||
    VerticalScaling.operationToLeaf(evaluationParams, scaleOp, t, scaleBy)
 | 
			
		||||
  | `Truncate(leftCutoff, rightCutoff, t) =>
 | 
			
		||||
    Truncate.operationToLeaf(evaluationParams, leftCutoff, rightCutoff, t)
 | 
			
		||||
  | `FloatFromDist(distToFloatOp, t) =>
 | 
			
		||||
    FloatFromDist.operationToLeaf(evaluationParams, distToFloatOp, t)
 | 
			
		||||
  | `Normalize(t) => Normalize.operationToLeaf(evaluationParams, t)
 | 
			
		||||
  | `Render(t) => Render.operationToLeaf(evaluationParams, t)
 | 
			
		||||
  | `Function(_) => Error("Function must be called with params")
 | 
			
		||||
  | `Symbol(r) => ExpressionTypes.ExpressionTree.Environment.get(evaluationParams.environment, r) |> E.O.toResult("Undeclared variable " ++ r)
 | 
			
		||||
  | `Hash(t) =>
 | 
			
		||||
    t
 | 
			
		||||
    |> E.A.fmap(((name: string, node: node)) =>
 | 
			
		||||
         toLeaf(evaluationParams, node) |> E.R.fmap(r => (name, r))
 | 
			
		||||
       )
 | 
			
		||||
    |> E.A.R.firstErrorOrOpen
 | 
			
		||||
    |> E.R.fmap(r => `Hash(r))
 | 
			
		||||
  | `Symbol(r) =>
 | 
			
		||||
    ExpressionTypes.ExpressionTree.Environment.get(
 | 
			
		||||
      evaluationParams.environment,
 | 
			
		||||
      r,
 | 
			
		||||
    )
 | 
			
		||||
    |> E.O.toResult("Undeclared variable " ++ r)
 | 
			
		||||
    |> E.R.bind(_, toLeaf(evaluationParams))
 | 
			
		||||
  | `FunctionCall(name, args) =>
 | 
			
		||||
    callableFunction(evaluationParams, name, args)
 | 
			
		||||
  };
 | 
			
		||||
    FunctionCall.run(evaluationParams, name, args)
 | 
			
		||||
    |> E.R.bind(_, toLeaf(evaluationParams))
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,10 @@
 | 
			
		|||
type algebraicOperation = [ | `Add | `Multiply | `Subtract | `Divide | `Exponentiate];
 | 
			
		||||
type algebraicOperation = [
 | 
			
		||||
  | `Add
 | 
			
		||||
  | `Multiply
 | 
			
		||||
  | `Subtract
 | 
			
		||||
  | `Divide
 | 
			
		||||
  | `Exponentiate
 | 
			
		||||
];
 | 
			
		||||
type pointwiseOperation = [ | `Add | `Multiply | `Exponentiate];
 | 
			
		||||
type scaleOperation = [ | `Multiply | `Exponentiate | `Log];
 | 
			
		||||
type distToFloatOperation = [
 | 
			
		||||
| 
						 | 
				
			
			@ -10,43 +16,95 @@ type distToFloatOperation = [
 | 
			
		|||
];
 | 
			
		||||
 | 
			
		||||
module ExpressionTree = {
 | 
			
		||||
  type node = [
 | 
			
		||||
  type hash = array((string, node))
 | 
			
		||||
  and node = [
 | 
			
		||||
    | `SymbolicDist(SymbolicTypes.symbolicDist)
 | 
			
		||||
    | `RenderedDist(DistTypes.shape)
 | 
			
		||||
    | `Symbol(string)
 | 
			
		||||
    | `Hash(hash)
 | 
			
		||||
    | `Array(array(node))
 | 
			
		||||
    | `Function(array(string), node)
 | 
			
		||||
    | `AlgebraicCombination(algebraicOperation, node, node)
 | 
			
		||||
    | `PointwiseCombination(pointwiseOperation, node, node)
 | 
			
		||||
    | `VerticalScaling(scaleOperation, node, node)
 | 
			
		||||
    | `Normalize(node)
 | 
			
		||||
    | `Render(node)
 | 
			
		||||
    | `Truncate(option(float), option(float), node)
 | 
			
		||||
    | `Normalize(node)
 | 
			
		||||
    | `FloatFromDist(distToFloatOperation, node)
 | 
			
		||||
    | `Function(array(string), node)
 | 
			
		||||
    | `FunctionCall(string, array(node))
 | 
			
		||||
    | `Symbol(string)
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  module Hash = {
 | 
			
		||||
    type t('a) = array((string, 'a));
 | 
			
		||||
    let getByName = (t: t('a), name) =>
 | 
			
		||||
      E.A.getBy(t, ((n, _)) => n == name) |> E.O.fmap(((_, r)) => r);
 | 
			
		||||
 | 
			
		||||
    let getByNameResult = (t: t('a), name) =>
 | 
			
		||||
      getByName(t, name) |> E.O.toResult(name ++ " expected and not found");
 | 
			
		||||
 | 
			
		||||
    let getByNames = (hash: t('a), names: array(string)) =>
 | 
			
		||||
      names |> E.A.fmap(name => (name, getByName(hash, name)));
 | 
			
		||||
  };
 | 
			
		||||
  // Have nil as option
 | 
			
		||||
  let getFloat = (node: node) =>
 | 
			
		||||
    node
 | 
			
		||||
    |> (
 | 
			
		||||
      fun
 | 
			
		||||
      | `RenderedDist(Discrete({xyShape: {xs: [|x|], ys: [|1.0|]}})) =>
 | 
			
		||||
        Some(x)
 | 
			
		||||
      | `SymbolicDist(`Float(x)) => Some(x)
 | 
			
		||||
      | _ => None
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  let toFloatIfNeeded = (node: node) =>
 | 
			
		||||
    switch (node |> getFloat) {
 | 
			
		||||
    | Some(float) => `SymbolicDist(`Float(float))
 | 
			
		||||
    | None => node
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  type samplingInputs = {
 | 
			
		||||
    sampleCount: int,
 | 
			
		||||
    outputXYPoints: int,
 | 
			
		||||
    kernelWidth: option(float),
 | 
			
		||||
    shapeLength: int
 | 
			
		||||
    shapeLength: int,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  module SamplingInputs = {
 | 
			
		||||
    type t = {
 | 
			
		||||
      sampleCount: option(int),
 | 
			
		||||
      outputXYPoints: option(int),
 | 
			
		||||
      kernelWidth: option(float),
 | 
			
		||||
      shapeLength: option(int),
 | 
			
		||||
    };
 | 
			
		||||
    let withDefaults = (t: t): samplingInputs => {
 | 
			
		||||
      sampleCount: t.sampleCount |> E.O.default(10000),
 | 
			
		||||
      outputXYPoints: t.outputXYPoints |> E.O.default(10000),
 | 
			
		||||
      kernelWidth: t.kernelWidth,
 | 
			
		||||
      shapeLength: t.shapeLength |> E.O.default(10000),
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type environment = Belt.Map.String.t(node);
 | 
			
		||||
 | 
			
		||||
  module Environment = {
 | 
			
		||||
    type t = environment
 | 
			
		||||
    type t = environment;
 | 
			
		||||
    module MS = Belt.Map.String;
 | 
			
		||||
    let fromArray = MS.fromArray
 | 
			
		||||
    let empty:t = [||]->fromArray;
 | 
			
		||||
    let mergeKeepSecond = (a:t,b:t) => MS.merge(a,b, (_,a,b) =>switch(a,b){
 | 
			
		||||
      | (_, Some(b)) => Some(b)
 | 
			
		||||
      | (Some(a), _) => Some(a)
 | 
			
		||||
      | _ => None
 | 
			
		||||
    })
 | 
			
		||||
    let update = (t,str, fn) => MS.update(t, str, fn)
 | 
			
		||||
    let get = (t:t,str) => MS.get(t, str)
 | 
			
		||||
  }
 | 
			
		||||
    let fromArray = MS.fromArray;
 | 
			
		||||
    let empty: t = [||]->fromArray;
 | 
			
		||||
    let mergeKeepSecond = (a: t, b: t) =>
 | 
			
		||||
      MS.merge(a, b, (_, a, b) =>
 | 
			
		||||
        switch (a, b) {
 | 
			
		||||
        | (_, Some(b)) => Some(b)
 | 
			
		||||
        | (Some(a), _) => Some(a)
 | 
			
		||||
        | _ => None
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    let update = (t, str, fn) => MS.update(t, str, fn);
 | 
			
		||||
    let get = (t: t, str) => MS.get(t, str);
 | 
			
		||||
    let getFunction = (t: t, str) =>
 | 
			
		||||
      switch (get(t, str)) {
 | 
			
		||||
      | Some(`Function(argNames, fn)) => Ok((argNames, fn))
 | 
			
		||||
      | _ => Error("Function " ++ str ++ " not found")
 | 
			
		||||
      };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  type evaluationParams = {
 | 
			
		||||
    samplingInputs,
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +124,7 @@ module ExpressionTree = {
 | 
			
		|||
    type t = node;
 | 
			
		||||
 | 
			
		||||
    let render = (evaluationParams: evaluationParams, r) =>
 | 
			
		||||
      `Render(r) |> evaluateNode(evaluationParams);
 | 
			
		||||
      `Render(r) |> evaluateNode(evaluationParams)
 | 
			
		||||
 | 
			
		||||
    let ensureIsRendered = (params, t) =>
 | 
			
		||||
      switch (t) {
 | 
			
		||||
| 
						 | 
				
			
			@ -114,6 +172,9 @@ type simplificationResult = [
 | 
			
		|||
];
 | 
			
		||||
 | 
			
		||||
module Program = {
 | 
			
		||||
  type statement = [ | `Assignment(string, ExpressionTree.node) | `Expression(ExpressionTree.node)];
 | 
			
		||||
  type statement = [
 | 
			
		||||
    | `Assignment(string, ExpressionTree.node)
 | 
			
		||||
    | `Expression(ExpressionTree.node)
 | 
			
		||||
  ];
 | 
			
		||||
  type program = array(statement);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,122 +0,0 @@
 | 
			
		|||
type node = ExpressionTypes.ExpressionTree.node;
 | 
			
		||||
 | 
			
		||||
let toOkSym = r => Ok(`SymbolicDist(r));
 | 
			
		||||
 | 
			
		||||
let twoFloats = (fn, n1: node, n2: node): result(node, string) =>
 | 
			
		||||
  switch (n1, n2) {
 | 
			
		||||
  | (`SymbolicDist(`Float(a)), `SymbolicDist(`Float(b))) => fn(a, b)
 | 
			
		||||
  | _ => Error("Variables have wrong type")
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
let threeFloats = (fn, n1: node, n2: node, n3: node): result(node, string) =>
 | 
			
		||||
  switch (n1, n2, n3) {
 | 
			
		||||
  | (
 | 
			
		||||
      `SymbolicDist(`Float(a)),
 | 
			
		||||
      `SymbolicDist(`Float(b)),
 | 
			
		||||
      `SymbolicDist(`Float(c)),
 | 
			
		||||
    ) =>
 | 
			
		||||
    fn(a, b, c)
 | 
			
		||||
  | _ => Error("Variables have wrong type")
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
let twoFloatsToOkSym = fn => twoFloats((f1, f2) => fn(f1, f2) |> toOkSym);
 | 
			
		||||
 | 
			
		||||
let threeFloats = fn => threeFloats((f1, f2, f3) => fn(f1, f2, f3));
 | 
			
		||||
 | 
			
		||||
let apply2 = (fn, args): result(node, string) =>
 | 
			
		||||
  switch (args) {
 | 
			
		||||
  | [|a, b|] => fn(a, b)
 | 
			
		||||
  | _ => Error("Needs 2 args")
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
let apply3 = (fn, args: array(node)): result(node, string) =>
 | 
			
		||||
  switch (args) {
 | 
			
		||||
  | [|a, b, c|] => fn(a, b, c)
 | 
			
		||||
  | _ => Error("Needs 3 args")
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
let to_: array(node) => result(node, string) =
 | 
			
		||||
  fun
 | 
			
		||||
  | [|`SymbolicDist(`Float(low)), `SymbolicDist(`Float(high))|]
 | 
			
		||||
      when low <= 0.0 && low < high => {
 | 
			
		||||
      Ok(`SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)));
 | 
			
		||||
    }
 | 
			
		||||
  | [|`SymbolicDist(`Float(low)), `SymbolicDist(`Float(high))|]
 | 
			
		||||
      when low < high => {
 | 
			
		||||
      Ok(`SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high)));
 | 
			
		||||
    }
 | 
			
		||||
  | [|`SymbolicDist(`Float(_)), `SymbolicDist(_)|] =>
 | 
			
		||||
    Error("Low value must be less than high value.")
 | 
			
		||||
  | _ => Error("Requires 2 variables");
 | 
			
		||||
 | 
			
		||||
let processCustomFn =
 | 
			
		||||
    (
 | 
			
		||||
      evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
      args: array(node),
 | 
			
		||||
      argNames: array(string),
 | 
			
		||||
      fnResult: node,
 | 
			
		||||
    ) =>
 | 
			
		||||
  if (E.A.length(args) == E.A.length(argNames)) {
 | 
			
		||||
    let newEnvironment =
 | 
			
		||||
      Belt.Array.zip(argNames, args)
 | 
			
		||||
      |> ExpressionTypes.ExpressionTree.Environment.fromArray;
 | 
			
		||||
    let newEvaluationParams: ExpressionTypes.ExpressionTree.evaluationParams = {
 | 
			
		||||
      samplingInputs: evaluationParams.samplingInputs,
 | 
			
		||||
      environment:
 | 
			
		||||
        ExpressionTypes.ExpressionTree.Environment.mergeKeepSecond(
 | 
			
		||||
          evaluationParams.environment,
 | 
			
		||||
          newEnvironment,
 | 
			
		||||
        ),
 | 
			
		||||
      evaluateNode: evaluationParams.evaluateNode,
 | 
			
		||||
    };
 | 
			
		||||
    evaluationParams.evaluateNode(newEvaluationParams, fnResult);
 | 
			
		||||
  } else {
 | 
			
		||||
    Error("Failure");
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
let fnn =
 | 
			
		||||
    (
 | 
			
		||||
      evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
      name,
 | 
			
		||||
      args: array(node),
 | 
			
		||||
    ) =>
 | 
			
		||||
  switch (
 | 
			
		||||
    name,
 | 
			
		||||
    ExpressionTypes.ExpressionTree.Environment.get(
 | 
			
		||||
      evaluationParams.environment,
 | 
			
		||||
      name,
 | 
			
		||||
    ),
 | 
			
		||||
  ) {
 | 
			
		||||
  | (_, Some(`Function(argNames, tt))) =>
 | 
			
		||||
    processCustomFn(evaluationParams, args, argNames, tt)
 | 
			
		||||
  | ("normal", _) =>
 | 
			
		||||
    apply2(twoFloatsToOkSym(SymbolicDist.Normal.make), args)
 | 
			
		||||
  | ("uniform", _) =>
 | 
			
		||||
    apply2(twoFloatsToOkSym(SymbolicDist.Uniform.make), args)
 | 
			
		||||
  | ("beta", _) => apply2(twoFloatsToOkSym(SymbolicDist.Beta.make), args)
 | 
			
		||||
  | ("cauchy", _) =>
 | 
			
		||||
    apply2(twoFloatsToOkSym(SymbolicDist.Cauchy.make), args)
 | 
			
		||||
  | ("lognormal", _) =>
 | 
			
		||||
    apply2(twoFloatsToOkSym(SymbolicDist.Lognormal.make), args)
 | 
			
		||||
  | ("lognormalFromMeanAndStdDev", _) =>
 | 
			
		||||
    apply2(twoFloatsToOkSym(SymbolicDist.Lognormal.fromMeanAndStdev), args)
 | 
			
		||||
  | ("exponential", _) =>
 | 
			
		||||
    switch (args) {
 | 
			
		||||
    | [|`SymbolicDist(`Float(a))|] =>
 | 
			
		||||
      Ok(`SymbolicDist(SymbolicDist.Exponential.make(a)))
 | 
			
		||||
    | _ => Error("Needs 3 valid arguments")
 | 
			
		||||
    }
 | 
			
		||||
  | ("triangular", _) =>
 | 
			
		||||
    switch (args) {
 | 
			
		||||
    | [|
 | 
			
		||||
        `SymbolicDist(`Float(a)),
 | 
			
		||||
        `SymbolicDist(`Float(b)),
 | 
			
		||||
        `SymbolicDist(`Float(c)),
 | 
			
		||||
      |] =>
 | 
			
		||||
      SymbolicDist.Triangular.make(a, b, c)
 | 
			
		||||
      |> E.R.fmap(r => `SymbolicDist(r))
 | 
			
		||||
    | _ => Error("Needs 3 valid arguments")
 | 
			
		||||
    }
 | 
			
		||||
  | ("to", _) => to_(args)
 | 
			
		||||
  | _ => Error("Function not found")
 | 
			
		||||
  };
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
[%%debugger.chrome]
 | 
			
		||||
module MathJsonToMathJsAdt = {
 | 
			
		||||
  type arg =
 | 
			
		||||
    | Symbol(string)
 | 
			
		||||
| 
						 | 
				
			
			@ -82,17 +81,14 @@ module MathAdtToDistDst = {
 | 
			
		|||
    Ok(`Symbol(sym));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // TODO: This only works on the top level, which needs to be refactored. Also, I think functions don't need to be done like this anymore.
 | 
			
		||||
  module MathAdtCleaner = {
 | 
			
		||||
    let transformWithSymbol = (f: float, s: string) =>
 | 
			
		||||
      switch (s) {
 | 
			
		||||
      | "K"
 | 
			
		||||
      | "k" => Some(f *. 1000.)
 | 
			
		||||
      | "M"
 | 
			
		||||
      | "m" => Some(f *. 1000000.)
 | 
			
		||||
      | "B"
 | 
			
		||||
      | "b" => Some(f *. 1000000000.)
 | 
			
		||||
      | "T"
 | 
			
		||||
      | "t" => Some(f *. 1000000000000.)
 | 
			
		||||
      | "K" => Some(f *. 1000.)
 | 
			
		||||
      | "M" => Some(f *. 1000000.)
 | 
			
		||||
      | "B" => Some(f *. 1000000000.)
 | 
			
		||||
      | "T" => Some(f *. 1000000000000.)
 | 
			
		||||
      | _ => None
 | 
			
		||||
      };
 | 
			
		||||
    let rec run =
 | 
			
		||||
| 
						 | 
				
			
			@ -127,9 +123,7 @@ module MathAdtToDistDst = {
 | 
			
		|||
        |> E.R.bind(_, nodeParser);
 | 
			
		||||
      switch (g("mean"), g("stdev"), g("mu"), g("sigma")) {
 | 
			
		||||
      | (Ok(mean), Ok(stdev), _, _) =>
 | 
			
		||||
        Ok(
 | 
			
		||||
          `FunctionCall(("lognormalFromMeanAndStdDev", [|mean, stdev|])),
 | 
			
		||||
        )
 | 
			
		||||
        Ok(`FunctionCall(("lognormalFromMeanAndStdDev", [|mean, stdev|])))
 | 
			
		||||
      | (_, _, Ok(mu), Ok(sigma)) =>
 | 
			
		||||
        Ok(`FunctionCall(("lognormal", [|mu, sigma|])))
 | 
			
		||||
      | _ =>
 | 
			
		||||
| 
						 | 
				
			
			@ -144,64 +138,35 @@ module MathAdtToDistDst = {
 | 
			
		|||
         )
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  let multiModal =
 | 
			
		||||
      (
 | 
			
		||||
        args: array(result(ExpressionTypes.ExpressionTree.node, string)),
 | 
			
		||||
        weights: option(array(float)),
 | 
			
		||||
      ) => {
 | 
			
		||||
    let weights = weights |> E.O.default([||]);
 | 
			
		||||
    let firstWithError = args |> Belt.Array.getBy(_, Belt.Result.isError);
 | 
			
		||||
    let withoutErrors = args |> E.A.fmap(E.R.toOption) |> E.A.O.concatSomes;
 | 
			
		||||
 | 
			
		||||
    switch (firstWithError) {
 | 
			
		||||
    | Some(Error(e)) => Error(e)
 | 
			
		||||
    | None when withoutErrors |> E.A.length == 0 =>
 | 
			
		||||
      Error("Multimodals need at least one input")
 | 
			
		||||
    | _ =>
 | 
			
		||||
      let components =
 | 
			
		||||
        withoutErrors
 | 
			
		||||
        |> E.A.fmapi((index, t) => {
 | 
			
		||||
             let w = weights |> E.A.get(_, index) |> E.O.default(1.0);
 | 
			
		||||
 | 
			
		||||
             `VerticalScaling((`Multiply, t, `SymbolicDist(`Float(w))));
 | 
			
		||||
           });
 | 
			
		||||
 | 
			
		||||
      let pointwiseSum =
 | 
			
		||||
        components
 | 
			
		||||
        |> Js.Array.sliceFrom(1)
 | 
			
		||||
        |> E.A.fold_left(
 | 
			
		||||
             (acc, x) => {`PointwiseCombination((`Add, acc, x))},
 | 
			
		||||
             E.A.unsafe_get(components, 0),
 | 
			
		||||
           );
 | 
			
		||||
 | 
			
		||||
      Ok(`Normalize(pointwiseSum));
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
          //  Error("Dotwise exponentiation needs two operands")
 | 
			
		||||
  //  Error("Dotwise exponentiation needs two operands")
 | 
			
		||||
  let operationParser =
 | 
			
		||||
      (
 | 
			
		||||
        name: string,
 | 
			
		||||
        args: result(array(ExpressionTypes.ExpressionTree.node), string),
 | 
			
		||||
      ):result(ExpressionTypes.ExpressionTree.node,string) => {
 | 
			
		||||
      )
 | 
			
		||||
      : result(ExpressionTypes.ExpressionTree.node, string) => {
 | 
			
		||||
    let toOkAlgebraic = r => Ok(`AlgebraicCombination(r));
 | 
			
		||||
    let toOkPointwise = r => Ok(`PointwiseCombination(r));
 | 
			
		||||
    let toOkTruncate = r => Ok(`Truncate(r));
 | 
			
		||||
    let toOkFloatFromDist = r => Ok(`FloatFromDist(r));
 | 
			
		||||
    args
 | 
			
		||||
    |> E.R.bind(_, args => {
 | 
			
		||||
         switch (name, args) {
 | 
			
		||||
         | ("add", [|l, r|]) => toOkAlgebraic((`Add, l, r))
 | 
			
		||||
         | ("add", _) => Error("Addition needs two operands")
 | 
			
		||||
         | ("unaryMinus", [|l|]) =>
 | 
			
		||||
           toOkAlgebraic((`Multiply, `SymbolicDist(`Float(-1.0)), l))
 | 
			
		||||
         | ("subtract", [|l, r|]) => toOkAlgebraic((`Subtract, l, r))
 | 
			
		||||
         | ("subtract", _) => Error("Subtraction needs two operands")
 | 
			
		||||
         | ("multiply", [|l, r|]) => toOkAlgebraic((`Multiply, l, r))
 | 
			
		||||
         | ("multiply", _) => Error("Multiplication needs two operands")
 | 
			
		||||
         | ("pow", [|l,r|]) => toOkAlgebraic((`Exponentiate, l, r))
 | 
			
		||||
         | ("pow", [|l, r|]) => toOkAlgebraic((`Exponentiate, l, r))
 | 
			
		||||
         | ("pow", _) => Error("Exponentiation needs two operands")
 | 
			
		||||
         | ("dotMultiply", [|l, r|]) => toOkPointwise((`Multiply, l, r))
 | 
			
		||||
         | ("dotMultiply", _) =>
 | 
			
		||||
           Error("Dotwise multiplication needs two operands")
 | 
			
		||||
         | ("dotPow", [|l, r|]) => toOkPointwise((`Exponentiate, l, r))
 | 
			
		||||
         | ("dotPow", _) =>
 | 
			
		||||
           Error("Dotwise exponentiation needs two operands")
 | 
			
		||||
         | ("rightLogShift", [|l, r|]) => toOkPointwise((`Add, l, r))
 | 
			
		||||
         | ("rightLogShift", _) =>
 | 
			
		||||
           Error("Dotwise addition needs two operands")
 | 
			
		||||
| 
						 | 
				
			
			@ -228,25 +193,41 @@ module MathAdtToDistDst = {
 | 
			
		|||
           Error(
 | 
			
		||||
             "truncate needs three arguments: the expression and both cutoffs",
 | 
			
		||||
           )
 | 
			
		||||
         | ("pdf", [|d, `SymbolicDist(`Float(v))|]) =>
 | 
			
		||||
           toOkFloatFromDist((`Pdf(v), d))
 | 
			
		||||
         | ("cdf", [|d, `SymbolicDist(`Float(v))|]) =>
 | 
			
		||||
           toOkFloatFromDist((`Cdf(v), d))
 | 
			
		||||
         | ("inv", [|d, `SymbolicDist(`Float(v))|]) =>
 | 
			
		||||
           toOkFloatFromDist((`Inv(v), d))
 | 
			
		||||
         | ("mean", [|d|]) => toOkFloatFromDist((`Mean, d))
 | 
			
		||||
         | ("sample", [|d|]) => toOkFloatFromDist((`Sample, d))
 | 
			
		||||
         | _ => Error("This type not currently supported")
 | 
			
		||||
         }
 | 
			
		||||
       });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let functionParser = (nodeParser, name, args) => {
 | 
			
		||||
  let functionParser =
 | 
			
		||||
      (
 | 
			
		||||
        nodeParser:
 | 
			
		||||
          MathJsonToMathJsAdt.arg =>
 | 
			
		||||
          Belt.Result.t(
 | 
			
		||||
            ProbExample.ExpressionTypes.ExpressionTree.node,
 | 
			
		||||
            string,
 | 
			
		||||
          ),
 | 
			
		||||
        name: string,
 | 
			
		||||
        args: array(MathJsonToMathJsAdt.arg),
 | 
			
		||||
      )
 | 
			
		||||
      : result(ExpressionTypes.ExpressionTree.node, string) => {
 | 
			
		||||
    let parseArray = ags =>
 | 
			
		||||
      ags |> E.A.fmap(nodeParser) |> E.A.R.firstErrorOrOpen;
 | 
			
		||||
    let parseArgs = () => parseArray(args);
 | 
			
		||||
    switch (name) {
 | 
			
		||||
    | "lognormal" => lognormal(args, parseArgs, nodeParser)
 | 
			
		||||
    | "multimodal"
 | 
			
		||||
    | "add"
 | 
			
		||||
    | "subtract"
 | 
			
		||||
    | "multiply"
 | 
			
		||||
    | "unaryMinus"
 | 
			
		||||
    | "dotMultiply"
 | 
			
		||||
    | "dotPow"
 | 
			
		||||
    | "rightLogShift"
 | 
			
		||||
    | "divide"
 | 
			
		||||
    | "pow"
 | 
			
		||||
    | "leftTruncate"
 | 
			
		||||
    | "rightTruncate"
 | 
			
		||||
    | "truncate" => operationParser(name, parseArgs())
 | 
			
		||||
    | "mm" =>
 | 
			
		||||
      let weights =
 | 
			
		||||
        args
 | 
			
		||||
| 
						 | 
				
			
			@ -254,39 +235,36 @@ module MathAdtToDistDst = {
 | 
			
		|||
        |> E.O.bind(
 | 
			
		||||
             _,
 | 
			
		||||
             fun
 | 
			
		||||
             | Array(values) => Some(values)
 | 
			
		||||
             | Array(values) => Some(parseArray(values))
 | 
			
		||||
             | _ => None,
 | 
			
		||||
           )
 | 
			
		||||
        |> E.O.fmap(o =>
 | 
			
		||||
             o
 | 
			
		||||
             |> E.A.fmap(
 | 
			
		||||
                  fun
 | 
			
		||||
                  | Value(r) => Some(r)
 | 
			
		||||
                  | _ => None,
 | 
			
		||||
                )
 | 
			
		||||
             |> E.A.O.concatSomes
 | 
			
		||||
           );
 | 
			
		||||
      let possibleDists =
 | 
			
		||||
        E.O.isSome(weights)
 | 
			
		||||
          ? Belt.Array.slice(args, ~offset=0, ~len=E.A.length(args) - 1)
 | 
			
		||||
          : args;
 | 
			
		||||
      let dists = possibleDists |> E.A.fmap(nodeParser);
 | 
			
		||||
      multiModal(dists, weights);
 | 
			
		||||
    | "add"
 | 
			
		||||
    | "subtract"
 | 
			
		||||
    | "multiply"
 | 
			
		||||
    | "dotMultiply"
 | 
			
		||||
    | "rightLogShift"
 | 
			
		||||
    | "divide"
 | 
			
		||||
    | "pow"
 | 
			
		||||
    | "leftTruncate"
 | 
			
		||||
    | "rightTruncate"
 | 
			
		||||
    | "truncate"
 | 
			
		||||
    | "mean"
 | 
			
		||||
    | "inv"
 | 
			
		||||
    | "sample"
 | 
			
		||||
    | "cdf"
 | 
			
		||||
    | "pdf" => operationParser(name, parseArgs())
 | 
			
		||||
      let dists = parseArray(possibleDists);
 | 
			
		||||
      switch (weights, dists) {
 | 
			
		||||
      | (Some(Error(r)), _) => Error(r)
 | 
			
		||||
      | (_, Error(r)) => Error(r)
 | 
			
		||||
      | (None, Ok(dists)) =>
 | 
			
		||||
        let hash: ExpressionTypes.ExpressionTree.node =
 | 
			
		||||
          `FunctionCall(("multimodal", [|`Hash(
 | 
			
		||||
            [|
 | 
			
		||||
            ("dists", `Array(dists)),
 | 
			
		||||
            ("weights", `Array([||]))
 | 
			
		||||
            |]
 | 
			
		||||
          )|]));
 | 
			
		||||
        Ok(hash);
 | 
			
		||||
      | (Some(Ok(weights)), Ok(dists)) =>
 | 
			
		||||
        let hash: ExpressionTypes.ExpressionTree.node =
 | 
			
		||||
          `FunctionCall(("multimodal", [|`Hash(
 | 
			
		||||
            [|
 | 
			
		||||
            ("dists", `Array(dists)),
 | 
			
		||||
            ("weights", `Array(weights))
 | 
			
		||||
            |]
 | 
			
		||||
          )|]));
 | 
			
		||||
        Ok(hash);
 | 
			
		||||
      };
 | 
			
		||||
    | name =>
 | 
			
		||||
      parseArgs()
 | 
			
		||||
      |> E.R.fmap((args: array(ExpressionTypes.ExpressionTree.node)) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -303,7 +281,7 @@ module MathAdtToDistDst = {
 | 
			
		|||
    | Symbol(sym) => Ok(`Symbol(sym))
 | 
			
		||||
    | Fn({name, args}) => functionParser(nodeParser, name, args)
 | 
			
		||||
    | _ => {
 | 
			
		||||
        Error("This type not currently supported")
 | 
			
		||||
        Error("This type not currently supported");
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
  // | FunctionAssignment({name, args, expression}) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -356,6 +334,7 @@ let fromString2 = str => {
 | 
			
		|||
       Inside of this function, MathAdtToDistDst is called whenever a distribution function is encountered.
 | 
			
		||||
     */
 | 
			
		||||
  let mathJsToJson = str |> pointwiseToRightLogShift |> Mathjs.parseMath;
 | 
			
		||||
 | 
			
		||||
  let mathJsParse =
 | 
			
		||||
    E.R.bind(mathJsToJson, r => {
 | 
			
		||||
      switch (MathJsonToMathJsAdt.run(r)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -365,6 +344,7 @@ let fromString2 = str => {
 | 
			
		|||
    });
 | 
			
		||||
 | 
			
		||||
  let value = E.R.bind(mathJsParse, MathAdtToDistDst.run);
 | 
			
		||||
  Js.log2(mathJsParse, value);
 | 
			
		||||
  value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,6 +33,7 @@ module Pointwise = {
 | 
			
		|||
  let toString =
 | 
			
		||||
    fun
 | 
			
		||||
    | `Add => "+"
 | 
			
		||||
    | `Exponentiate => "^"
 | 
			
		||||
    | `Multiply => "*";
 | 
			
		||||
 | 
			
		||||
  let format = (a, b, c) => b ++ " " ++ toString(a) ++ " " ++ c;
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +52,7 @@ module DistToFloat = {
 | 
			
		|||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Note that different logarithms don't really do anything.
 | 
			
		||||
module Scale = {
 | 
			
		||||
  type t = scaleOperation;
 | 
			
		||||
  let toFn =
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										143
									
								
								src/distPlus/expressionTree/PTypes.re
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/distPlus/expressionTree/PTypes.re
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,143 @@
 | 
			
		|||
open ExpressionTypes.ExpressionTree;
 | 
			
		||||
 | 
			
		||||
module Function = {
 | 
			
		||||
  type t = (array(string), node);
 | 
			
		||||
  let fromNode: node => option(t) =
 | 
			
		||||
    node =>
 | 
			
		||||
      switch (node) {
 | 
			
		||||
      | `Function(r) => Some(r)
 | 
			
		||||
      | _ => None
 | 
			
		||||
      };
 | 
			
		||||
  let argumentNames = ((a, _): t) => a;
 | 
			
		||||
  let internals = ((_, b): t) => b;
 | 
			
		||||
  let run =
 | 
			
		||||
      (
 | 
			
		||||
        evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
        args: array(node),
 | 
			
		||||
        t: t,
 | 
			
		||||
      ) =>
 | 
			
		||||
    if (E.A.length(args) == E.A.length(argumentNames(t))) {
 | 
			
		||||
      let newEnvironment =
 | 
			
		||||
        Belt.Array.zip(argumentNames(t), args)
 | 
			
		||||
        |> ExpressionTypes.ExpressionTree.Environment.fromArray;
 | 
			
		||||
      let newEvaluationParams: ExpressionTypes.ExpressionTree.evaluationParams = {
 | 
			
		||||
        samplingInputs: evaluationParams.samplingInputs,
 | 
			
		||||
        environment:
 | 
			
		||||
          ExpressionTypes.ExpressionTree.Environment.mergeKeepSecond(
 | 
			
		||||
            evaluationParams.environment,
 | 
			
		||||
            newEnvironment,
 | 
			
		||||
          ),
 | 
			
		||||
        evaluateNode: evaluationParams.evaluateNode,
 | 
			
		||||
      };
 | 
			
		||||
      evaluationParams.evaluateNode(newEvaluationParams, internals(t));
 | 
			
		||||
    } else {
 | 
			
		||||
      Error("Wrong number of variables");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Primative = {
 | 
			
		||||
  type t = [
 | 
			
		||||
    | `SymbolicDist(SymbolicTypes.symbolicDist)
 | 
			
		||||
    | `RenderedDist(DistTypes.shape)
 | 
			
		||||
    | `Function(array(string), node)
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  let isPrimative: node => bool =
 | 
			
		||||
    fun
 | 
			
		||||
    | `SymbolicDist(_)
 | 
			
		||||
    | `RenderedDist(_)
 | 
			
		||||
    | `Function(_) => true
 | 
			
		||||
    | _ => false;
 | 
			
		||||
 | 
			
		||||
  let fromNode: node => option(t) =
 | 
			
		||||
    fun
 | 
			
		||||
    | `SymbolicDist(_) as n
 | 
			
		||||
    | `RenderedDist(_) as n
 | 
			
		||||
    | `Function(_) as n => Some(n)
 | 
			
		||||
    | _ => None;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module SamplingDistribution = {
 | 
			
		||||
  type t = [
 | 
			
		||||
    | `SymbolicDist(SymbolicTypes.symbolicDist)
 | 
			
		||||
    | `RenderedDist(DistTypes.shape)
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  let isSamplingDistribution: node => bool =
 | 
			
		||||
    fun
 | 
			
		||||
    | `SymbolicDist(_) => true
 | 
			
		||||
    | `RenderedDist(_) => true
 | 
			
		||||
    | _ => false;
 | 
			
		||||
 | 
			
		||||
  let fromNode: node => result(t, string) =
 | 
			
		||||
    fun
 | 
			
		||||
    | `SymbolicDist(n) => Ok(`SymbolicDist(n))
 | 
			
		||||
    | `RenderedDist(n) => Ok(`RenderedDist(n))
 | 
			
		||||
    | _ => Error("Not valid type");
 | 
			
		||||
 | 
			
		||||
  let renderIfIsNotSamplingDistribution = (params, t): result(node, string) =>
 | 
			
		||||
    !isSamplingDistribution(t)
 | 
			
		||||
      ? switch (Render.render(params, t)) {
 | 
			
		||||
        | Ok(r) => Ok(r)
 | 
			
		||||
        | Error(e) => Error(e)
 | 
			
		||||
        }
 | 
			
		||||
      : Ok(t);
 | 
			
		||||
 | 
			
		||||
  let map = (~renderedDistFn, ~symbolicDistFn, node: node) =>
 | 
			
		||||
    node
 | 
			
		||||
    |> (
 | 
			
		||||
      fun
 | 
			
		||||
      | `RenderedDist(r) => Some(renderedDistFn(r))
 | 
			
		||||
      | `SymbolicDist(s) => Some(symbolicDistFn(s))
 | 
			
		||||
      | _ => None
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  let sampleN = n =>
 | 
			
		||||
    map(
 | 
			
		||||
      ~renderedDistFn=Shape.sampleNRendered(n),
 | 
			
		||||
      ~symbolicDistFn=SymbolicDist.T.sampleN(n),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  let getCombinationSamples = (n, algebraicOp, t1: node, t2: node) => {
 | 
			
		||||
    switch (sampleN(n, t1), sampleN(n, t2)) {
 | 
			
		||||
    | (Some(a), Some(b)) =>
 | 
			
		||||
      Some(
 | 
			
		||||
        Belt.Array.zip(a, b)
 | 
			
		||||
        |> E.A.fmap(((a, b)) => Operation.Algebraic.toFn(algebraicOp, a, b)),
 | 
			
		||||
      )
 | 
			
		||||
    | _ => None
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let combineShapesUsingSampling =
 | 
			
		||||
      (evaluationParams: evaluationParams, algebraicOp, t1: node, t2: node) => {
 | 
			
		||||
    let i1 = renderIfIsNotSamplingDistribution(evaluationParams, t1);
 | 
			
		||||
    let i2 = renderIfIsNotSamplingDistribution(evaluationParams, t2);
 | 
			
		||||
    E.R.merge(i1, i2)
 | 
			
		||||
    |> E.R.bind(
 | 
			
		||||
         _,
 | 
			
		||||
         ((a, b)) => {
 | 
			
		||||
           let samples =
 | 
			
		||||
             getCombinationSamples(
 | 
			
		||||
               evaluationParams.samplingInputs.sampleCount,
 | 
			
		||||
               algebraicOp,
 | 
			
		||||
               a,
 | 
			
		||||
               b,
 | 
			
		||||
             );
 | 
			
		||||
 | 
			
		||||
           //  todo: This bottom part should probably be somewhere else.
 | 
			
		||||
           let shape =
 | 
			
		||||
             samples
 | 
			
		||||
             |> E.O.fmap(
 | 
			
		||||
                  SamplesToShape.fromSamples(
 | 
			
		||||
                    ~samplingInputs=evaluationParams.samplingInputs,
 | 
			
		||||
                  ),
 | 
			
		||||
                )
 | 
			
		||||
             |> E.O.bind(_, r => r.shape)
 | 
			
		||||
             |> E.O.toResult("No response");
 | 
			
		||||
           shape |> E.R.fmap(r => `Normalize(`RenderedDist(r)));
 | 
			
		||||
         },
 | 
			
		||||
       );
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,72 +0,0 @@
 | 
			
		|||
open ExpressionTypes.ExpressionTree;
 | 
			
		||||
 | 
			
		||||
let isSamplingDistribution: node => bool =
 | 
			
		||||
  fun
 | 
			
		||||
  | `SymbolicDist(_) => true
 | 
			
		||||
  | `RenderedDist(_) => true
 | 
			
		||||
  | _ => false;
 | 
			
		||||
 | 
			
		||||
let renderIfIsNotSamplingDistribution = (params, t): result(node, string) =>
 | 
			
		||||
  !isSamplingDistribution(t)
 | 
			
		||||
    ? switch (Render.render(params, t)) {
 | 
			
		||||
      | Ok(r) => Ok(r)
 | 
			
		||||
      | Error(e) => Error(e)
 | 
			
		||||
      }
 | 
			
		||||
    : Ok(t);
 | 
			
		||||
 | 
			
		||||
let map = (~renderedDistFn, ~symbolicDistFn, node: node) =>
 | 
			
		||||
  node
 | 
			
		||||
  |> (
 | 
			
		||||
    fun
 | 
			
		||||
    | `RenderedDist(r) => Some(renderedDistFn(r))
 | 
			
		||||
    | `SymbolicDist(s) => Some(symbolicDistFn(s))
 | 
			
		||||
    | _ => None
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let sampleN = n =>
 | 
			
		||||
  map(
 | 
			
		||||
    ~renderedDistFn=Shape.sampleNRendered(n),
 | 
			
		||||
    ~symbolicDistFn=SymbolicDist.T.sampleN(n),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let getCombinationSamples = (n, algebraicOp, t1: node, t2: node) => {
 | 
			
		||||
  switch (sampleN(n, t1), sampleN(n, t2)) {
 | 
			
		||||
  | (Some(a), Some(b)) =>
 | 
			
		||||
    Some(
 | 
			
		||||
      Belt.Array.zip(a, b)
 | 
			
		||||
      |> E.A.fmap(((a, b)) => Operation.Algebraic.toFn(algebraicOp, a, b)),
 | 
			
		||||
    )
 | 
			
		||||
  | _ => None
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let combineShapesUsingSampling =
 | 
			
		||||
    (evaluationParams: evaluationParams, algebraicOp, t1: node, t2: node) => {
 | 
			
		||||
  let i1 = renderIfIsNotSamplingDistribution(evaluationParams, t1);
 | 
			
		||||
  let i2 = renderIfIsNotSamplingDistribution(evaluationParams, t2);
 | 
			
		||||
  E.R.merge(i1, i2)
 | 
			
		||||
  |> E.R.bind(
 | 
			
		||||
       _,
 | 
			
		||||
       ((a, b)) => {
 | 
			
		||||
         let samples =
 | 
			
		||||
           getCombinationSamples(
 | 
			
		||||
             evaluationParams.samplingInputs.sampleCount,
 | 
			
		||||
             algebraicOp,
 | 
			
		||||
             a,
 | 
			
		||||
             b,
 | 
			
		||||
           );
 | 
			
		||||
 | 
			
		||||
         //  todo: This bottom part should probably be somewhere else.
 | 
			
		||||
         let shape =
 | 
			
		||||
           samples
 | 
			
		||||
           |> E.O.fmap(
 | 
			
		||||
                SamplesToShape.fromSamples(
 | 
			
		||||
                  ~samplingInputs=evaluationParams.samplingInputs,
 | 
			
		||||
                ),
 | 
			
		||||
              )
 | 
			
		||||
           |> E.O.bind(_, r => r.shape)
 | 
			
		||||
           |> E.O.toResult("No response");
 | 
			
		||||
         shape |> E.R.fmap(r => `Normalize(`RenderedDist(r)));
 | 
			
		||||
       },
 | 
			
		||||
     );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ module Inputs = {
 | 
			
		|||
      shapeLength: option(int),
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
  let defaultRecommendedLength = 10000;
 | 
			
		||||
  let defaultRecommendedLength = 100;
 | 
			
		||||
  let defaultShouldDownsample = true;
 | 
			
		||||
 | 
			
		||||
  type ingredients = {
 | 
			
		||||
| 
						 | 
				
			
			@ -91,18 +91,16 @@ module Internals = {
 | 
			
		|||
  };
 | 
			
		||||
  let makeOutputs = (graph, shape): outputs => {graph, shape};
 | 
			
		||||
 | 
			
		||||
  let makeInputs = (inputs): ExpressionTypes.ExpressionTree.samplingInputs => {
 | 
			
		||||
    sampleCount: inputs.samplingInputs.sampleCount |> E.O.default(10000),
 | 
			
		||||
    outputXYPoints:
 | 
			
		||||
      inputs.samplingInputs.outputXYPoints |> E.O.default(10000),
 | 
			
		||||
    kernelWidth: inputs.samplingInputs.kernelWidth,
 | 
			
		||||
    shapeLength: inputs.samplingInputs.shapeLength |> E.O.default(10000),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let runNode = (inputs, node) => {
 | 
			
		||||
    ExpressionTree.toShape(
 | 
			
		||||
      {
 | 
			
		||||
        sampleCount: inputs.samplingInputs.sampleCount |> E.O.default(10000),
 | 
			
		||||
        outputXYPoints:
 | 
			
		||||
          inputs.samplingInputs.outputXYPoints |> E.O.default(10000),
 | 
			
		||||
        kernelWidth: inputs.samplingInputs.kernelWidth,
 | 
			
		||||
        shapeLength: inputs.samplingInputs.shapeLength |> E.O.default(10000),
 | 
			
		||||
      },
 | 
			
		||||
      inputs.environment,
 | 
			
		||||
      node,
 | 
			
		||||
    );
 | 
			
		||||
    ExpressionTree.toLeaf(makeInputs(inputs), inputs.environment, node);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let runProgram = (inputs: inputs, p: ExpressionTypes.Program.program) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -114,20 +112,19 @@ module Internals = {
 | 
			
		|||
             ins := addVariable(ins^, name, node);
 | 
			
		||||
             None;
 | 
			
		||||
           }
 | 
			
		||||
         | `Expression(node) => Some(runNode(ins^, node)),
 | 
			
		||||
         | `Expression(node) =>
 | 
			
		||||
           Some(
 | 
			
		||||
             runNode(ins^, node) |> E.R.fmap(r => (ins^.environment, r)),
 | 
			
		||||
           ),
 | 
			
		||||
       )
 | 
			
		||||
    |> E.A.O.concatSomes
 | 
			
		||||
    |> E.A.R.firstErrorOrOpen;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let inputsToShape = (inputs: inputs) => {
 | 
			
		||||
  let inputsToLeaf = (inputs: inputs) => {
 | 
			
		||||
    MathJsParser.fromString(inputs.guesstimatorString)
 | 
			
		||||
    |> E.R.bind(_, g => runProgram(inputs, g))
 | 
			
		||||
    |> E.R.bind(_, r =>
 | 
			
		||||
         E.A.last(r)
 | 
			
		||||
         |> E.O.toResult("No rendered lines")
 | 
			
		||||
         |> E.R.fmap(Shape.T.normalize)
 | 
			
		||||
       );
 | 
			
		||||
    |> E.R.bind(_, r => E.A.last(r) |> E.O.toResult("No rendered lines"));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let outputToDistPlus = (inputs: Inputs.inputs, shape: DistTypes.shape) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -141,9 +138,113 @@ module Internals = {
 | 
			
		|||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let renderIfNeeded =
 | 
			
		||||
    (inputs, node: ExpressionTypes.ExpressionTree.node)
 | 
			
		||||
    : result(ExpressionTypes.ExpressionTree.node, string) =>
 | 
			
		||||
  node
 | 
			
		||||
  |> (
 | 
			
		||||
    fun
 | 
			
		||||
    | `Normalize(_) as n
 | 
			
		||||
    | `SymbolicDist(_) as n => {
 | 
			
		||||
        `Render(n)
 | 
			
		||||
        |> Internals.runNode(Internals.distPlusRenderInputsToInputs(inputs))
 | 
			
		||||
        |> (
 | 
			
		||||
          fun
 | 
			
		||||
          | Ok(`RenderedDist(_)) as r => r
 | 
			
		||||
          | Error(r) => Error(r)
 | 
			
		||||
          | _ => Error("Didn't render, but intended to")
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    | n => Ok(n)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let run = (inputs: Inputs.inputs) => {
 | 
			
		||||
  inputs
 | 
			
		||||
  |> Internals.distPlusRenderInputsToInputs
 | 
			
		||||
  |> Internals.inputsToShape
 | 
			
		||||
  |> Internals.inputsToLeaf
 | 
			
		||||
  |> E.R.bind(_, ((lastIns, r)) =>
 | 
			
		||||
       r
 | 
			
		||||
       |> renderIfNeeded(inputs)
 | 
			
		||||
       |> (
 | 
			
		||||
         fun
 | 
			
		||||
         | Ok(`RenderedDist(n)) => Ok(n)
 | 
			
		||||
         | Ok(n) =>
 | 
			
		||||
           Error(
 | 
			
		||||
             "Didn't output a rendered distribution. Format:"
 | 
			
		||||
             ++ ExpressionTree.toString(n),
 | 
			
		||||
           )
 | 
			
		||||
         | Error(r) => Error(r)
 | 
			
		||||
       )
 | 
			
		||||
     )
 | 
			
		||||
  |> E.R.fmap(Internals.outputToDistPlus(inputs));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let exportDistPlus =
 | 
			
		||||
    (
 | 
			
		||||
      inputs,
 | 
			
		||||
      env: ProbExample.ExpressionTypes.ExpressionTree.environment,
 | 
			
		||||
      node: ExpressionTypes.ExpressionTree.node,
 | 
			
		||||
    ) =>
 | 
			
		||||
  node
 | 
			
		||||
  |> renderIfNeeded(inputs)
 | 
			
		||||
  |> E.R.bind(
 | 
			
		||||
       _,
 | 
			
		||||
       fun
 | 
			
		||||
       | `RenderedDist(Discrete({xyShape: {xs: [|x|], ys: [|1.0|]}})) =>
 | 
			
		||||
         Ok(`Float(x))
 | 
			
		||||
       | `SymbolicDist(`Float(x)) => Ok(`Float(x))
 | 
			
		||||
       | `RenderedDist(n) =>
 | 
			
		||||
         Ok(`DistPlus(Internals.outputToDistPlus(inputs, n)))
 | 
			
		||||
       | `Function(n) => Ok(`Function((n, env)))
 | 
			
		||||
       | n =>
 | 
			
		||||
         Error(
 | 
			
		||||
           "Didn't output a rendered distribution. Format:"
 | 
			
		||||
           ++ ExpressionTree.toString(n),
 | 
			
		||||
         ),
 | 
			
		||||
     );
 | 
			
		||||
 | 
			
		||||
// This isn't ok with floats, which can't be done in a function easily
 | 
			
		||||
let exportDistPlus2 =
 | 
			
		||||
    (
 | 
			
		||||
      inputs,
 | 
			
		||||
      env: ProbExample.ExpressionTypes.ExpressionTree.environment,
 | 
			
		||||
      node: ExpressionTypes.ExpressionTree.node,
 | 
			
		||||
    ) =>
 | 
			
		||||
  node
 | 
			
		||||
  |> renderIfNeeded(inputs)
 | 
			
		||||
  |> E.R.bind(
 | 
			
		||||
       _,
 | 
			
		||||
       fun
 | 
			
		||||
       | `RenderedDist(n) =>
 | 
			
		||||
         Ok(`DistPlus(Internals.outputToDistPlus(inputs, n)))
 | 
			
		||||
       | `Function(n) => Ok(`Function((n, env)))
 | 
			
		||||
       | n =>
 | 
			
		||||
         Error(
 | 
			
		||||
           "Didn't output a rendered distribution. Format:"
 | 
			
		||||
           ++ ExpressionTree.toString(n),
 | 
			
		||||
         ),
 | 
			
		||||
     );
 | 
			
		||||
 | 
			
		||||
let run2 = (inputs: Inputs.inputs) => {
 | 
			
		||||
  inputs
 | 
			
		||||
  |> Internals.distPlusRenderInputsToInputs
 | 
			
		||||
  |> Internals.inputsToLeaf
 | 
			
		||||
  |> E.R.bind(_, ((a, b)) => exportDistPlus(inputs, a, b));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let runFunction =
 | 
			
		||||
    (
 | 
			
		||||
      ins: Inputs.inputs,
 | 
			
		||||
      fn: (array(string), ExpressionTypes.ExpressionTree.node),
 | 
			
		||||
      fnInputs,
 | 
			
		||||
    ) => {
 | 
			
		||||
  let inputs = ins |> Internals.distPlusRenderInputsToInputs;
 | 
			
		||||
  let output =
 | 
			
		||||
    ExpressionTree.runFunction(
 | 
			
		||||
      Internals.makeInputs(inputs),
 | 
			
		||||
      inputs.environment,
 | 
			
		||||
      fnInputs,
 | 
			
		||||
      fn,
 | 
			
		||||
    );
 | 
			
		||||
  output |> E.R.bind(_, exportDistPlus2(ins, inputs.environment));
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
// Not yet used
 | 
			
		||||
type inputs = {
 | 
			
		||||
  samplingInputs: ExpressionTypes.ExpressionTree.SamplingInputs.t,
 | 
			
		||||
  program: string,
 | 
			
		||||
  environment: ExpressionTypes.ExpressionTree.environment,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let addVariable =
 | 
			
		||||
    ({program, samplingInputs, environment}: inputs, str, node): inputs => {
 | 
			
		||||
  samplingInputs,
 | 
			
		||||
  program,
 | 
			
		||||
  environment:
 | 
			
		||||
    ExpressionTypes.ExpressionTree.Environment.update(environment, str, _ =>
 | 
			
		||||
      Some(node)
 | 
			
		||||
    ),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let runNode = (inputs: inputs, node) => {
 | 
			
		||||
  ExpressionTree.toLeaf(
 | 
			
		||||
    ExpressionTypes.ExpressionTree.SamplingInputs.withDefaults(
 | 
			
		||||
      inputs.samplingInputs,
 | 
			
		||||
    ),
 | 
			
		||||
    inputs.environment,
 | 
			
		||||
    node,
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let runProgram = (inputs: inputs, p: ExpressionTypes.Program.program) => {
 | 
			
		||||
  let ins = ref(inputs);
 | 
			
		||||
  p
 | 
			
		||||
  |> E.A.fmap(
 | 
			
		||||
       fun
 | 
			
		||||
       | `Assignment(name, node) => {
 | 
			
		||||
           ins := addVariable(ins^, name, node);
 | 
			
		||||
           None;
 | 
			
		||||
         }
 | 
			
		||||
       | `Expression(node) =>
 | 
			
		||||
         Some(runNode(ins^, node) |> E.R.fmap(r => (ins, r))),
 | 
			
		||||
     )
 | 
			
		||||
  |> E.A.O.concatSomes
 | 
			
		||||
  |> E.A.R.firstErrorOrOpen;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let inputsToLeaf = (inputs: inputs) => {
 | 
			
		||||
  MathJsParser.fromString(inputs.program)
 | 
			
		||||
  |> E.R.bind(_, g => runProgram(inputs, g))
 | 
			
		||||
  |> E.R.bind(_, r => E.A.last(r) |> E.O.toResult("No rendered lines"));
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										258
									
								
								src/distPlus/typeSystem/HardcodedFunctions.re
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								src/distPlus/typeSystem/HardcodedFunctions.re
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,258 @@
 | 
			
		|||
open TypeSystem;
 | 
			
		||||
 | 
			
		||||
let wrongInputsError = (r: array(typedValue)) => {
 | 
			
		||||
  let inputs = r |> E.A.fmap(TypedValue.toString) |>Js.String.concatMany(_, ",");
 | 
			
		||||
  Js.log3("Inputs were", inputs, r);
 | 
			
		||||
  Error("Wrong inputs. The inputs were:" ++ inputs);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let to_: (float, float) => result(node, string) =
 | 
			
		||||
  (low, high) =>
 | 
			
		||||
    switch (low, high) {
 | 
			
		||||
    | (low, high) when low <= 0.0 && low < high =>
 | 
			
		||||
      Ok(`SymbolicDist(SymbolicDist.Normal.from90PercentCI(low, high)))
 | 
			
		||||
    | (low, high) when low < high =>
 | 
			
		||||
      Ok(`SymbolicDist(SymbolicDist.Lognormal.from90PercentCI(low, high)))
 | 
			
		||||
    | (_, _) => Error("Low value must be less than high value.")
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
let makeSymbolicFromTwoFloats = (name, fn) =>
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name,
 | 
			
		||||
    ~outputType=`SamplingDistribution,
 | 
			
		||||
    ~inputTypes=[|`Float, `Float|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`Float(a), `Float(b)|] => Ok(`SymbolicDist(fn(a, b)))
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let makeSymbolicFromOneFloat = (name, fn) =>
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name,
 | 
			
		||||
    ~outputType=`SamplingDistribution,
 | 
			
		||||
    ~inputTypes=[|`Float|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`Float(a)|] => Ok(`SymbolicDist(fn(a)))
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let makeDistFloat = (name, fn) =>
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name,
 | 
			
		||||
    ~outputType=`SamplingDistribution,
 | 
			
		||||
    ~inputTypes=[|`SamplingDistribution, `Float|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`SamplingDist(a), `Float(b)|] => fn(a, b)
 | 
			
		||||
      | [|`RenderedDist(a), `Float(b)|] => fn(`RenderedDist(a), b)
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let makeRenderedDistFloat = (name, fn) =>
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name,
 | 
			
		||||
    ~outputType=`RenderedDistribution,
 | 
			
		||||
    ~inputTypes=[|`RenderedDistribution, `Float|],
 | 
			
		||||
    ~shouldCoerceTypes=true,
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`RenderedDist(a), `Float(b)|] => fn(a, b)
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let makeDist = (name, fn) =>
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name,
 | 
			
		||||
    ~outputType=`SamplingDistribution,
 | 
			
		||||
    ~inputTypes=[|`SamplingDistribution|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`SamplingDist(a)|] => fn(a)
 | 
			
		||||
      | [|`RenderedDist(a)|] => fn(`RenderedDist(a))
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
let floatFromDist =
 | 
			
		||||
    (
 | 
			
		||||
      distToFloatOp: ExpressionTypes.distToFloatOperation,
 | 
			
		||||
      t: TypeSystem.samplingDist,
 | 
			
		||||
    )
 | 
			
		||||
    : result(node, string) => {
 | 
			
		||||
  switch (t) {
 | 
			
		||||
  | `SymbolicDist(s) =>
 | 
			
		||||
    SymbolicDist.T.operate(distToFloatOp, s)
 | 
			
		||||
    |> E.R.bind(_, v => Ok(`SymbolicDist(`Float(v))))
 | 
			
		||||
  | `RenderedDist(rs) =>
 | 
			
		||||
    Shape.operate(distToFloatOp, rs) |> (v => Ok(`SymbolicDist(`Float(v))))
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let verticalScaling = (scaleOp, rs, scaleBy) => {
 | 
			
		||||
  // scaleBy has to be a single float, otherwise we'll return an error.
 | 
			
		||||
  let fn = (secondary, main) =>
 | 
			
		||||
    Operation.Scale.toFn(scaleOp, main, secondary);
 | 
			
		||||
  let integralSumCacheFn = Operation.Scale.toIntegralSumCacheFn(scaleOp);
 | 
			
		||||
  let integralCacheFn = Operation.Scale.toIntegralCacheFn(scaleOp);
 | 
			
		||||
  Ok(
 | 
			
		||||
    `RenderedDist(
 | 
			
		||||
      Shape.T.mapY(
 | 
			
		||||
        ~integralSumCacheFn=integralSumCacheFn(scaleBy),
 | 
			
		||||
        ~integralCacheFn=integralCacheFn(scaleBy),
 | 
			
		||||
        ~fn=fn(scaleBy),
 | 
			
		||||
        rs,
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Multimodal = {
 | 
			
		||||
  let getByNameResult = ExpressionTypes.ExpressionTree.Hash.getByNameResult;
 | 
			
		||||
 | 
			
		||||
  let _paramsToDistsAndWeights = (r: array(typedValue)) =>
 | 
			
		||||
    switch (r) {
 | 
			
		||||
    | [|`Hash(r)|] =>
 | 
			
		||||
      let dists =
 | 
			
		||||
        getByNameResult(r, "dists")
 | 
			
		||||
        ->E.R.bind(TypeSystem.TypedValue.toArray)
 | 
			
		||||
        ->E.R.bind(r =>
 | 
			
		||||
            r
 | 
			
		||||
            |> E.A.fmap(TypeSystem.TypedValue.toDist)
 | 
			
		||||
            |> E.A.R.firstErrorOrOpen
 | 
			
		||||
          );
 | 
			
		||||
      let weights =
 | 
			
		||||
        getByNameResult(r, "weights")
 | 
			
		||||
        ->E.R.bind(TypeSystem.TypedValue.toArray)
 | 
			
		||||
        ->E.R.bind(r =>
 | 
			
		||||
            r
 | 
			
		||||
            |> E.A.fmap(TypeSystem.TypedValue.toFloat)
 | 
			
		||||
            |> E.A.R.firstErrorOrOpen
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
      E.R.merge(dists, weights)
 | 
			
		||||
      |> E.R.fmap(((a, b)) =>
 | 
			
		||||
           E.A.zipMaxLength(a, b)
 | 
			
		||||
           |> E.A.fmap(((a, b)) =>
 | 
			
		||||
                (a |> E.O.toExn(""), b |> E.O.default(1.0))
 | 
			
		||||
              )
 | 
			
		||||
         );
 | 
			
		||||
    | _ => Error("Needs items")
 | 
			
		||||
    };
 | 
			
		||||
  let _runner: array(typedValue) => result(node, string) =
 | 
			
		||||
    r => {
 | 
			
		||||
      let paramsToDistsAndWeights =
 | 
			
		||||
        _paramsToDistsAndWeights(r)
 | 
			
		||||
        |> E.R.fmap(
 | 
			
		||||
             E.A.fmap(((dist, weight)) =>
 | 
			
		||||
               `FunctionCall((
 | 
			
		||||
                 "scaleMultiply",
 | 
			
		||||
                 [|dist, `SymbolicDist(`Float(weight))|],
 | 
			
		||||
               ))
 | 
			
		||||
             ),
 | 
			
		||||
           );
 | 
			
		||||
      let pointwiseSum: result(node, string) =
 | 
			
		||||
        paramsToDistsAndWeights->E.R.bind(
 | 
			
		||||
          E.R.errorIfCondition(E.A.isEmpty, "Needs one input"),
 | 
			
		||||
        )
 | 
			
		||||
        |> E.R.fmap(r =>
 | 
			
		||||
             r
 | 
			
		||||
             |> Js.Array.sliceFrom(1)
 | 
			
		||||
             |> E.A.fold_left(
 | 
			
		||||
                  (acc, x) => {`PointwiseCombination((`Add, acc, x))},
 | 
			
		||||
                  E.A.unsafe_get(r, 0),
 | 
			
		||||
                )
 | 
			
		||||
           );
 | 
			
		||||
      pointwiseSum;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  let _function =
 | 
			
		||||
    Function.T.make(
 | 
			
		||||
      ~name="multimodal",
 | 
			
		||||
      ~outputType=`SamplingDistribution,
 | 
			
		||||
      ~inputTypes=[|
 | 
			
		||||
        `Hash([|
 | 
			
		||||
          ("dists", `Array(`SamplingDistribution)),
 | 
			
		||||
          ("weights", `Array(`Float)),
 | 
			
		||||
        |]),
 | 
			
		||||
      |],
 | 
			
		||||
      ~run=_runner,
 | 
			
		||||
      (),
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let all = [|
 | 
			
		||||
  makeSymbolicFromTwoFloats("normal", SymbolicDist.Normal.make),
 | 
			
		||||
  makeSymbolicFromTwoFloats("uniform", SymbolicDist.Uniform.make),
 | 
			
		||||
  makeSymbolicFromTwoFloats("beta", SymbolicDist.Beta.make),
 | 
			
		||||
  makeSymbolicFromTwoFloats("lognormal", SymbolicDist.Lognormal.make),
 | 
			
		||||
  makeSymbolicFromTwoFloats(
 | 
			
		||||
    "lognormalFromMeanAndStdDev",
 | 
			
		||||
    SymbolicDist.Lognormal.fromMeanAndStdev,
 | 
			
		||||
  ),
 | 
			
		||||
  makeSymbolicFromOneFloat("exponential", SymbolicDist.Exponential.make),
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name="to",
 | 
			
		||||
    ~outputType=`SamplingDistribution,
 | 
			
		||||
    ~inputTypes=[|`Float, `Float|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`Float(a), `Float(b)|] => to_(a, b)
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  ),
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name="triangular",
 | 
			
		||||
    ~outputType=`SamplingDistribution,
 | 
			
		||||
    ~inputTypes=[|`Float, `Float, `Float|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`Float(a), `Float(b), `Float(c)|] =>
 | 
			
		||||
        SymbolicDist.Triangular.make(a, b, c)
 | 
			
		||||
        |> E.R.fmap(r => `SymbolicDist(r))
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  ),
 | 
			
		||||
  makeDistFloat("pdf", (dist, float) => floatFromDist(`Pdf(float), dist)),
 | 
			
		||||
  makeDistFloat("inv", (dist, float) => floatFromDist(`Inv(float), dist)),
 | 
			
		||||
  makeDistFloat("cdf", (dist, float) => floatFromDist(`Cdf(float), dist)),
 | 
			
		||||
  makeDist("mean", dist => floatFromDist(`Mean, dist)),
 | 
			
		||||
  makeDist("sample", dist => floatFromDist(`Sample, dist)),
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name="render",
 | 
			
		||||
    ~outputType=`RenderedDistribution,
 | 
			
		||||
    ~inputTypes=[|`RenderedDistribution|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`RenderedDist(c)|] => Ok(`RenderedDist(c))
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  ),
 | 
			
		||||
  Function.T.make(
 | 
			
		||||
    ~name="normalize",
 | 
			
		||||
    ~outputType=`SamplingDistribution,
 | 
			
		||||
    ~inputTypes=[|`SamplingDistribution|],
 | 
			
		||||
    ~run=
 | 
			
		||||
      fun
 | 
			
		||||
      | [|`SamplingDist(`SymbolicDist(c))|] => Ok(`SymbolicDist(c))
 | 
			
		||||
      | [|`SamplingDist(`RenderedDist(c))|] =>
 | 
			
		||||
        Ok(`RenderedDist(Shape.T.normalize(c)))
 | 
			
		||||
      | e => wrongInputsError(e),
 | 
			
		||||
    (),
 | 
			
		||||
  ),
 | 
			
		||||
  makeRenderedDistFloat("scaleExp", (dist, float) =>
 | 
			
		||||
    verticalScaling(`Exponentiate, dist, float)
 | 
			
		||||
  ),
 | 
			
		||||
  makeRenderedDistFloat("scaleMultiply", (dist, float) =>
 | 
			
		||||
    verticalScaling(`Multiply, dist, float)
 | 
			
		||||
  ),
 | 
			
		||||
  makeRenderedDistFloat("scaleLog", (dist, float) =>
 | 
			
		||||
    verticalScaling(`Log, dist, float)
 | 
			
		||||
  ),
 | 
			
		||||
  Multimodal._function
 | 
			
		||||
|];
 | 
			
		||||
							
								
								
									
										228
									
								
								src/distPlus/typeSystem/TypeSystem.re
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								src/distPlus/typeSystem/TypeSystem.re
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,228 @@
 | 
			
		|||
type node = ExpressionTypes.ExpressionTree.node;
 | 
			
		||||
let getFloat = ExpressionTypes.ExpressionTree.getFloat;
 | 
			
		||||
 | 
			
		||||
type samplingDist = [
 | 
			
		||||
  | `SymbolicDist(SymbolicTypes.symbolicDist)
 | 
			
		||||
  | `RenderedDist(DistTypes.shape)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
type hashType = array((string, _type))
 | 
			
		||||
and _type = [
 | 
			
		||||
  | `Float
 | 
			
		||||
  | `SamplingDistribution
 | 
			
		||||
  | `RenderedDistribution
 | 
			
		||||
  | `Array(_type)
 | 
			
		||||
  | `Hash(hashType)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
type hashTypedValue = array((string, typedValue))
 | 
			
		||||
and typedValue = [
 | 
			
		||||
  | `Float(float)
 | 
			
		||||
  | `RenderedDist(DistTypes.shape)
 | 
			
		||||
  | `SamplingDist(samplingDist)
 | 
			
		||||
  | `Array(array(typedValue))
 | 
			
		||||
  | `Hash(hashTypedValue)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
type _function = {
 | 
			
		||||
  name: string,
 | 
			
		||||
  inputTypes: array(_type),
 | 
			
		||||
  outputType: _type,
 | 
			
		||||
  run: array(typedValue) => result(node, string),
 | 
			
		||||
  shouldCoerceTypes: bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type functions = array(_function);
 | 
			
		||||
type inputNodes = array(node);
 | 
			
		||||
 | 
			
		||||
module TypedValue = {
 | 
			
		||||
  let rec toString: typedValue => string =
 | 
			
		||||
    fun
 | 
			
		||||
    | `SamplingDist(_) => "[sampling dist]"
 | 
			
		||||
    | `RenderedDist(_) => "[rendered Shape]"
 | 
			
		||||
    | `Float(f) => "Float: " ++ Js.Float.toString(f)
 | 
			
		||||
    | `Array(a) =>
 | 
			
		||||
      "[" ++ (a |> E.A.fmap(toString) |> Js.String.concatMany(_, ",")) ++ "]"
 | 
			
		||||
    | `Hash(v) =>
 | 
			
		||||
      "{"
 | 
			
		||||
      ++ (
 | 
			
		||||
        v
 | 
			
		||||
        |> E.A.fmap(((name, value)) => name ++ ":" ++ toString(value))
 | 
			
		||||
        |> Js.String.concatMany(_, ",")
 | 
			
		||||
      )
 | 
			
		||||
      ++ "}";
 | 
			
		||||
 | 
			
		||||
  let rec fromNode = (node: node): result(typedValue, string) =>
 | 
			
		||||
    switch (node) {
 | 
			
		||||
    | `SymbolicDist(`Float(r)) => Ok(`Float(r))
 | 
			
		||||
    | `SymbolicDist(s) => Ok(`SamplingDist(`SymbolicDist(s)))
 | 
			
		||||
    | `RenderedDist(s) => Ok(`RenderedDist(s))
 | 
			
		||||
    | `Array(r) =>
 | 
			
		||||
      r
 | 
			
		||||
      |> E.A.fmap(fromNode)
 | 
			
		||||
      |> E.A.R.firstErrorOrOpen
 | 
			
		||||
      |> E.R.fmap(r => `Array(r))
 | 
			
		||||
    | `Hash(hash) =>
 | 
			
		||||
      hash
 | 
			
		||||
      |> E.A.fmap(((name, t)) => fromNode(t) |> E.R.fmap(r => (name, r)))
 | 
			
		||||
      |> E.A.R.firstErrorOrOpen
 | 
			
		||||
      |> E.R.fmap(r => `Hash(r))
 | 
			
		||||
    | e => Error("Wrong type: " ++ ExpressionTreeBasic.toString(e))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  // todo: Arrays and hashes
 | 
			
		||||
  let rec fromNodeWithTypeCoercion = (evaluationParams, _type: _type, node) => {
 | 
			
		||||
    switch (_type, node) {
 | 
			
		||||
    | (`Float, _) =>
 | 
			
		||||
      switch (getFloat(node)) {
 | 
			
		||||
      | Some(a) => Ok(`Float(a))
 | 
			
		||||
      | _ => Error("Type Error: Expected float.")
 | 
			
		||||
      }
 | 
			
		||||
    | (`SamplingDistribution, _) =>
 | 
			
		||||
      PTypes.SamplingDistribution.renderIfIsNotSamplingDistribution(
 | 
			
		||||
        evaluationParams,
 | 
			
		||||
        node,
 | 
			
		||||
      )
 | 
			
		||||
      |> E.R.bind(_, fromNode)
 | 
			
		||||
    | (`RenderedDistribution, _) =>{
 | 
			
		||||
      ExpressionTypes.ExpressionTree.Render.render(evaluationParams, node)
 | 
			
		||||
      |> E.R.bind(_, fromNode);
 | 
			
		||||
    }
 | 
			
		||||
    | (`Array(_type), `Array(b)) =>
 | 
			
		||||
      b
 | 
			
		||||
      |> E.A.fmap(fromNodeWithTypeCoercion(evaluationParams, _type))
 | 
			
		||||
      |> E.A.R.firstErrorOrOpen
 | 
			
		||||
      |> E.R.fmap(r => `Array(r))
 | 
			
		||||
    | (`Hash(named), `Hash(r)) =>
 | 
			
		||||
      let keyValues =
 | 
			
		||||
        named
 | 
			
		||||
        |> E.A.fmap(((name, intendedType)) =>
 | 
			
		||||
             (
 | 
			
		||||
               name,
 | 
			
		||||
               intendedType,
 | 
			
		||||
               ExpressionTypes.ExpressionTree.Hash.getByName(r, name),
 | 
			
		||||
             )
 | 
			
		||||
           );
 | 
			
		||||
      let typedHash =
 | 
			
		||||
        keyValues
 | 
			
		||||
        |> E.A.fmap(((name, intendedType, optionNode)) =>
 | 
			
		||||
             switch (optionNode) {
 | 
			
		||||
             | Some(node) =>
 | 
			
		||||
               fromNodeWithTypeCoercion(evaluationParams, intendedType, node)
 | 
			
		||||
               |> E.R.fmap(node => (name, node))
 | 
			
		||||
             | None => Error("Hash parameter not present in hash.")
 | 
			
		||||
             }
 | 
			
		||||
           )
 | 
			
		||||
        |> E.A.R.firstErrorOrOpen
 | 
			
		||||
        |> E.R.fmap(r => `Hash(r));
 | 
			
		||||
      typedHash;
 | 
			
		||||
    | _ => Error("fromNodeWithTypeCoercion error, sorry.")
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let toFloat: typedValue => result(float, string) =
 | 
			
		||||
    fun
 | 
			
		||||
    | `Float(x) => Ok(x)
 | 
			
		||||
    | _ => Error("Not a float");
 | 
			
		||||
 | 
			
		||||
  let toArray: typedValue => result(array('a), string) =
 | 
			
		||||
    fun
 | 
			
		||||
    | `Array(x) => Ok(x)
 | 
			
		||||
    | _ => Error("Not an array");
 | 
			
		||||
 | 
			
		||||
  let toNamed: typedValue => result(hashTypedValue, string) =
 | 
			
		||||
    fun
 | 
			
		||||
    | `Hash(x) => Ok(x)
 | 
			
		||||
    | _ => Error("Not a named item");
 | 
			
		||||
 | 
			
		||||
  let toDist: typedValue => result(node,string) =
 | 
			
		||||
    fun
 | 
			
		||||
    | `SamplingDist(`SymbolicDist(c)) => Ok(`SymbolicDist(c))
 | 
			
		||||
    | `SamplingDist(`RenderedDist(c)) => Ok(`RenderedDist(c))
 | 
			
		||||
    | `RenderedDist(c) => Ok(`RenderedDist(c))
 | 
			
		||||
    | `Float(x) => Ok(`SymbolicDist(`Float(x)))
 | 
			
		||||
    | x => Error("Cannot be converted into a distribution: " ++ toString(x));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Function = {
 | 
			
		||||
  type t = _function;
 | 
			
		||||
  type ts = functions;
 | 
			
		||||
 | 
			
		||||
  module T = {
 | 
			
		||||
    let make =
 | 
			
		||||
        (~name, ~inputTypes, ~outputType, ~run, ~shouldCoerceTypes=true, _): t => {
 | 
			
		||||
      name,
 | 
			
		||||
      inputTypes,
 | 
			
		||||
      outputType,
 | 
			
		||||
      run,
 | 
			
		||||
      shouldCoerceTypes,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let _inputLengthCheck = (inputNodes: inputNodes, t: t) => {
 | 
			
		||||
      let expectedLength = E.A.length(t.inputTypes);
 | 
			
		||||
      let actualLength = E.A.length(inputNodes);
 | 
			
		||||
      expectedLength == actualLength
 | 
			
		||||
        ? Ok(inputNodes)
 | 
			
		||||
        : Error(
 | 
			
		||||
            "Wrong number of inputs. Expected"
 | 
			
		||||
            ++ (expectedLength |> E.I.toString)
 | 
			
		||||
            ++ ". Got:"
 | 
			
		||||
            ++ (actualLength |> E.I.toString),
 | 
			
		||||
          );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let _coerceInputNodes =
 | 
			
		||||
        (evaluationParams, inputTypes, shouldCoerce, inputNodes) =>
 | 
			
		||||
      Belt.Array.zip(inputTypes, inputNodes)
 | 
			
		||||
      |> E.A.fmap(((def, input)) =>
 | 
			
		||||
           shouldCoerce
 | 
			
		||||
             ? TypedValue.fromNodeWithTypeCoercion(
 | 
			
		||||
                 evaluationParams,
 | 
			
		||||
                 def,
 | 
			
		||||
                 input,
 | 
			
		||||
               )
 | 
			
		||||
             : TypedValue.fromNode(input)
 | 
			
		||||
         )
 | 
			
		||||
      |> E.A.R.firstErrorOrOpen;
 | 
			
		||||
 | 
			
		||||
    let inputsToTypedValues =
 | 
			
		||||
        (
 | 
			
		||||
          evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
          inputNodes: inputNodes,
 | 
			
		||||
          t: t,
 | 
			
		||||
        ) => {
 | 
			
		||||
      _inputLengthCheck(inputNodes, t)
 | 
			
		||||
      ->E.R.bind(
 | 
			
		||||
          _coerceInputNodes(
 | 
			
		||||
            evaluationParams,
 | 
			
		||||
            t.inputTypes,
 | 
			
		||||
            t.shouldCoerceTypes,
 | 
			
		||||
          ),
 | 
			
		||||
        )
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let run =
 | 
			
		||||
        (
 | 
			
		||||
          evaluationParams: ExpressionTypes.ExpressionTree.evaluationParams,
 | 
			
		||||
          inputNodes: inputNodes,
 | 
			
		||||
          t: t,
 | 
			
		||||
        ) => {
 | 
			
		||||
      inputsToTypedValues(evaluationParams, inputNodes, t)->E.R.bind(t.run)
 | 
			
		||||
      |> (
 | 
			
		||||
        fun
 | 
			
		||||
        | Ok(i) => Ok(i)
 | 
			
		||||
        | Error(r) => {
 | 
			
		||||
            Error("Function " ++ t.name ++ " error: " ++ r);
 | 
			
		||||
          }
 | 
			
		||||
      );
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  module Ts = {
 | 
			
		||||
    let findByName = (ts: ts, n: string) =>
 | 
			
		||||
      ts |> Belt.Array.getBy(_, ({name}) => name == n);
 | 
			
		||||
 | 
			
		||||
    let findByNameAndRun = (ts: ts, n: string, evaluationParams, inputTypes) =>
 | 
			
		||||
      findByName(ts, n) |> E.O.fmap(T.run(evaluationParams, inputTypes));
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +26,9 @@ module FloatFloatMap = {
 | 
			
		|||
  let fmap = (fn, t: t) => Belt.MutableMap.map(t, fn);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module Int = {
 | 
			
		||||
  let max = (i1: int, i2: int) => i1 > i2 ? i1 : i2;
 | 
			
		||||
};
 | 
			
		||||
/* Utils */
 | 
			
		||||
module U = {
 | 
			
		||||
  let isEqual = (a, b) => a == b;
 | 
			
		||||
| 
						 | 
				
			
			@ -146,6 +149,11 @@ module R = {
 | 
			
		|||
  let fmap = Rationale.Result.fmap;
 | 
			
		||||
  let bind = Rationale.Result.bind;
 | 
			
		||||
  let toExn = Belt.Result.getExn;
 | 
			
		||||
  let default = (default, res: Belt.Result.t('a, 'b)) =>
 | 
			
		||||
    switch (res) {
 | 
			
		||||
    | Ok(r) => r
 | 
			
		||||
    | Error(_) => default
 | 
			
		||||
    };
 | 
			
		||||
  let merge = (a, b) =>
 | 
			
		||||
    switch (a, b) {
 | 
			
		||||
    | (Error(e), _) => Error(e)
 | 
			
		||||
| 
						 | 
				
			
			@ -157,6 +165,9 @@ module R = {
 | 
			
		|||
    | Ok(r) => Some(r)
 | 
			
		||||
    | Error(_) => None
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  let errorIfCondition = (errorCondition, errorMessage, r) =>
 | 
			
		||||
    errorCondition(r) ? Error(errorMessage) : Ok(r);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let safe_fn_of_string = (fn, s: string): option('a) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -263,6 +274,7 @@ module A = {
 | 
			
		|||
  let init = Array.init;
 | 
			
		||||
  let reduce = Belt.Array.reduce;
 | 
			
		||||
  let reducei = Belt.Array.reduceWithIndex;
 | 
			
		||||
  let isEmpty = r => length(r) < 1;
 | 
			
		||||
  let min = a =>
 | 
			
		||||
    get(a, 0)
 | 
			
		||||
    |> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i < j ? i : j));
 | 
			
		||||
| 
						 | 
				
			
			@ -285,6 +297,16 @@ module A = {
 | 
			
		|||
      |> Rationale.Result.return
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  // This zips while taking the longest elements of each array.
 | 
			
		||||
  let zipMaxLength = (array1, array2) => {
 | 
			
		||||
    let maxLength = Int.max(length(array1), length(array2));
 | 
			
		||||
    let result = maxLength |> Belt.Array.makeUninitializedUnsafe;
 | 
			
		||||
    for (i in 0 to maxLength - 1) {
 | 
			
		||||
      Belt.Array.set(result, i, (get(array1, i), get(array2, i))) |> ignore;
 | 
			
		||||
    };
 | 
			
		||||
    result;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  let asList = (f: list('a) => list('a), r: array('a)) =>
 | 
			
		||||
    r |> to_list |> f |> of_list;
 | 
			
		||||
  /* TODO: Is there a better way of doing this? */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user