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