Merge pull request #13 from foretold-app/playground-addition
Added playground back, starting to improve code for it to be mergable
This commit is contained in:
commit
239c08b6cb
16
packages/playground/.gitignore
vendored
Normal file
16
packages/playground/.gitignore
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.DS_Store
|
||||||
|
.merlin
|
||||||
|
.bsb.lock
|
||||||
|
npm-debug.log
|
||||||
|
/node_modules/
|
||||||
|
.cache
|
||||||
|
.cache/*
|
||||||
|
dist
|
||||||
|
lib/*
|
||||||
|
*.cache
|
||||||
|
build
|
||||||
|
yarn-error.log
|
||||||
|
*.bs.js
|
||||||
|
# Local Netlify folder
|
||||||
|
.netlify
|
||||||
|
.idea
|
22
packages/playground/LICENSE
Normal file
22
packages/playground/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Foretold
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
19
packages/playground/README.md
Normal file
19
packages/playground/README.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Squiggle
|
||||||
|
|
||||||
|
This is an experiment DSL/language for making probabilistic estimates.
|
||||||
|
|
||||||
|
## DistPlus
|
||||||
|
We have a custom library called DistPlus to handle distributions with additional metadata. This helps handle mixed distributions (continuous + discrete), a cache for a cdf, possible unit types (specific times are supported), and limited domains.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
Currently it only has a few very simple models.
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn
|
||||||
|
yarn run start
|
||||||
|
yarn run parcel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected future setup
|
||||||
|
![setup](https://raw.githubusercontent.com/foretold-app/widedomain/master/Screen%20Shot%202020-06-30%20at%208.27.32%20AM.png)
|
BIN
packages/playground/Screen Shot 2020-06-30 at 8.27.32 AM.png
Normal file
BIN
packages/playground/Screen Shot 2020-06-30 at 8.27.32 AM.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 347 KiB |
13
packages/playground/__tests__/Bandwidth__Test.re
Normal file
13
packages/playground/__tests__/Bandwidth__Test.re
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
open Jest;
|
||||||
|
open Expect;
|
||||||
|
|
||||||
|
describe("Bandwidth", () => {
|
||||||
|
test("nrd0()", () => {
|
||||||
|
let data = [|1., 4., 3., 2.|];
|
||||||
|
expect(Bandwidth.nrd0(data)) |> toEqual(0.7625801874014622);
|
||||||
|
});
|
||||||
|
test("nrd()", () => {
|
||||||
|
let data = [|1., 4., 3., 2.|];
|
||||||
|
expect(Bandwidth.nrd(data)) |> toEqual(0.8981499984950554);
|
||||||
|
});
|
||||||
|
});
|
104
packages/playground/__tests__/DistTypes__Test.re
Normal file
104
packages/playground/__tests__/DistTypes__Test.re
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("DistTypes", () => {
|
||||||
|
describe("Domain", () => {
|
||||||
|
let makeComplete = (yPoint, expectation) =>
|
||||||
|
makeTest(
|
||||||
|
"With input: " ++ Js.Float.toString(yPoint),
|
||||||
|
DistTypes.Domain.yPointToSubYPoint(Complete, yPoint),
|
||||||
|
expectation,
|
||||||
|
);
|
||||||
|
let makeSingle =
|
||||||
|
(
|
||||||
|
direction: [ | `left | `right],
|
||||||
|
excludingProbabilityMass,
|
||||||
|
yPoint,
|
||||||
|
expectation,
|
||||||
|
) =>
|
||||||
|
makeTest(
|
||||||
|
"Excluding: "
|
||||||
|
++ Js.Float.toString(excludingProbabilityMass)
|
||||||
|
++ " and yPoint: "
|
||||||
|
++ Js.Float.toString(yPoint),
|
||||||
|
DistTypes.Domain.yPointToSubYPoint(
|
||||||
|
direction == `left
|
||||||
|
? LeftLimited({xPoint: 3.0, excludingProbabilityMass})
|
||||||
|
: RightLimited({xPoint: 3.0, excludingProbabilityMass}),
|
||||||
|
yPoint,
|
||||||
|
),
|
||||||
|
expectation,
|
||||||
|
);
|
||||||
|
let makeDouble = (domain, yPoint, expectation) =>
|
||||||
|
makeTest(
|
||||||
|
"Excluding: limits",
|
||||||
|
DistTypes.Domain.yPointToSubYPoint(domain, yPoint),
|
||||||
|
expectation,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("With Complete Domain", () => {
|
||||||
|
makeComplete(0.0, Some(0.0));
|
||||||
|
makeComplete(0.6, Some(0.6));
|
||||||
|
makeComplete(1.0, Some(1.0));
|
||||||
|
});
|
||||||
|
describe("With Left Limit", () => {
|
||||||
|
makeSingle(`left, 0.5, 1.0, Some(1.0));
|
||||||
|
makeSingle(`left, 0.5, 0.75, Some(0.5));
|
||||||
|
makeSingle(`left, 0.8, 0.9, Some(0.5));
|
||||||
|
makeSingle(`left, 0.5, 0.4, None);
|
||||||
|
makeSingle(`left, 0.5, 0.5, Some(0.0));
|
||||||
|
});
|
||||||
|
describe("With Right Limit", () => {
|
||||||
|
makeSingle(`right, 0.5, 1.0, None);
|
||||||
|
makeSingle(`right, 0.5, 0.25, Some(0.5));
|
||||||
|
makeSingle(`right, 0.8, 0.5, None);
|
||||||
|
makeSingle(`right, 0.2, 0.2, Some(0.25));
|
||||||
|
makeSingle(`right, 0.5, 0.5, Some(1.0));
|
||||||
|
makeSingle(`right, 0.5, 0.0, Some(0.0));
|
||||||
|
makeSingle(`right, 0.5, 0.5, Some(1.0));
|
||||||
|
});
|
||||||
|
describe("With Left and Right Limit", () => {
|
||||||
|
makeDouble(
|
||||||
|
LeftAndRightLimited(
|
||||||
|
{excludingProbabilityMass: 0.25, xPoint: 3.0},
|
||||||
|
{excludingProbabilityMass: 0.25, xPoint: 10.0},
|
||||||
|
),
|
||||||
|
0.5,
|
||||||
|
Some(0.5),
|
||||||
|
);
|
||||||
|
makeDouble(
|
||||||
|
LeftAndRightLimited(
|
||||||
|
{excludingProbabilityMass: 0.1, xPoint: 3.0},
|
||||||
|
{excludingProbabilityMass: 0.1, xPoint: 10.0},
|
||||||
|
),
|
||||||
|
0.2,
|
||||||
|
Some(0.125),
|
||||||
|
);
|
||||||
|
makeDouble(
|
||||||
|
LeftAndRightLimited(
|
||||||
|
{excludingProbabilityMass: 0.1, xPoint: 3.0},
|
||||||
|
{excludingProbabilityMass: 0.1, xPoint: 10.0},
|
||||||
|
),
|
||||||
|
0.1,
|
||||||
|
Some(0.0),
|
||||||
|
);
|
||||||
|
makeDouble(
|
||||||
|
LeftAndRightLimited(
|
||||||
|
{excludingProbabilityMass: 0.1, xPoint: 3.0},
|
||||||
|
{excludingProbabilityMass: 0.1, xPoint: 10.0},
|
||||||
|
),
|
||||||
|
0.05,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
415
packages/playground/__tests__/Distributions__Test.re
Normal file
415
packages/playground/__tests__/Distributions__Test.re
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
open Jest;
|
||||||
|
open Expect;
|
||||||
|
|
||||||
|
let shape: DistTypes.xyShape = {xs: [|1., 4., 8.|], ys: [|8., 9., 2.|]};
|
||||||
|
|
||||||
|
// let makeTest = (~only=false, str, item1, item2) =>
|
||||||
|
// only
|
||||||
|
// ? Only.test(str, () =>
|
||||||
|
// expect(item1) |> toEqual(item2)
|
||||||
|
// )
|
||||||
|
// : test(str, () =>
|
||||||
|
// expect(item1) |> toEqual(item2)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let makeTestCloseEquality = (~only=false, str, item1, item2, ~digits) =>
|
||||||
|
// only
|
||||||
|
// ? Only.test(str, () =>
|
||||||
|
// expect(item1) |> toBeSoCloseTo(item2, ~digits)
|
||||||
|
// )
|
||||||
|
// : test(str, () =>
|
||||||
|
// expect(item1) |> toBeSoCloseTo(item2, ~digits)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// describe("Shape", () => {
|
||||||
|
// describe("Continuous", () => {
|
||||||
|
// open Continuous;
|
||||||
|
// let continuous = make(`Linear, shape, None);
|
||||||
|
// makeTest("minX", T.minX(continuous), 1.0);
|
||||||
|
// makeTest("maxX", T.maxX(continuous), 8.0);
|
||||||
|
// makeTest(
|
||||||
|
// "mapY",
|
||||||
|
// T.mapY(r => r *. 2.0, continuous) |> getShape |> (r => r.ys),
|
||||||
|
// [|16., 18.0, 4.0|],
|
||||||
|
// );
|
||||||
|
// describe("xToY", () => {
|
||||||
|
// describe("when Linear", () => {
|
||||||
|
// makeTest(
|
||||||
|
// "at 4.0",
|
||||||
|
// T.xToY(4., continuous),
|
||||||
|
// {continuous: 9.0, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// // Note: This below is weird to me, I'm not sure if it's what we want really.
|
||||||
|
// makeTest(
|
||||||
|
// "at 0.0",
|
||||||
|
// T.xToY(0., continuous),
|
||||||
|
// {continuous: 8.0, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "at 5.0",
|
||||||
|
// T.xToY(5., continuous),
|
||||||
|
// {continuous: 7.25, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "at 10.0",
|
||||||
|
// T.xToY(10., continuous),
|
||||||
|
// {continuous: 2.0, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// describe("when Stepwise", () => {
|
||||||
|
// let continuous = make(`Stepwise, shape, None);
|
||||||
|
// makeTest(
|
||||||
|
// "at 4.0",
|
||||||
|
// T.xToY(4., continuous),
|
||||||
|
// {continuous: 9.0, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "at 0.0",
|
||||||
|
// T.xToY(0., continuous),
|
||||||
|
// {continuous: 0.0, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "at 5.0",
|
||||||
|
// T.xToY(5., continuous),
|
||||||
|
// {continuous: 9.0, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "at 10.0",
|
||||||
|
// T.xToY(10., continuous),
|
||||||
|
// {continuous: 2.0, discrete: 0.0},
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// makeTest(
|
||||||
|
// "integral",
|
||||||
|
// T.Integral.get(~cache=None, continuous) |> getShape,
|
||||||
|
// {xs: [|1.0, 4.0, 8.0|], ys: [|0.0, 25.5, 47.5|]},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "toLinear",
|
||||||
|
// {
|
||||||
|
// let continuous =
|
||||||
|
// make(`Stepwise, {xs: [|1., 4., 8.|], ys: [|0.1, 5., 1.0|]}, None);
|
||||||
|
// continuous |> toLinear |> E.O.fmap(getShape);
|
||||||
|
// },
|
||||||
|
// Some({
|
||||||
|
// xs: [|1.00007, 1.00007, 4.0, 4.00007, 8.0, 8.00007|],
|
||||||
|
// ys: [|0.0, 0.1, 0.1, 5.0, 5.0, 1.0|],
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "toLinear",
|
||||||
|
// {
|
||||||
|
// let continuous = make(`Stepwise, {xs: [|0.0|], ys: [|0.3|]}, None);
|
||||||
|
// continuous |> toLinear |> E.O.fmap(getShape);
|
||||||
|
// },
|
||||||
|
// Some({xs: [|0.0|], ys: [|0.3|]}),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integralXToY",
|
||||||
|
// T.Integral.xToY(~cache=None, 0.0, continuous),
|
||||||
|
// 0.0,
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integralXToY",
|
||||||
|
// T.Integral.xToY(~cache=None, 2.0, continuous),
|
||||||
|
// 8.5,
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integralXToY",
|
||||||
|
// T.Integral.xToY(~cache=None, 100.0, continuous),
|
||||||
|
// 47.5,
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integralEndY",
|
||||||
|
// continuous
|
||||||
|
// |> T.normalize //scaleToIntegralSum(~intendedSum=1.0)
|
||||||
|
// |> T.Integral.sum(~cache=None),
|
||||||
|
// 1.0,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe("Discrete", () => {
|
||||||
|
// open Discrete;
|
||||||
|
// let shape: DistTypes.xyShape = {
|
||||||
|
// xs: [|1., 4., 8.|],
|
||||||
|
// ys: [|0.3, 0.5, 0.2|],
|
||||||
|
// };
|
||||||
|
// let discrete = make(shape, None);
|
||||||
|
// makeTest("minX", T.minX(discrete), 1.0);
|
||||||
|
// makeTest("maxX", T.maxX(discrete), 8.0);
|
||||||
|
// makeTest(
|
||||||
|
// "mapY",
|
||||||
|
// T.mapY(r => r *. 2.0, discrete) |> (r => getShape(r).ys),
|
||||||
|
// [|0.6, 1.0, 0.4|],
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 4.0",
|
||||||
|
// T.xToY(4., discrete),
|
||||||
|
// {discrete: 0.5, continuous: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 0.0",
|
||||||
|
// T.xToY(0., discrete),
|
||||||
|
// {discrete: 0.0, continuous: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 5.0",
|
||||||
|
// T.xToY(5., discrete),
|
||||||
|
// {discrete: 0.0, continuous: 0.0},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "scaleBy",
|
||||||
|
// scaleBy(~scale=4.0, discrete),
|
||||||
|
// make({xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, None),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "normalize, then scale by 4.0",
|
||||||
|
// discrete
|
||||||
|
// |> T.normalize
|
||||||
|
// |> scaleBy(~scale=4.0),
|
||||||
|
// make({xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]}, None),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "scaleToIntegralSum: back and forth",
|
||||||
|
// discrete
|
||||||
|
// |> T.normalize
|
||||||
|
// |> scaleBy(~scale=4.0)
|
||||||
|
// |> T.normalize,
|
||||||
|
// discrete,
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integral",
|
||||||
|
// T.Integral.get(~cache=None, discrete),
|
||||||
|
// Continuous.make(
|
||||||
|
// `Stepwise,
|
||||||
|
// {xs: [|1., 4., 8.|], ys: [|0.3, 0.8, 1.0|]},
|
||||||
|
// None
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integral with 1 element",
|
||||||
|
// T.Integral.get(~cache=None, Discrete.make({xs: [|0.0|], ys: [|1.0|]}, None)),
|
||||||
|
// Continuous.make(`Stepwise, {xs: [|0.0|], ys: [|1.0|]}, None),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integralXToY",
|
||||||
|
// T.Integral.xToY(~cache=None, 6.0, discrete),
|
||||||
|
// 0.9,
|
||||||
|
// );
|
||||||
|
// makeTest("integralEndY", T.Integral.sum(~cache=None, discrete), 1.0);
|
||||||
|
// makeTest("mean", T.mean(discrete), 3.9);
|
||||||
|
// makeTestCloseEquality(
|
||||||
|
// "variance",
|
||||||
|
// T.variance(discrete),
|
||||||
|
// 5.89,
|
||||||
|
// ~digits=7,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe("Mixed", () => {
|
||||||
|
// open Distributions.Mixed;
|
||||||
|
// let discreteShape: DistTypes.xyShape = {
|
||||||
|
// xs: [|1., 4., 8.|],
|
||||||
|
// ys: [|0.3, 0.5, 0.2|],
|
||||||
|
// };
|
||||||
|
// let discrete = Discrete.make(discreteShape, None);
|
||||||
|
// let continuous =
|
||||||
|
// Continuous.make(
|
||||||
|
// `Linear,
|
||||||
|
// {xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]},
|
||||||
|
// None
|
||||||
|
// )
|
||||||
|
// |> Continuous.T.normalize; //scaleToIntegralSum(~intendedSum=1.0);
|
||||||
|
// let mixed = Mixed.make(
|
||||||
|
// ~continuous,
|
||||||
|
// ~discrete,
|
||||||
|
// );
|
||||||
|
// makeTest("minX", T.minX(mixed), 1.0);
|
||||||
|
// makeTest("maxX", T.maxX(mixed), 14.0);
|
||||||
|
// makeTest(
|
||||||
|
// "mapY",
|
||||||
|
// T.mapY(r => r *. 2.0, mixed),
|
||||||
|
// Mixed.make(
|
||||||
|
// ~continuous=
|
||||||
|
// Continuous.make(
|
||||||
|
// `Linear,
|
||||||
|
// {
|
||||||
|
// xs: [|3., 7., 14.|],
|
||||||
|
// ys: [|
|
||||||
|
// 0.11588411588411589,
|
||||||
|
// 0.16383616383616384,
|
||||||
|
// 0.24775224775224775,
|
||||||
|
// |],
|
||||||
|
// },
|
||||||
|
// None
|
||||||
|
// ),
|
||||||
|
// ~discrete=Discrete.make({xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]}, None)
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 4.0",
|
||||||
|
// T.xToY(4., mixed),
|
||||||
|
// {discrete: 0.25, continuous: 0.03196803196803197},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 0.0",
|
||||||
|
// T.xToY(0., mixed),
|
||||||
|
// {discrete: 0.0, continuous: 0.028971028971028972},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 5.0",
|
||||||
|
// T.xToY(7., mixed),
|
||||||
|
// {discrete: 0.0, continuous: 0.04095904095904096},
|
||||||
|
// );
|
||||||
|
// makeTest("integralEndY", T.Integral.sum(~cache=None, mixed), 1.0);
|
||||||
|
// makeTest(
|
||||||
|
// "scaleBy",
|
||||||
|
// Mixed.scaleBy(~scale=2.0, mixed),
|
||||||
|
// Mixed.make(
|
||||||
|
// ~continuous=
|
||||||
|
// Continuous.make(
|
||||||
|
// `Linear,
|
||||||
|
// {
|
||||||
|
// xs: [|3., 7., 14.|],
|
||||||
|
// ys: [|
|
||||||
|
// 0.11588411588411589,
|
||||||
|
// 0.16383616383616384,
|
||||||
|
// 0.24775224775224775,
|
||||||
|
// |],
|
||||||
|
// },
|
||||||
|
// None
|
||||||
|
// ),
|
||||||
|
// ~discrete=Discrete.make({xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]}, None),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "integral",
|
||||||
|
// T.Integral.get(~cache=None, mixed),
|
||||||
|
// Continuous.make(
|
||||||
|
// `Linear,
|
||||||
|
// {
|
||||||
|
// xs: [|1.00007, 1.00007, 3., 4., 4.00007, 7., 8., 8.00007, 14.|],
|
||||||
|
// ys: [|
|
||||||
|
// 0.0,
|
||||||
|
// 0.0,
|
||||||
|
// 0.15,
|
||||||
|
// 0.18496503496503497,
|
||||||
|
// 0.4349674825174825,
|
||||||
|
// 0.5398601398601399,
|
||||||
|
// 0.5913086913086913,
|
||||||
|
// 0.6913122927072927,
|
||||||
|
// 1.0,
|
||||||
|
// |],
|
||||||
|
// },
|
||||||
|
// None,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe("Distplus", () => {
|
||||||
|
// open DistPlus;
|
||||||
|
// let discreteShape: DistTypes.xyShape = {
|
||||||
|
// xs: [|1., 4., 8.|],
|
||||||
|
// ys: [|0.3, 0.5, 0.2|],
|
||||||
|
// };
|
||||||
|
// let discrete = Discrete.make(discreteShape, None);
|
||||||
|
// let continuous =
|
||||||
|
// Continuous.make(
|
||||||
|
// `Linear,
|
||||||
|
// {xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]},
|
||||||
|
// None
|
||||||
|
// )
|
||||||
|
// |> Continuous.T.normalize; //scaleToIntegralSum(~intendedSum=1.0);
|
||||||
|
// let mixed =
|
||||||
|
// Mixed.make(
|
||||||
|
// ~continuous,
|
||||||
|
// ~discrete,
|
||||||
|
// );
|
||||||
|
// let distPlus =
|
||||||
|
// DistPlus.make(
|
||||||
|
// ~shape=Mixed(mixed),
|
||||||
|
// ~squiggleString=None,
|
||||||
|
// (),
|
||||||
|
// );
|
||||||
|
// makeTest("minX", T.minX(distPlus), 1.0);
|
||||||
|
// makeTest("maxX", T.maxX(distPlus), 14.0);
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 4.0",
|
||||||
|
// T.xToY(4., distPlus),
|
||||||
|
// {discrete: 0.25, continuous: 0.03196803196803197},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 0.0",
|
||||||
|
// T.xToY(0., distPlus),
|
||||||
|
// {discrete: 0.0, continuous: 0.028971028971028972},
|
||||||
|
// );
|
||||||
|
// makeTest(
|
||||||
|
// "xToY at 5.0",
|
||||||
|
// T.xToY(7., distPlus),
|
||||||
|
// {discrete: 0.0, continuous: 0.04095904095904096},
|
||||||
|
// );
|
||||||
|
// makeTest("integralEndY", T.Integral.sum(~cache=None, distPlus), 1.0);
|
||||||
|
// makeTest(
|
||||||
|
// "integral",
|
||||||
|
// T.Integral.get(~cache=None, distPlus) |> T.toContinuous,
|
||||||
|
// Some(
|
||||||
|
// Continuous.make(
|
||||||
|
// `Linear,
|
||||||
|
// {
|
||||||
|
// xs: [|1.00007, 1.00007, 3., 4., 4.00007, 7., 8., 8.00007, 14.|],
|
||||||
|
// ys: [|
|
||||||
|
// 0.0,
|
||||||
|
// 0.0,
|
||||||
|
// 0.15,
|
||||||
|
// 0.18496503496503497,
|
||||||
|
// 0.4349674825174825,
|
||||||
|
// 0.5398601398601399,
|
||||||
|
// 0.5913086913086913,
|
||||||
|
// 0.6913122927072927,
|
||||||
|
// 1.0,
|
||||||
|
// |],
|
||||||
|
// },
|
||||||
|
// None,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// describe("Shape", () => {
|
||||||
|
// let mean = 10.0;
|
||||||
|
// let stdev = 4.0;
|
||||||
|
// let variance = stdev ** 2.0;
|
||||||
|
// let numSamples = 10000;
|
||||||
|
// open Distributions.Shape;
|
||||||
|
// let normal: SymbolicTypes.symbolicDist = `Normal({mean, stdev});
|
||||||
|
// let normalShape = ExpressionTree.toShape(numSamples, `SymbolicDist(normal));
|
||||||
|
// let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev);
|
||||||
|
// let lognormalShape = ExpressionTree.toShape(numSamples, `SymbolicDist(lognormal));
|
||||||
|
|
||||||
|
// makeTestCloseEquality(
|
||||||
|
// "Mean of a normal",
|
||||||
|
// T.mean(normalShape),
|
||||||
|
// mean,
|
||||||
|
// ~digits=2,
|
||||||
|
// );
|
||||||
|
// makeTestCloseEquality(
|
||||||
|
// "Variance of a normal",
|
||||||
|
// T.variance(normalShape),
|
||||||
|
// variance,
|
||||||
|
// ~digits=1,
|
||||||
|
// );
|
||||||
|
// makeTestCloseEquality(
|
||||||
|
// "Mean of a lognormal",
|
||||||
|
// T.mean(lognormalShape),
|
||||||
|
// mean,
|
||||||
|
// ~digits=2,
|
||||||
|
// );
|
||||||
|
// makeTestCloseEquality(
|
||||||
|
// "Variance of a lognormal",
|
||||||
|
// T.variance(lognormalShape),
|
||||||
|
// variance,
|
||||||
|
// ~digits=0,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// });
|
|
@ -1,6 +1,6 @@
|
||||||
open Jest;
|
open Jest;
|
||||||
open Expect;
|
open Expect;
|
||||||
/*
|
|
||||||
let makeTest = (~only=false, str, item1, item2) =>
|
let makeTest = (~only=false, str, item1, item2) =>
|
||||||
only
|
only
|
||||||
? Only.test(str, () =>
|
? Only.test(str, () =>
|
||||||
|
@ -54,4 +54,4 @@ describe("XYShapes", () => {
|
||||||
Error("Sad"),
|
Error("Sad"),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}); */
|
});
|
51
packages/playground/__tests__/Samples__test.re
Normal file
51
packages/playground/__tests__/Samples__test.re
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("Lodash", () => {
|
||||||
|
describe("Lodash", () => {
|
||||||
|
makeTest(
|
||||||
|
"split",
|
||||||
|
SamplesToShape.Internals.T.splitContinuousAndDiscrete([|1.432, 1.33455, 2.0|]),
|
||||||
|
([|1.432, 1.33455, 2.0|], E.FloatFloatMap.empty()),
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"split",
|
||||||
|
SamplesToShape.Internals.T.splitContinuousAndDiscrete([|
|
||||||
|
1.432,
|
||||||
|
1.33455,
|
||||||
|
2.0,
|
||||||
|
2.0,
|
||||||
|
2.0,
|
||||||
|
2.0,
|
||||||
|
|])
|
||||||
|
|> (((c, disc)) => (c, disc |> E.FloatFloatMap.toArray)),
|
||||||
|
([|1.432, 1.33455|], [|(2.0, 4.0)|]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let makeDuplicatedArray = count => {
|
||||||
|
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int);
|
||||||
|
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare);
|
||||||
|
E.A.concatMany([|sorted, sorted, sorted, sorted|])
|
||||||
|
|> Belt.SortArray.stableSortBy(_, compare);
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_, discrete) =
|
||||||
|
SamplesToShape.Internals.T.splitContinuousAndDiscrete(makeDuplicatedArray(10));
|
||||||
|
let toArr = discrete |> E.FloatFloatMap.toArray;
|
||||||
|
makeTest("splitMedium", toArr |> Belt.Array.length, 10);
|
||||||
|
|
||||||
|
let (c, discrete) =
|
||||||
|
SamplesToShape.Internals.T.splitContinuousAndDiscrete(makeDuplicatedArray(500));
|
||||||
|
let toArr = discrete |> E.FloatFloatMap.toArray;
|
||||||
|
makeTest("splitMedium", toArr |> Belt.Array.length, 500);
|
||||||
|
})
|
||||||
|
});
|
63
packages/playground/__tests__/XYShape__Test.re
Normal file
63
packages/playground/__tests__/XYShape__Test.re
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
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 shape1: DistTypes.xyShape = {xs: [|1., 4., 8.|], ys: [|0.2, 0.4, 0.8|]};
|
||||||
|
|
||||||
|
let shape2: DistTypes.xyShape = {
|
||||||
|
xs: [|1., 5., 10.|],
|
||||||
|
ys: [|0.2, 0.5, 0.8|],
|
||||||
|
};
|
||||||
|
|
||||||
|
let shape3: DistTypes.xyShape = {
|
||||||
|
xs: [|1., 20., 50.|],
|
||||||
|
ys: [|0.2, 0.5, 0.8|],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("XYShapes", () => {
|
||||||
|
describe("logScorePoint", () => {
|
||||||
|
makeTest(
|
||||||
|
"When identical",
|
||||||
|
XYShape.logScorePoint(30, shape1, shape1),
|
||||||
|
Some(0.0),
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"When similar",
|
||||||
|
XYShape.logScorePoint(30, shape1, shape2),
|
||||||
|
Some(1.658971191043856),
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"When very different",
|
||||||
|
XYShape.logScorePoint(30, shape1, shape3),
|
||||||
|
Some(210.3721280423322),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// describe("transverse", () => {
|
||||||
|
// makeTest(
|
||||||
|
// "When very different",
|
||||||
|
// XYShape.Transversal._transverse(
|
||||||
|
// (aCurrent, aLast) => aCurrent +. aLast,
|
||||||
|
// [|1.0, 2.0, 3.0, 4.0|],
|
||||||
|
// ),
|
||||||
|
// [|1.0, 3.0, 6.0, 10.0|],
|
||||||
|
// )
|
||||||
|
// });
|
||||||
|
describe("integrateWithTriangles", () => {
|
||||||
|
makeTest(
|
||||||
|
"integrates correctly",
|
||||||
|
XYShape.Range.integrateWithTriangles(shape1),
|
||||||
|
Some({
|
||||||
|
xs: [|1., 4., 8.|],
|
||||||
|
ys: [|0.0, 0.9000000000000001, 3.3000000000000007|],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
54
packages/playground/bsconfig.json
Normal file
54
packages/playground/bsconfig.json
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"name": "probExample",
|
||||||
|
"reason": {
|
||||||
|
"react-jsx": 3
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"dir": "src",
|
||||||
|
"subdirs": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dir": "showcase",
|
||||||
|
"type": "dev",
|
||||||
|
"subdirs": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dir": "__tests__",
|
||||||
|
"type": "dev",
|
||||||
|
"subdirs": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bsc-flags": [
|
||||||
|
"-bs-super-errors",
|
||||||
|
"-bs-no-version-header",
|
||||||
|
"-bs-g"
|
||||||
|
],
|
||||||
|
"package-specs": [
|
||||||
|
{
|
||||||
|
"module": "commonjs",
|
||||||
|
"in-source": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"suffix": ".bs.js",
|
||||||
|
"namespace": true,
|
||||||
|
"bs-dependencies": [
|
||||||
|
"@glennsl/bs-jest",
|
||||||
|
"@glennsl/bs-json",
|
||||||
|
"@rescriptbr/reform",
|
||||||
|
"@rescript/react",
|
||||||
|
"bs-css",
|
||||||
|
"bs-css-dom",
|
||||||
|
"squiggle-experimental",
|
||||||
|
"rationale",
|
||||||
|
"bs-moment",
|
||||||
|
"reschema"
|
||||||
|
],
|
||||||
|
"refmt": 3,
|
||||||
|
"warnings": {
|
||||||
|
"number": "+A-42-48-9-30-4-102"
|
||||||
|
},
|
||||||
|
"ppx-flags": [
|
||||||
|
"lenses-ppx/ppx"
|
||||||
|
]
|
||||||
|
}
|
22
packages/playground/docs/README.md
Normal file
22
packages/playground/docs/README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Squiggle
|
||||||
|
|
||||||
|
Squiggle is a DSL for making probabilistic estimations. It is meant
|
||||||
|
to be a programming analogue of the [Guesstimate](https://www.getguesstimate.com/)
|
||||||
|
application.
|
||||||
|
|
||||||
|
There are several use cases for a language that represent uncertainty. Some include
|
||||||
|
writing cost effectiveness analysis, as well as doing accurate forecasting.
|
||||||
|
|
||||||
|
Squiggle is written in [Rescript](https://rescript-lang.org/) and is presented
|
||||||
|
as a website.
|
||||||
|
|
||||||
|
If you wish to try squiggle out, you can visit our [main page](https://squiggle-language.com/).
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
We use the [Math.js expression language](https://mathjs.org/index.html) for Squiggle.
|
||||||
|
So any expression that's available on Math.js is supported on Squiggle.
|
||||||
|
|
||||||
|
To represent uncertainty, we use a custom DSL called [DistML](https://docs.google.com/document/d/1xlEC8KjchP4PL-KdSxfBJr0UZ9nVMSAlz-rjAQEinyA/edit#).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
To contribute to this project, we recommend visiting our [Contributing Guide](contributing.md).
|
27
packages/playground/docs/index.html
Normal file
27
packages/playground/docs/index.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Squiggle Documentation</title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
|
<meta name="description" content="Description">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script>
|
||||||
|
window.$docsify = {
|
||||||
|
name: 'Squiggle',
|
||||||
|
repo: 'foretold-app/squiggle',
|
||||||
|
loadSidebar: true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- Docsify v4 -->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
|
||||||
|
<!-- CDN files for docsify-katex -->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/docsify-katex@latest/dist/docsify-katex.js"></script>
|
||||||
|
<!-- or <script src="//cdn.jsdelivr.net/gh/upupming/docsify-katex@latest/dist/docsify-katex.js"></script> -->
|
||||||
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.css"/>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
packages/playground/docs/package.json
Normal file
5
packages/playground/docs/package.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"docsify-cli": "^4.4.3"
|
||||||
|
}
|
||||||
|
}
|
1323
packages/playground/docs/yarn.lock
Normal file
1323
packages/playground/docs/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
4
packages/playground/netlify.toml
Normal file
4
packages/playground/netlify.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
17027
packages/playground/package-lock.json
generated
Normal file
17027
packages/playground/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
82
packages/playground/package.json
Normal file
82
packages/playground/package.json
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
{
|
||||||
|
"name": "estiband",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"homepage": "https://foretold-app.github.io/estiband/",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rescript build",
|
||||||
|
"build:style": "tailwind build src/styles/index.css -o src/styles/tailwind.css",
|
||||||
|
"start": "rescript build -w",
|
||||||
|
"clean": "rescript clean",
|
||||||
|
"parcel": "parcel ./src/index.html",
|
||||||
|
"parcel-build": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
|
||||||
|
"showcase": "PORT=12345 parcel showcase/index.html",
|
||||||
|
"server": "moduleserve ./ --port 8000",
|
||||||
|
"predeploy": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
|
||||||
|
"deploy": "gh-pages -d dist",
|
||||||
|
"test": "jest",
|
||||||
|
"test:ci": "yarn jest",
|
||||||
|
"watch:test": "jest --watchAll",
|
||||||
|
"watch:s": "yarn jest -- Converter_test --watch"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"BuckleScript",
|
||||||
|
"ReasonReact",
|
||||||
|
"reason-react"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@glennsl/bs-json": "^5.0.2",
|
||||||
|
"@rescript/react": "^0.10.3",
|
||||||
|
"@rescriptbr/reform": "^11.0.1",
|
||||||
|
"ace-builds": "^1.4.12",
|
||||||
|
"antd": "^4.18.5",
|
||||||
|
"autoprefixer": "9.8.8",
|
||||||
|
"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": "^15.1.0",
|
||||||
|
"bs-css-dom": "^3.1.0",
|
||||||
|
"bs-moment": "0.6.0",
|
||||||
|
"bs-reform": "^10.0.3",
|
||||||
|
"bsb-js": "1.1.7",
|
||||||
|
"d3": "7.3.0",
|
||||||
|
"gh-pages": "2.2.0",
|
||||||
|
"jest": "^25.5.1",
|
||||||
|
"jstat": "1.9.2",
|
||||||
|
"lenses-ppx": "5.1.0",
|
||||||
|
"less": "3.10.3",
|
||||||
|
"lodash": "4.17.15",
|
||||||
|
"mathjs": "5.10.3",
|
||||||
|
"moduleserve": "0.9.1",
|
||||||
|
"moment": "2.24.0",
|
||||||
|
"pdfast": "^0.2.0",
|
||||||
|
"postcss-cli": "7.1.0",
|
||||||
|
"rationale": "0.2.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": "^17.3.2",
|
||||||
|
"react-vega": "^7.4.4",
|
||||||
|
"reason-react": ">=0.7.0",
|
||||||
|
"reschema": "^2.2.0",
|
||||||
|
"rescript": "^9.1.4",
|
||||||
|
"squiggle-experimental": "^0.1.8",
|
||||||
|
"tailwindcss": "1.2.0",
|
||||||
|
"vega": "*",
|
||||||
|
"vega-embed": "6.6.0",
|
||||||
|
"vega-lite": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@glennsl/bs-jest": "^0.5.1",
|
||||||
|
"bs-platform": "9.0.2",
|
||||||
|
"docsify": "^4.12.2",
|
||||||
|
"parcel-bundler": "1.12.4",
|
||||||
|
"parcel-plugin-bundle-visualiser": "^1.2.0",
|
||||||
|
"parcel-plugin-less-js-enabled": "1.0.2"
|
||||||
|
},
|
||||||
|
"alias": {
|
||||||
|
"react": "./node_modules/react",
|
||||||
|
"react-dom": "./node_modules/react-dom"
|
||||||
|
}
|
||||||
|
}
|
8
packages/playground/postcss.config.js
Normal file
8
packages/playground/postcss.config.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
//postcss.config.js
|
||||||
|
const tailwindcss = require('tailwindcss');
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
tailwindcss('./tailwind.js'),
|
||||||
|
require('autoprefixer'),
|
||||||
|
],
|
||||||
|
};
|
5
packages/playground/shell.nix
Normal file
5
packages/playground/shell.nix
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
name = "squiggle";
|
||||||
|
buildInputs = with pkgs; [ yarn yarn2nix nodePackages.npm ];
|
||||||
|
}
|
1
packages/playground/showcase/Entries.re
Normal file
1
packages/playground/showcase/Entries.re
Normal file
|
@ -0,0 +1 @@
|
||||||
|
let entries = [];
|
30
packages/playground/showcase/EntryTypes.re
Normal file
30
packages/playground/showcase/EntryTypes.re
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
type compEntry = {
|
||||||
|
mutable id: string,
|
||||||
|
title: string,
|
||||||
|
render: unit => React.element,
|
||||||
|
container: containerType,
|
||||||
|
}
|
||||||
|
and folderEntry = {
|
||||||
|
mutable id: string,
|
||||||
|
title: string,
|
||||||
|
children: list(navEntry),
|
||||||
|
}
|
||||||
|
and navEntry =
|
||||||
|
| CompEntry(compEntry)
|
||||||
|
| FolderEntry(folderEntry)
|
||||||
|
and containerType =
|
||||||
|
| FullWidth
|
||||||
|
| Sidebar;
|
||||||
|
|
||||||
|
let entry = (~title, ~render): navEntry => {
|
||||||
|
CompEntry({id: "", title, render, container: FullWidth});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maybe different api, this avoids breaking changes
|
||||||
|
let sidebar = (~title, ~render): navEntry => {
|
||||||
|
CompEntry({id: "", title, render, container: Sidebar});
|
||||||
|
};
|
||||||
|
|
||||||
|
let folder = (~title, ~children): navEntry => {
|
||||||
|
FolderEntry({id: "", title, children});
|
||||||
|
};
|
163
packages/playground/showcase/Lib.res
Normal file
163
packages/playground/showcase/Lib.res
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
open EntryTypes
|
||||||
|
|
||||||
|
module HS = Belt.HashMap.String
|
||||||
|
|
||||||
|
let entriesByPath: HS.t<navEntry> = HS.make(~hintSize=100)
|
||||||
|
|
||||||
|
/* Creates unique id's per scope based on title */
|
||||||
|
let buildIds = entries => {
|
||||||
|
let genId = (title, path) => {
|
||||||
|
let noSpaces = Js.String.replaceByRe(%re("/\\s+/g"), "-", title)
|
||||||
|
if !HS.has(entriesByPath, path ++ ("/" ++ noSpaces)) {
|
||||||
|
noSpaces
|
||||||
|
} else {
|
||||||
|
let rec loop = num => {
|
||||||
|
let testId = noSpaces ++ ("-" ++ string_of_int(num))
|
||||||
|
if !HS.has(entriesByPath, path ++ ("/" ++ testId)) {
|
||||||
|
testId
|
||||||
|
} else {
|
||||||
|
loop(num + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rec processFolder = (f: folderEntry, curPath) => {
|
||||||
|
f.id = curPath ++ ("/" ++ genId(f.title, curPath))
|
||||||
|
HS.set(entriesByPath, f.id, FolderEntry(f))
|
||||||
|
f.children |> E.L.iter(x =>
|
||||||
|
switch x {
|
||||||
|
| CompEntry(c) => processEntry(c, f.id)
|
||||||
|
| FolderEntry(f) => processFolder(f, f.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
and processEntry = (c: compEntry, curPath) => {
|
||||||
|
c.id = curPath ++ ("/" ++ genId(c.title, curPath))
|
||||||
|
HS.set(entriesByPath, c.id, CompEntry(c))
|
||||||
|
}
|
||||||
|
entries |> E.L.iter(x =>
|
||||||
|
switch x {
|
||||||
|
| CompEntry(c) => processEntry(c, "")
|
||||||
|
| FolderEntry(f) => processFolder(f, "")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries = Entries.entries
|
||||||
|
buildIds(entries)
|
||||||
|
|
||||||
|
module Styles = {
|
||||||
|
open CssJs
|
||||||
|
let pageContainer = style(. [ display(#flex), height(#vh(100.)) ])
|
||||||
|
let leftNav = style(. [ padding(#em(2.)),
|
||||||
|
flexBasis(#px(200)),
|
||||||
|
flexShrink(0.),
|
||||||
|
backgroundColor(#hex("eaeff3")),
|
||||||
|
boxShadows([ Shadow.box(~x=px(-1), ~blur=px(1), ~inset=true, rgba(0, 0, 0, #percent(0.1))) ]),
|
||||||
|
])
|
||||||
|
|
||||||
|
let folderNav = style(. [ selector(.
|
||||||
|
">h4",
|
||||||
|
[ cursor(#pointer), margin2(~v=#em(0.3), ~h=#zero), hover([ color(#hex("7089ad")) ]) ],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
let folderChildren = style(. [ paddingLeft(#px(7)) ])
|
||||||
|
let compNav = style(. [ cursor(#pointer),
|
||||||
|
paddingBottom(#px(3)),
|
||||||
|
hover([ color(#hex("7089ad")) ]),
|
||||||
|
])
|
||||||
|
let compContainer = style(. [ padding(#em(2.)), flexGrow(1.) ])
|
||||||
|
// Approximate sidebar container for entry
|
||||||
|
let sidebarContainer = style(. [ maxWidth(#px(430)) ])
|
||||||
|
let folderChildContainer = style(. [ marginBottom(#em(2.)) ])
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseUrl = "/showcase/index.html"
|
||||||
|
|
||||||
|
module Index = {
|
||||||
|
type state = {route: RescriptReactRouter.url}
|
||||||
|
|
||||||
|
type action =
|
||||||
|
| ItemClick(string)
|
||||||
|
| ChangeRoute(RescriptReactRouter.url)
|
||||||
|
|
||||||
|
let changeId = (id: string) => {
|
||||||
|
let _ = RescriptReactRouter.push(baseUrl ++ ("#" ++ id))
|
||||||
|
}
|
||||||
|
|
||||||
|
let buildNav = _ => {
|
||||||
|
let rec buildFolder = (f: folderEntry) =>
|
||||||
|
<div key=f.id style=Styles.folderNav>
|
||||||
|
<h4 onClick={_e => changeId(f.id)}> {f.title->React.string} </h4>
|
||||||
|
<div style=Styles.folderChildren>
|
||||||
|
{(f.children
|
||||||
|
|> E.L.fmap(e =>
|
||||||
|
switch e {
|
||||||
|
| FolderEntry(folder) => buildFolder(folder)
|
||||||
|
| CompEntry(entry) => buildEntry(entry)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.L.toArray)->React.array}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
and buildEntry = (e: compEntry) =>
|
||||||
|
<div key=e.id style=Styles.compNav onClick={_e => changeId(e.id)}>
|
||||||
|
{e.title->React.string}
|
||||||
|
</div>
|
||||||
|
(entries
|
||||||
|
|> E.L.fmap(e =>
|
||||||
|
switch e {
|
||||||
|
| FolderEntry(folder) => buildFolder(folder)
|
||||||
|
| CompEntry(entry) => buildEntry(entry)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.L.toArray)->React.array
|
||||||
|
}
|
||||||
|
|
||||||
|
let renderEntry = e =>
|
||||||
|
switch e.container {
|
||||||
|
| FullWidth => e.render()
|
||||||
|
| Sidebar => <div style=Styles.sidebarContainer> {e.render()} </div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = () => {
|
||||||
|
let (route, setRoute) = React.useState(() => {
|
||||||
|
let url: RescriptReactRouter.url = {path: list{}, hash: "", search: ""}
|
||||||
|
url
|
||||||
|
})
|
||||||
|
|
||||||
|
React.useState(() => {
|
||||||
|
let _ = RescriptReactRouter.watchUrl(url => setRoute(_ => url))
|
||||||
|
}) |> ignore
|
||||||
|
|
||||||
|
<div style=Styles.pageContainer>
|
||||||
|
<div style=Styles.leftNav> {buildNav(setRoute)} </div>
|
||||||
|
<div style=Styles.compContainer>
|
||||||
|
{if route.hash == "" {
|
||||||
|
React.null
|
||||||
|
} else {
|
||||||
|
switch HS.get(entriesByPath, route.hash) {
|
||||||
|
| Some(navEntry) =>
|
||||||
|
switch navEntry {
|
||||||
|
| CompEntry(c) => renderEntry(c)
|
||||||
|
| FolderEntry(f) =>
|
||||||
|
/* Rendering immediate children */
|
||||||
|
(f.children
|
||||||
|
|> E.L.fmap(child =>
|
||||||
|
switch child {
|
||||||
|
| CompEntry(c) =>
|
||||||
|
<div style=Styles.folderChildContainer key=c.id> {renderEntry(c)} </div>
|
||||||
|
| _ => React.null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.L.toArray)->React.array
|
||||||
|
}
|
||||||
|
| None => <div> {"Component not found"->React.string} </div>
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
5
packages/playground/showcase/ShowcaseIndex.res
Normal file
5
packages/playground/showcase/ShowcaseIndex.res
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
switch(ReactDOM.querySelector("main")){
|
||||||
|
| Some(root) => ReactDOM.render(<div> <Lib.Index /> </div>, root)
|
||||||
|
| None => () // do nothing
|
||||||
|
}
|
||||||
|
RescriptReactRouter.push("")
|
24
packages/playground/showcase/index.html
Normal file
24
packages/playground/showcase/index.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
||||||
|
<link href="../src/styles/index.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Showcase</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="main"></div>
|
||||||
|
<script src=" ./ShowcaseIndex.bs.js "></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
6
packages/playground/src/Antd/Antd.res
Normal file
6
packages/playground/src/Antd/Antd.res
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module Input = Antd_Input
|
||||||
|
module Grid = Antd_Grid
|
||||||
|
module Form = Antd_Form
|
||||||
|
module Card = Antd_Card
|
||||||
|
module Button = Antd_Button
|
||||||
|
module IconName = Antd_IconName
|
38
packages/playground/src/Antd/Antd_Button.res
Normal file
38
packages/playground/src/Antd/Antd_Button.res
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
|
||||||
|
@deriving(abstract)
|
||||||
|
type props
|
||||||
|
type makeType = props => React.element
|
||||||
|
|
||||||
|
@obj external makeProps:
|
||||||
|
(
|
||||||
|
~disabled: bool=?,
|
||||||
|
~ghost: bool=?,
|
||||||
|
~href: string=?,
|
||||||
|
~htmlType: @string [#button | #submit | #submit]=?,
|
||||||
|
~icon: 'a=?,
|
||||||
|
~shape: @string [#circle | #round]=?,
|
||||||
|
~size: @string [#small | #large]=?,
|
||||||
|
~target: string=?,
|
||||||
|
~loading: bool=?,
|
||||||
|
~_type: @string
|
||||||
|
[
|
||||||
|
| #primary
|
||||||
|
| #default
|
||||||
|
| #dashed
|
||||||
|
| #danger
|
||||||
|
| #link
|
||||||
|
| #ghost
|
||||||
|
]=?,
|
||||||
|
~onClick: ReactEvent.Mouse.t => unit=?,
|
||||||
|
~block: bool=?,
|
||||||
|
~children: React.element=?,
|
||||||
|
~className: string=?,
|
||||||
|
~id: string=?,
|
||||||
|
~testId: string=?,
|
||||||
|
unit
|
||||||
|
) =>
|
||||||
|
props =
|
||||||
|
""
|
||||||
|
|
||||||
|
@module("antd")
|
||||||
|
external make : makeType = "Button"
|
32
packages/playground/src/Antd/Antd_Card.res
Normal file
32
packages/playground/src/Antd/Antd_Card.res
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
@deriving(abstract)
|
||||||
|
type props
|
||||||
|
type makeType = props => React.element
|
||||||
|
|
||||||
|
@obj external makeProps: (
|
||||||
|
~actions: array<React.element>=?,
|
||||||
|
~activeTabKey: string=?,
|
||||||
|
~headStyle: ReactDOMStyle.t=?,
|
||||||
|
~bodyStyle: ReactDOMStyle.t=?,
|
||||||
|
~style: ReactDOMStyle.t=?,
|
||||||
|
~bordered: bool=?,
|
||||||
|
~cover: React.element=?,
|
||||||
|
~defaultActiveTabKey: string=?,
|
||||||
|
~extra: React.element=?,
|
||||||
|
~hoverable: bool=?,
|
||||||
|
~loading: bool=?,
|
||||||
|
~tabList: array<{
|
||||||
|
"key": string,
|
||||||
|
"tab": React.element,
|
||||||
|
}>
|
||||||
|
=?,
|
||||||
|
~size: @string [ #default | #small]=?,
|
||||||
|
~title: 'a=?,
|
||||||
|
~_type: string=?,
|
||||||
|
~onTabChange: string => unit=?,
|
||||||
|
~children: React.element=?,
|
||||||
|
unit // This unit is a quirk of the type system. Apparently it must exist to have optional arguments in a type
|
||||||
|
) => props = ""
|
||||||
|
|
||||||
|
@module("antd")
|
||||||
|
external make : makeType = "Card"
|
52
packages/playground/src/Antd/Antd_Form.res
Normal file
52
packages/playground/src/Antd/Antd_Form.res
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
@deriving(abstract)
|
||||||
|
type props
|
||||||
|
type makeType = props => React.element
|
||||||
|
|
||||||
|
@obj
|
||||||
|
external makeProps:
|
||||||
|
(
|
||||||
|
~onSubmit: ReactEvent.Form.t => unit=?,
|
||||||
|
~hideRequiredMark: bool=?,
|
||||||
|
~id: string=?,
|
||||||
|
~className: string=?,
|
||||||
|
~style: ReactDOMStyle.t=?,
|
||||||
|
~colon: bool=?,
|
||||||
|
~validateStatus: string=?,
|
||||||
|
~extra: string=?,
|
||||||
|
~required: bool=?,
|
||||||
|
~label: string=?,
|
||||||
|
~help: string=?,
|
||||||
|
~hasFeedback: bool=?,
|
||||||
|
~children:React.element=?,
|
||||||
|
unit
|
||||||
|
) =>
|
||||||
|
props =
|
||||||
|
""
|
||||||
|
|
||||||
|
|
||||||
|
@module("antd")
|
||||||
|
external make : makeType = "Form"
|
||||||
|
|
||||||
|
module Item = {
|
||||||
|
type props
|
||||||
|
type makeType = props => React.element
|
||||||
|
@obj
|
||||||
|
external makeProps:
|
||||||
|
(
|
||||||
|
~colon:string=?,
|
||||||
|
~validateStatus:string=?,
|
||||||
|
~extra:string=?,
|
||||||
|
~className:string=?,
|
||||||
|
~required:bool=?,
|
||||||
|
~style:ReactDOMStyle.t=?,
|
||||||
|
~label:string=?,
|
||||||
|
~id:string=?,
|
||||||
|
~help:string=?,
|
||||||
|
~hasFeedback:bool=?,
|
||||||
|
~children:React.element=?,
|
||||||
|
unit
|
||||||
|
) => props = ""
|
||||||
|
@module("antd")
|
||||||
|
external make : makeType = "Form.Item"
|
||||||
|
}
|
58
packages/playground/src/Antd/Antd_Grid.res
Normal file
58
packages/playground/src/Antd/Antd_Grid.res
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
|
||||||
|
module Row = {
|
||||||
|
type props
|
||||||
|
|
||||||
|
@obj
|
||||||
|
external makeProps:
|
||||||
|
(
|
||||||
|
~className: string=?,
|
||||||
|
~_type: string=?,
|
||||||
|
~align: string=?,
|
||||||
|
~justify: string=?,
|
||||||
|
~gutter: 'a=?,
|
||||||
|
~style: ReactDOMStyle.t=?,
|
||||||
|
~prefixCls: string=?,
|
||||||
|
~children: React.element=?,
|
||||||
|
unit
|
||||||
|
) =>
|
||||||
|
props =
|
||||||
|
"";
|
||||||
|
|
||||||
|
type makeType = props => React.element
|
||||||
|
|
||||||
|
@module("antd")
|
||||||
|
external make : makeType = "Row"
|
||||||
|
}
|
||||||
|
|
||||||
|
module Col = {
|
||||||
|
type props
|
||||||
|
|
||||||
|
@obj
|
||||||
|
external makeProps:
|
||||||
|
(
|
||||||
|
~className: string=?,
|
||||||
|
~span: int=?,
|
||||||
|
~order: int=?,
|
||||||
|
~offset: int=?,
|
||||||
|
~push: int=?,
|
||||||
|
~pull: int=?,
|
||||||
|
~xs: 'a=?,
|
||||||
|
~sm: 'b=?,
|
||||||
|
~md: 'c=?,
|
||||||
|
~lg: 'd=?,
|
||||||
|
~xl: 'e=?,
|
||||||
|
~xxl: 'f=?,
|
||||||
|
~prefixCls: string=?,
|
||||||
|
~style: ReactDOMStyle.t=?,
|
||||||
|
~children: React.element=?,
|
||||||
|
unit
|
||||||
|
) =>
|
||||||
|
props =
|
||||||
|
"";
|
||||||
|
|
||||||
|
type makeType = props => React.element
|
||||||
|
|
||||||
|
@module("antd")
|
||||||
|
external make : makeType = "Col"
|
||||||
|
}
|
581
packages/playground/src/Antd/Antd_IconName.res
Normal file
581
packages/playground/src/Antd/Antd_IconName.res
Normal file
|
@ -0,0 +1,581 @@
|
||||||
|
type t = string
|
||||||
|
|
||||||
|
let toString = t => t
|
||||||
|
|
||||||
|
let fromString = t => t
|
||||||
|
|
||||||
|
let compare = (t1, t2) => t1 == t2
|
||||||
|
|
||||||
|
let stepBackward = "step-backward"
|
||||||
|
|
||||||
|
let stepForward = "step-forward"
|
||||||
|
|
||||||
|
let fastBackward = "fast-backward"
|
||||||
|
|
||||||
|
let fastForward = "fast-forward"
|
||||||
|
|
||||||
|
let shrink = "shrink"
|
||||||
|
|
||||||
|
let arrowsAlt = "arrows-alt"
|
||||||
|
|
||||||
|
let down = "down"
|
||||||
|
|
||||||
|
let up = "up"
|
||||||
|
|
||||||
|
let left = "left"
|
||||||
|
|
||||||
|
let right = "right"
|
||||||
|
|
||||||
|
let caretUp = "caret-up"
|
||||||
|
|
||||||
|
let caretDown = "caret-down"
|
||||||
|
|
||||||
|
let caretLeft = "caret-left"
|
||||||
|
|
||||||
|
let caretRight = "caret-right"
|
||||||
|
|
||||||
|
let upCircle = "up-circle"
|
||||||
|
|
||||||
|
let downCircle = "down-circle"
|
||||||
|
|
||||||
|
let leftCircle = "left-circle"
|
||||||
|
|
||||||
|
let rightCircle = "right-circle"
|
||||||
|
|
||||||
|
let upCircleO = "up-circle-o"
|
||||||
|
|
||||||
|
let downCircleO = "down-circle-o"
|
||||||
|
|
||||||
|
let rightCircleO = "right-circle-o"
|
||||||
|
|
||||||
|
let leftCircleO = "left-circle-o"
|
||||||
|
|
||||||
|
let doubleRight = "double-right"
|
||||||
|
|
||||||
|
let doubleLeft = "double-left"
|
||||||
|
|
||||||
|
let verticleLeft = "verticle-left"
|
||||||
|
|
||||||
|
let verticleRight = "verticle-right"
|
||||||
|
|
||||||
|
let forward = "forward"
|
||||||
|
|
||||||
|
let backward = "backward"
|
||||||
|
|
||||||
|
let rollback = "rollback"
|
||||||
|
|
||||||
|
let enter = "enter"
|
||||||
|
|
||||||
|
let retweet = "retweet"
|
||||||
|
|
||||||
|
let swap = "swap"
|
||||||
|
|
||||||
|
let swapLeft = "swap-left"
|
||||||
|
|
||||||
|
let swapRight = "swap-right"
|
||||||
|
|
||||||
|
let arrowUp = "arrow-up"
|
||||||
|
|
||||||
|
let arrowDown = "arrow-down"
|
||||||
|
|
||||||
|
let arrowLeft = "arrow-left"
|
||||||
|
|
||||||
|
let arrowRight = "arrow-right"
|
||||||
|
|
||||||
|
let playCircle = "play-circle"
|
||||||
|
|
||||||
|
let playCircleO = "play-circle-o"
|
||||||
|
|
||||||
|
let upSquare = "up-square"
|
||||||
|
|
||||||
|
let downSquare = "down-square"
|
||||||
|
|
||||||
|
let leftSquare = "left-square"
|
||||||
|
|
||||||
|
let rightSquare = "right-square"
|
||||||
|
|
||||||
|
let upSquareO = "up-square-o"
|
||||||
|
|
||||||
|
let downSquareO = "down-square-o"
|
||||||
|
|
||||||
|
let leftSquareO = "left-square-o"
|
||||||
|
|
||||||
|
let rightSquareO = "right-square-o"
|
||||||
|
|
||||||
|
let login = "login"
|
||||||
|
|
||||||
|
let logout = "logout"
|
||||||
|
|
||||||
|
let menuFold = "menu-fold"
|
||||||
|
|
||||||
|
let menuUnfold = "menu-unfold"
|
||||||
|
|
||||||
|
let question = "question"
|
||||||
|
|
||||||
|
let questionCircleO = "question-circle-o"
|
||||||
|
|
||||||
|
let questionCircle = "question-circle"
|
||||||
|
|
||||||
|
let plus = "plus"
|
||||||
|
|
||||||
|
let plusCircleO = "plus-circle-o"
|
||||||
|
|
||||||
|
let plusCircle = "plus-circle"
|
||||||
|
|
||||||
|
let pause = "pause"
|
||||||
|
|
||||||
|
let pauseCircleO = "pause-circle-o"
|
||||||
|
|
||||||
|
let pauseCircle = "pause-circle"
|
||||||
|
|
||||||
|
let minus = "minus"
|
||||||
|
|
||||||
|
let minusCircleO = "minus-circle-o"
|
||||||
|
|
||||||
|
let minusCircle = "minus-circle"
|
||||||
|
|
||||||
|
let plusSquare = "plus-square"
|
||||||
|
|
||||||
|
let plusSquareO = "plus-square-o"
|
||||||
|
|
||||||
|
let minusSquare = "minus-square"
|
||||||
|
|
||||||
|
let minusSquareO = "minus-square-o"
|
||||||
|
|
||||||
|
let info = "info"
|
||||||
|
|
||||||
|
let infoCircleO = "info-circle-o"
|
||||||
|
|
||||||
|
let infoCircle = "info-circle"
|
||||||
|
|
||||||
|
let exclamation = "exclamation"
|
||||||
|
|
||||||
|
let exclamationCircleO = "exclamation-circle-o"
|
||||||
|
|
||||||
|
let exclamationCircle = "exclamation-circle"
|
||||||
|
|
||||||
|
let close = "close"
|
||||||
|
|
||||||
|
let closeCircle = "close-circle"
|
||||||
|
|
||||||
|
let closeCircleO = "close-circle-o"
|
||||||
|
|
||||||
|
let closeSquare = "close-square"
|
||||||
|
|
||||||
|
let closeSquareO = "close-square-o"
|
||||||
|
|
||||||
|
let check = "check"
|
||||||
|
|
||||||
|
let checkCircle = "check-circle"
|
||||||
|
|
||||||
|
let checkCircleO = "check-circle-o"
|
||||||
|
|
||||||
|
let checkSquare = "check-square"
|
||||||
|
|
||||||
|
let checkSquareO = "check-square-o"
|
||||||
|
|
||||||
|
let clockCircleO = "clock-circle-o"
|
||||||
|
|
||||||
|
let clockCircle = "clock-circle"
|
||||||
|
|
||||||
|
let warning = "warning"
|
||||||
|
|
||||||
|
let lock = "lock"
|
||||||
|
|
||||||
|
let unlock = "unlock"
|
||||||
|
|
||||||
|
let areaChart = "area-chart"
|
||||||
|
|
||||||
|
let pieChart = "pie-chart"
|
||||||
|
|
||||||
|
let barChart = "bar-chart"
|
||||||
|
|
||||||
|
let dotChart = "dot-chart"
|
||||||
|
|
||||||
|
let bars = "bars"
|
||||||
|
|
||||||
|
let book = "book"
|
||||||
|
|
||||||
|
let calendar = "calendar"
|
||||||
|
|
||||||
|
let cloud = "cloud"
|
||||||
|
|
||||||
|
let cloudDownload = "cloud-download"
|
||||||
|
|
||||||
|
let code = "code"
|
||||||
|
|
||||||
|
let codeO = "code-o"
|
||||||
|
|
||||||
|
let copy = "copy"
|
||||||
|
|
||||||
|
let creditCard = "credit-card"
|
||||||
|
|
||||||
|
let delete = "delete"
|
||||||
|
|
||||||
|
let desktop = "desktop"
|
||||||
|
|
||||||
|
let download = "download"
|
||||||
|
|
||||||
|
let edit = "edit"
|
||||||
|
|
||||||
|
let ellipsis = "ellipsis"
|
||||||
|
|
||||||
|
let file = "file"
|
||||||
|
|
||||||
|
let fileText = "file-text"
|
||||||
|
|
||||||
|
let fileUnknown = "file-unknown"
|
||||||
|
|
||||||
|
let filePdf = "file-pdf"
|
||||||
|
|
||||||
|
let fileWord = "file-word"
|
||||||
|
|
||||||
|
let fileExcel = "file-excel"
|
||||||
|
|
||||||
|
let fileJpg = "file-jpg"
|
||||||
|
|
||||||
|
let filePpt = "file-ppt"
|
||||||
|
|
||||||
|
let fileMarkdown = "file-markdown"
|
||||||
|
|
||||||
|
let fileAdd = "file-add"
|
||||||
|
|
||||||
|
let folder = "folder"
|
||||||
|
|
||||||
|
let folderOpen = "folder-open"
|
||||||
|
|
||||||
|
let folderAdd = "folder-add"
|
||||||
|
|
||||||
|
let hdd = "hdd"
|
||||||
|
|
||||||
|
let frown = "frown"
|
||||||
|
|
||||||
|
let frownO = "frown-o"
|
||||||
|
|
||||||
|
let meh = "meh"
|
||||||
|
|
||||||
|
let mehO = "meh-o"
|
||||||
|
|
||||||
|
let smile = "smile"
|
||||||
|
|
||||||
|
let smileO = "smile-o"
|
||||||
|
|
||||||
|
let inbox = "inbox"
|
||||||
|
|
||||||
|
let laptop = "laptop"
|
||||||
|
|
||||||
|
let appstoreO = "appstore-o"
|
||||||
|
|
||||||
|
let appstore = "appstore"
|
||||||
|
|
||||||
|
let lineChart = "line-chart"
|
||||||
|
|
||||||
|
let link = "link"
|
||||||
|
|
||||||
|
let mail = "mail"
|
||||||
|
|
||||||
|
let mobile = "mobile"
|
||||||
|
|
||||||
|
let notification = "notification"
|
||||||
|
|
||||||
|
let paperClip = "paper-clip"
|
||||||
|
|
||||||
|
let picture = "picture"
|
||||||
|
|
||||||
|
let poweroff = "poweroff"
|
||||||
|
|
||||||
|
let reload = "reload"
|
||||||
|
|
||||||
|
let search = "search"
|
||||||
|
|
||||||
|
let setting = "setting"
|
||||||
|
|
||||||
|
let shareAlt = "share-alt"
|
||||||
|
|
||||||
|
let shoppingCart = "shopping-cart"
|
||||||
|
|
||||||
|
let tablet = "tablet"
|
||||||
|
|
||||||
|
let tag = "tag"
|
||||||
|
|
||||||
|
let tagO = "tag-o"
|
||||||
|
|
||||||
|
let tags = "tags"
|
||||||
|
|
||||||
|
let tagsO = "tags-o"
|
||||||
|
|
||||||
|
let toTop = "to-top"
|
||||||
|
|
||||||
|
let upload = "upload"
|
||||||
|
|
||||||
|
let user = "user"
|
||||||
|
|
||||||
|
let videoCamera = "video-camera"
|
||||||
|
|
||||||
|
let home = "home"
|
||||||
|
|
||||||
|
let loading = "loading"
|
||||||
|
|
||||||
|
let loading3Quarters = "loading-3-quarters"
|
||||||
|
|
||||||
|
let cloudUploadO = "cloud-upload-o"
|
||||||
|
|
||||||
|
let cloudDownloadO = "cloud-download-o"
|
||||||
|
|
||||||
|
let cloudUpload = "cloud-upload"
|
||||||
|
|
||||||
|
let cloudO = "cloud-o"
|
||||||
|
|
||||||
|
let starO = "star-o"
|
||||||
|
|
||||||
|
let star = "star"
|
||||||
|
|
||||||
|
let heartO = "heart-o"
|
||||||
|
|
||||||
|
let heart = "heart"
|
||||||
|
|
||||||
|
let environment = "environment"
|
||||||
|
|
||||||
|
let environmentO = "environment-o"
|
||||||
|
|
||||||
|
let eye = "eye"
|
||||||
|
|
||||||
|
let eyeO = "eye-o"
|
||||||
|
|
||||||
|
let camera = "camera"
|
||||||
|
|
||||||
|
let cameraO = "camera-o"
|
||||||
|
|
||||||
|
let save = "save"
|
||||||
|
|
||||||
|
let team = "team"
|
||||||
|
|
||||||
|
let solution = "solution"
|
||||||
|
|
||||||
|
let phone = "phone"
|
||||||
|
|
||||||
|
let filter = "filter"
|
||||||
|
|
||||||
|
let exception_ = "exception"
|
||||||
|
|
||||||
|
let \"export" = "export"
|
||||||
|
|
||||||
|
let customerService = "customer-service"
|
||||||
|
|
||||||
|
let qrcode = "qrcode"
|
||||||
|
|
||||||
|
let scan = "scan"
|
||||||
|
|
||||||
|
let like = "like"
|
||||||
|
|
||||||
|
let likeO = "like-o"
|
||||||
|
|
||||||
|
let dislike = "dislike"
|
||||||
|
|
||||||
|
let dislikeO = "dislike-o"
|
||||||
|
|
||||||
|
let message = "message"
|
||||||
|
|
||||||
|
let payCircle = "pay-circle"
|
||||||
|
|
||||||
|
let payCircleO = "pay-circle-o"
|
||||||
|
|
||||||
|
let calculator = "calculator"
|
||||||
|
|
||||||
|
let pushpin = "pushpin"
|
||||||
|
|
||||||
|
let pushpinO = "pushpin-o"
|
||||||
|
|
||||||
|
let bulb = "bulb"
|
||||||
|
|
||||||
|
let select = "select"
|
||||||
|
|
||||||
|
let switcher = "switcher"
|
||||||
|
|
||||||
|
let rocket = "rocket"
|
||||||
|
|
||||||
|
let bell = "bell"
|
||||||
|
|
||||||
|
let disconnect = "disconnect"
|
||||||
|
|
||||||
|
let database = "database"
|
||||||
|
|
||||||
|
let compass = "compass"
|
||||||
|
|
||||||
|
let barcode = "barcode"
|
||||||
|
|
||||||
|
let hourglass = "hourglass"
|
||||||
|
|
||||||
|
let key = "key"
|
||||||
|
|
||||||
|
let flag = "flag"
|
||||||
|
|
||||||
|
let layout = "layout"
|
||||||
|
|
||||||
|
let printer = "printer"
|
||||||
|
|
||||||
|
let sound = "sound"
|
||||||
|
|
||||||
|
let usb = "usb"
|
||||||
|
|
||||||
|
let skin = "skin"
|
||||||
|
|
||||||
|
let tool = "tool"
|
||||||
|
|
||||||
|
let sync = "sync"
|
||||||
|
|
||||||
|
let wifi = "wifi"
|
||||||
|
|
||||||
|
let car = "car"
|
||||||
|
|
||||||
|
let schedule = "schedule"
|
||||||
|
|
||||||
|
let userAdd = "user-add"
|
||||||
|
|
||||||
|
let userDelete = "user-delete"
|
||||||
|
|
||||||
|
let usergroupAdd = "usergroup-add"
|
||||||
|
|
||||||
|
let usergroupDelete = "usergroup-delete"
|
||||||
|
|
||||||
|
let man = "man"
|
||||||
|
|
||||||
|
let woman = "woman"
|
||||||
|
|
||||||
|
let shop = "shop"
|
||||||
|
|
||||||
|
let gift = "gift"
|
||||||
|
|
||||||
|
let idcard = "idcard"
|
||||||
|
|
||||||
|
let medicineBox = "medicine-box"
|
||||||
|
|
||||||
|
let redEnvelope = "red-envelope"
|
||||||
|
|
||||||
|
let coffee = "coffee"
|
||||||
|
|
||||||
|
let copyright = "copyright"
|
||||||
|
|
||||||
|
let trademark = "trademark"
|
||||||
|
|
||||||
|
let safety = "safety"
|
||||||
|
|
||||||
|
let wallet = "wallet"
|
||||||
|
|
||||||
|
let bank = "bank"
|
||||||
|
|
||||||
|
let trophy = "trophy"
|
||||||
|
|
||||||
|
let contacts = "contacts"
|
||||||
|
|
||||||
|
let global = "global"
|
||||||
|
|
||||||
|
let shake = "shake"
|
||||||
|
|
||||||
|
let api = "api"
|
||||||
|
|
||||||
|
let fork = "fork"
|
||||||
|
|
||||||
|
let dashboard = "dashboard"
|
||||||
|
|
||||||
|
let form = "form"
|
||||||
|
|
||||||
|
let table = "table"
|
||||||
|
|
||||||
|
let profile = "profile"
|
||||||
|
|
||||||
|
let android = "android"
|
||||||
|
|
||||||
|
let androidO = "android-o"
|
||||||
|
|
||||||
|
let apple = "apple"
|
||||||
|
|
||||||
|
let appleO = "apple-o"
|
||||||
|
|
||||||
|
let windows = "windows"
|
||||||
|
|
||||||
|
let windowsO = "windows-o"
|
||||||
|
|
||||||
|
let ie = "ie"
|
||||||
|
|
||||||
|
let chrome = "chrome"
|
||||||
|
|
||||||
|
let github = "github"
|
||||||
|
|
||||||
|
let aliwangwang = "aliwangwang"
|
||||||
|
|
||||||
|
let aliwangwangO = "aliwangwang-o"
|
||||||
|
|
||||||
|
let dingding = "dingding"
|
||||||
|
|
||||||
|
let dingdingO = "dingding-o"
|
||||||
|
|
||||||
|
let weiboSquare = "weibo-square"
|
||||||
|
|
||||||
|
let weiboCircle = "weibo-circle"
|
||||||
|
|
||||||
|
let taobaoCircle = "taobao-circle"
|
||||||
|
|
||||||
|
let html5 = "html5"
|
||||||
|
|
||||||
|
let weibo = "weibo"
|
||||||
|
|
||||||
|
let twitter = "twitter"
|
||||||
|
|
||||||
|
let wechat = "wechat"
|
||||||
|
|
||||||
|
let youtube = "youtube"
|
||||||
|
|
||||||
|
let alipayCircle = "alipay-circle"
|
||||||
|
|
||||||
|
let taobao = "taobao"
|
||||||
|
|
||||||
|
let skype = "skype"
|
||||||
|
|
||||||
|
let qq = "qq"
|
||||||
|
|
||||||
|
let mediumWorkmark = "medium-workmark"
|
||||||
|
|
||||||
|
let gitlab = "gitlab"
|
||||||
|
|
||||||
|
let medium = "medium"
|
||||||
|
|
||||||
|
let linkedin = "linkedin"
|
||||||
|
|
||||||
|
let googlePlus = "google-plus"
|
||||||
|
|
||||||
|
let dropbox = "dropbox"
|
||||||
|
|
||||||
|
let facebook = "facebook"
|
||||||
|
|
||||||
|
let codepen = "codepen"
|
||||||
|
|
||||||
|
let amazon = "amazon"
|
||||||
|
|
||||||
|
let google = "google"
|
||||||
|
|
||||||
|
let codepenCircle = "codepen-circle"
|
||||||
|
|
||||||
|
let alipay = "alipay"
|
||||||
|
|
||||||
|
let antDesign = "ant-design"
|
||||||
|
|
||||||
|
let aliyun = "aliyun"
|
||||||
|
|
||||||
|
let zhihu = "zhihu"
|
||||||
|
|
||||||
|
let slack = "slack"
|
||||||
|
|
||||||
|
let slackSquare = "slack-square"
|
||||||
|
|
||||||
|
let behance = "behance"
|
||||||
|
|
||||||
|
let behanceSquare = "behance-square"
|
||||||
|
|
||||||
|
let dribbble = "dribbble"
|
||||||
|
|
||||||
|
let dribbbleSquare = "dribbble-square"
|
||||||
|
|
||||||
|
let instagram = "instagram"
|
||||||
|
|
||||||
|
let yuque = "yuque"
|
21
packages/playground/src/Antd/Antd_Input.res
Normal file
21
packages/playground/src/Antd/Antd_Input.res
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
@deriving(abstract)
|
||||||
|
type props
|
||||||
|
type makeType = props => React.element
|
||||||
|
|
||||||
|
@obj external makeProps: (
|
||||||
|
@as("type")
|
||||||
|
~htmlType: string=?,
|
||||||
|
~name: string=?,
|
||||||
|
~value: string=?,
|
||||||
|
~defaultValue: string=?,
|
||||||
|
~onChange: ReactEvent.Form.t => unit=?,
|
||||||
|
~onPressEnter: ReactEvent.Keyboard.t => unit=?,
|
||||||
|
~onBlur: ReactEvent.Focus.t => unit=?,
|
||||||
|
~className: string=?,
|
||||||
|
~style: ReactDOMStyle.t=?,
|
||||||
|
~placeholder: string=?,
|
||||||
|
unit // This unit is a quirk of the type system. Apparently it must exist to have optional arguments in a type
|
||||||
|
) => props = ""
|
||||||
|
|
||||||
|
@module("antd")
|
||||||
|
external make : makeType = "Input"
|
78
packages/playground/src/App.res
Normal file
78
packages/playground/src/App.res
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
type route =
|
||||||
|
| DistBuilder
|
||||||
|
| Home
|
||||||
|
| NotFound
|
||||||
|
|
||||||
|
let routeToPath = route =>
|
||||||
|
switch route {
|
||||||
|
| DistBuilder => "/dist-builder"
|
||||||
|
| Home => "/"
|
||||||
|
| _ => "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
module Menu = {
|
||||||
|
module Styles = {
|
||||||
|
open CssJs
|
||||||
|
let menu = style(. [ position(#relative),
|
||||||
|
marginTop(em(0.25)),
|
||||||
|
marginBottom(em(0.25)),
|
||||||
|
selector(.
|
||||||
|
"a",
|
||||||
|
[ borderRadius(em(0.25)),
|
||||||
|
display(#inlineBlock),
|
||||||
|
backgroundColor(#hex("eee")),
|
||||||
|
padding(em(1.)),
|
||||||
|
cursor(#pointer),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
selector(. "a:hover", [ backgroundColor(#hex("bfcad4")) ]),
|
||||||
|
selector(. "a:hover", [ backgroundColor(#hex("bfcad4")) ]),
|
||||||
|
selector(.
|
||||||
|
"a:not(:firstChild):not(:lastChild)",
|
||||||
|
[ marginRight(em(0.25)), marginLeft(em(0.25)) ],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
module Item = {
|
||||||
|
@react.component
|
||||||
|
let make = (~href, ~children) =>
|
||||||
|
<a
|
||||||
|
href
|
||||||
|
onClick={e => {
|
||||||
|
e->ReactEvent.Synthetic.preventDefault
|
||||||
|
RescriptReactRouter.push(href)
|
||||||
|
}}>
|
||||||
|
children
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = () =>
|
||||||
|
<div style=Styles.menu>
|
||||||
|
<Item href={routeToPath(Home)} key="home"> {"Home" |> R.ste} </Item>
|
||||||
|
<Item href={routeToPath(DistBuilder)} key="dist-builder"> {"Dist Builder" |> 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 = RescriptReactRouter.useUrl()
|
||||||
|
|
||||||
|
let routing = switch url.path {
|
||||||
|
| list{"dist-builder"} => DistBuilder
|
||||||
|
| list{} => Home
|
||||||
|
| _ => NotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
<>
|
||||||
|
<Menu />
|
||||||
|
{switch routing {
|
||||||
|
| DistBuilder => <DistBuilder />
|
||||||
|
| Home => <Home />
|
||||||
|
| _ => fixedLength("Page is not found" |> R.ste)
|
||||||
|
}}
|
||||||
|
</>
|
||||||
|
}
|
44
packages/playground/src/ExampleStyles.re
Normal file
44
packages/playground/src/ExampleStyles.re
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
let reasonReactBlue = "#48a9dc";
|
||||||
|
|
||||||
|
// The {j|...|j} feature is just string interpolation, from
|
||||||
|
// bucklescript.github.io/docs/en/interop-cheatsheet#string-unicode-interpolation
|
||||||
|
// This allows us to conveniently write CSS, together with variables, by
|
||||||
|
// constructing a string
|
||||||
|
let style = {j|
|
||||||
|
body {
|
||||||
|
background-color: rgb(224, 226, 229);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: white;
|
||||||
|
color: $reasonReactBlue;
|
||||||
|
box-shadow: 0 0 0 1px $reasonReactBlue;
|
||||||
|
border: none;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
background-color: $reasonReactBlue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
margin: 12px 0px;
|
||||||
|
box-shadow: 0px 4px 16px rgb(200, 200, 200);
|
||||||
|
width: 720px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.containerTitle {
|
||||||
|
background-color: rgb(242, 243, 245);
|
||||||
|
border-radius: 12px 12px 0px 0px;
|
||||||
|
padding: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.containerContent {
|
||||||
|
background-color: white;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 0px 0px 12px 12px;
|
||||||
|
}
|
||||||
|
|j};
|
21
packages/playground/src/GuesstimatorDist.re
Normal file
21
packages/playground/src/GuesstimatorDist.re
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
let normal = (mean: float, std: float) =>
|
||||||
|
Js.Float.(
|
||||||
|
{
|
||||||
|
let nMean = toPrecisionWithPrecision(mean, ~digits=4);
|
||||||
|
let nStd = toPrecisionWithPrecision(std, ~digits=2);
|
||||||
|
{j|normal($(nMean), $(nStd))|j};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let logNormal = (mean: float, std: float) => {
|
||||||
|
Js.Float.(
|
||||||
|
{
|
||||||
|
let nMean = toPrecisionWithPrecision(mean, ~digits=4);
|
||||||
|
let nStd = toPrecisionWithPrecision(std, ~digits=2);
|
||||||
|
{j|lognormal({mean: $(nMean), stdev: $(nStd)})|j};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let divide = (str1: string, str2: string) => {j|$(str1)/$(str2)|j};
|
||||||
|
let min = (str1: string, str2: string) => {j|min($(str1),$(str2))|j};
|
5
packages/playground/src/Index.res
Normal file
5
packages/playground/src/Index.res
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
%raw(`import('./styles/index.css')`)
|
||||||
|
switch ReactDOM.querySelector("#app") {
|
||||||
|
| Some(root) => ReactDOM.render(<App />, root)
|
||||||
|
| None => ()
|
||||||
|
}
|
8
packages/playground/src/R.res
Normal file
8
packages/playground/src/R.res
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
let ste = React.string
|
||||||
|
let showIf = (cond, comp) => cond ? comp : React.null
|
||||||
|
|
||||||
|
module O = {
|
||||||
|
let defaultNull = E.O.default(React.null)
|
||||||
|
let fmapOrNull = (fn, el) => el |> E.O.fmap(fn) |> E.O.default(React.null)
|
||||||
|
let flatten = E.O.default(React.null)
|
||||||
|
}
|
36
packages/playground/src/components/CodeEditor.js
Normal file
36
packages/playground/src/components/CodeEditor.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
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 function CodeEditor(props) {
|
||||||
|
return (
|
||||||
|
<AceEditor
|
||||||
|
value={props.value}
|
||||||
|
mode="golang"
|
||||||
|
height="400px"
|
||||||
|
width="100%"
|
||||||
|
theme="github"
|
||||||
|
showGutter={false}
|
||||||
|
highlightActiveLine={false}
|
||||||
|
showPrintMargin={false}
|
||||||
|
onChange={props.onChange}
|
||||||
|
name="UNIQUE_ID_OF_DIV"
|
||||||
|
editorProps={{
|
||||||
|
$blockScrolling: true,
|
||||||
|
}}
|
||||||
|
setOptions={{
|
||||||
|
enableBasicAutocompletion: false,
|
||||||
|
enableLiveAutocompletion: true,
|
||||||
|
enableSnippets: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
5
packages/playground/src/components/CodeEditor.res
Normal file
5
packages/playground/src/components/CodeEditor.res
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
type props
|
||||||
|
@obj external makeProps : (~value:string=?, ~onChange: string => unit, ~children: React.element=?, unit) => props = ""
|
||||||
|
|
||||||
|
@module("./CodeEditor.js")
|
||||||
|
external make: props => React.element = "CodeEditor"
|
271
packages/playground/src/components/DistBuilder.res
Normal file
271
packages/playground/src/components/DistBuilder.res
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
open ReForm
|
||||||
|
open Antd.Grid
|
||||||
|
|
||||||
|
module FormConfig = %lenses(
|
||||||
|
type state = {
|
||||||
|
squiggleString: string,
|
||||||
|
sampleCount: string,
|
||||||
|
outputXYPoints: string,
|
||||||
|
downsampleTo: string,
|
||||||
|
kernelWidth: string,
|
||||||
|
diagramStart: string,
|
||||||
|
diagramStop: string,
|
||||||
|
diagramCount: string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type options = {
|
||||||
|
sampleCount: int,
|
||||||
|
outputXYPoints: int,
|
||||||
|
downsampleTo: option<int>,
|
||||||
|
kernelWidth: option<float>,
|
||||||
|
diagramStart: float,
|
||||||
|
diagramStop: float,
|
||||||
|
diagramCount: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
module Form = ReForm.Make(FormConfig)
|
||||||
|
|
||||||
|
module FieldText = {
|
||||||
|
@react.component
|
||||||
|
let make = (~field, ~label) => <>
|
||||||
|
<Form.Field
|
||||||
|
field
|
||||||
|
render={({handleChange, error, value, validate}) =>{
|
||||||
|
Js.Console.log(CodeEditor.make);
|
||||||
|
(<CodeEditor value onChange={r => handleChange(r)} />)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
module FieldString = {
|
||||||
|
@react.component
|
||||||
|
let make = (~field, ~label) =>
|
||||||
|
<Form.Field
|
||||||
|
field
|
||||||
|
render={({handleChange, error, value, validate}) =>
|
||||||
|
<Antd.Form.Item label={label}>
|
||||||
|
<Antd.Input
|
||||||
|
value onChange={ReForm.Helpers.handleChange(handleChange)} onBlur={_ => validate()}
|
||||||
|
/>
|
||||||
|
</Antd.Form.Item>}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
module FieldFloat = {
|
||||||
|
@react.component
|
||||||
|
let make = (~field, ~label, ~className=CssJs.style(. [])) =>
|
||||||
|
<Form.Field
|
||||||
|
field
|
||||||
|
render={({handleChange, error, value, validate}) =>
|
||||||
|
<Antd.Input
|
||||||
|
value
|
||||||
|
onChange={ReForm.Helpers.handleChange(handleChange)}
|
||||||
|
onBlur={_ => validate()}
|
||||||
|
style={className}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
module Styles = {
|
||||||
|
open CssJs
|
||||||
|
let rows = style(. [ selector(. ">.antCol:firstChild", [ paddingLeft(em(0.25)), paddingRight(em(0.125)) ]),
|
||||||
|
selector(. ">.antCol:lastChild", [ paddingLeft(em(0.125)), paddingRight(em(0.25)) ]),
|
||||||
|
selector(.
|
||||||
|
">.antCol:not(:firstChild):not(:lastChild)",
|
||||||
|
[ paddingLeft(em(0.125)), paddingRight(em(0.125)) ],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
let parent = style(. [ selector(. ".antInputNumber", [ width(#percent(100.)) ]),
|
||||||
|
selector(. ".anticon", [ verticalAlign(#zero) ]),
|
||||||
|
])
|
||||||
|
let form = style(. [ backgroundColor(hex("eee")), padding(em(1.)) ])
|
||||||
|
let dist = style(. [ padding(em(1.)) ])
|
||||||
|
let spacer = style(. [ marginTop(em(1.)) ])
|
||||||
|
let groupA = style(. [ selector(. ".antInputNumberInput", [ backgroundColor(hex("fff7db")) ]),
|
||||||
|
])
|
||||||
|
let groupB = style(. [ selector(. ".antInputNumberInput", [ backgroundColor(hex("eaf4ff")) ]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
module DemoDist = {
|
||||||
|
@react.component
|
||||||
|
let make = (~squiggleString: string, ~options) =>
|
||||||
|
<Antd.Card title={"Distribution"}>
|
||||||
|
<div>
|
||||||
|
{switch options {
|
||||||
|
| Some(options) =>
|
||||||
|
let inputs1 = ProgramEvaluator.Inputs.make(
|
||||||
|
~samplingInputs={
|
||||||
|
sampleCount: Some(options.sampleCount),
|
||||||
|
outputXYPoints: Some(options.outputXYPoints),
|
||||||
|
kernelWidth: options.kernelWidth,
|
||||||
|
shapeLength: Some(options.downsampleTo |> E.O.default(1000)),
|
||||||
|
},
|
||||||
|
~squiggleString,
|
||||||
|
~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,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
|
||||||
|
let distributionList = ProgramEvaluator.evaluateProgram(inputs1)
|
||||||
|
|
||||||
|
let renderExpression = response1 =>
|
||||||
|
switch response1 {
|
||||||
|
| #DistPlus(distPlus1) => <DistPlusPlot distPlus={DistPlus.T.normalize(distPlus1)} />
|
||||||
|
| #Float(f) => <NumberShower number=f precision=3 />
|
||||||
|
| #Function((f, a), env) =>
|
||||||
|
// Problem: When it gets the function, it doesn't save state about previous commands
|
||||||
|
let foo: ProgramEvaluator.Inputs.inputs = {
|
||||||
|
squiggleString: squiggleString,
|
||||||
|
samplingInputs: inputs1.samplingInputs,
|
||||||
|
environment: env,
|
||||||
|
}
|
||||||
|
let results =
|
||||||
|
E.A.Floats.range(options.diagramStart, options.diagramStop, options.diagramCount)
|
||||||
|
|> E.A.fmap(r =>
|
||||||
|
ProgramEvaluator.evaluateFunction(
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the list of distributions given by the
|
||||||
|
switch distributionList {
|
||||||
|
| Ok(xs) =>
|
||||||
|
let childrenElements = List.map(renderExpression, xs)
|
||||||
|
Js.Console.log(childrenElements)
|
||||||
|
<ul>
|
||||||
|
{Belt.List.toArray(Belt.List.mapWithIndex(childrenElements, (i, child) => <li key={Belt.Int.toString(i)}>child</li>))->React.array}
|
||||||
|
</ul>
|
||||||
|
| Error(r) => r |> R.ste
|
||||||
|
}
|
||||||
|
| _ => "Nothing to show. Try to change the distribution description." |> R.ste
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</Antd.Card>
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = () => {
|
||||||
|
let (reloader, setReloader) = React.useState(() => 1)
|
||||||
|
let reform = Form.use(
|
||||||
|
~validationStrategy=OnDemand,
|
||||||
|
~schema=Form.Validation.Schema([]),
|
||||||
|
~onSubmit=({state}) => None,
|
||||||
|
~initialState={
|
||||||
|
//squiggleString: "mm(normal(-10, 2), uniform(18, 25), lognormal({mean: 10, stdev: 8}), triangular(31,40,50))",
|
||||||
|
squiggleString: "mm(normal(5,2), normal(10,2))",
|
||||||
|
sampleCount: "1000",
|
||||||
|
outputXYPoints: "1000",
|
||||||
|
downsampleTo: "",
|
||||||
|
kernelWidth: "",
|
||||||
|
diagramStart: "0",
|
||||||
|
diagramStop: "10",
|
||||||
|
diagramCount: "20",
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
|
||||||
|
let onSubmit = e => {
|
||||||
|
e->ReactEvent.Synthetic.preventDefault
|
||||||
|
reform.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
let squiggleString = reform.state.values.squiggleString
|
||||||
|
let sampleCount = reform.state.values.sampleCount |> Js.Float.fromString
|
||||||
|
let outputXYPoints = 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 options = switch (sampleCount, outputXYPoints, downsampleTo) {
|
||||||
|
| (_, _, _)
|
||||||
|
if !Js.Float.isNaN(sampleCount) &&
|
||||||
|
(!Js.Float.isNaN(outputXYPoints) &&
|
||||||
|
(!Js.Float.isNaN(downsampleTo) && (sampleCount > 10. && outputXYPoints > 10.))) =>
|
||||||
|
Some({
|
||||||
|
sampleCount: sampleCount |> int_of_float,
|
||||||
|
outputXYPoints: outputXYPoints |> int_of_float,
|
||||||
|
downsampleTo: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
let demoDist = React.useMemo1(
|
||||||
|
() => <DemoDist squiggleString options />,
|
||||||
|
[
|
||||||
|
reform.state.values.squiggleString,
|
||||||
|
reform.state.values.sampleCount,
|
||||||
|
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 onReload = _ => setReloader(_ => reloader + 1)
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Antd.Card
|
||||||
|
title={"Distribution Form" |> R.ste}
|
||||||
|
extra={<Antd.Button icon=Antd.IconName.reload shape=#circle onClick=onReload />}>
|
||||||
|
<Form.Provider value=reform>
|
||||||
|
<Antd.Form onSubmit>
|
||||||
|
<Row _type="flex" style=Styles.rows>
|
||||||
|
<Col span=24> <FieldText field=FormConfig.SquiggleString label="Program" /> </Col>
|
||||||
|
</Row>
|
||||||
|
<Row _type="flex" style=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>
|
||||||
|
}
|
306
packages/playground/src/components/charts/DistPlusPlot.res
Normal file
306
packages/playground/src/components/charts/DistPlusPlot.res
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
open DistPlusPlotReducer
|
||||||
|
let plotBlue = #hex("1860ad")
|
||||||
|
|
||||||
|
let showAsForm = (distPlus: DistTypes.distPlus) =>
|
||||||
|
<div> <Antd.Input value={distPlus.squiggleString |> E.O.default("")} /> </div>
|
||||||
|
|
||||||
|
let showFloat = (~precision=3, number) => <NumberShower number precision />
|
||||||
|
|
||||||
|
let table = (distPlus, x) =>
|
||||||
|
<div>
|
||||||
|
<table className="table-auto text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 "> {"X Point" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"Discrete Value" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"Continuous Value" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"Y Integral to Point" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"Y Integral Total" |> React.string} </td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 border"> {x |> E.Float.toString |> React.string} </td>
|
||||||
|
<td className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> DistPlus.T.xToY(x)
|
||||||
|
|> DistTypes.MixedPoint.toDiscreteValue
|
||||||
|
|> Js.Float.toPrecisionWithPrecision(_, ~digits=7)
|
||||||
|
|> React.string}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> DistPlus.T.xToY(x)
|
||||||
|
|> DistTypes.MixedPoint.toContinuousValue
|
||||||
|
|> Js.Float.toPrecisionWithPrecision(_, ~digits=7)
|
||||||
|
|> React.string}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> DistPlus.T.Integral.xToY(x)
|
||||||
|
|> E.Float.with2DigitsPrecision
|
||||||
|
|> React.string}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> DistPlus.T.Integral.sum
|
||||||
|
|> E.Float.with2DigitsPrecision
|
||||||
|
|> React.string}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table className="table-auto text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2"> {"Continuous Total" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"Discrete Total" |> React.string} </td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus
|
||||||
|
|> DistPlus.T.toContinuous
|
||||||
|
|> E.O.fmap(Continuous.T.Integral.sum)
|
||||||
|
|> E.O.fmap(E.Float.with2DigitsPrecision)
|
||||||
|
|> E.O.default("")
|
||||||
|
|> React.string}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> DistPlus.T.toDiscrete
|
||||||
|
|> E.O.fmap(Discrete.T.Integral.sum)
|
||||||
|
|> E.O.fmap(E.Float.with2DigitsPrecision)
|
||||||
|
|> E.O.default("")
|
||||||
|
|> React.string}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
let percentiles = distPlus =>
|
||||||
|
<div>
|
||||||
|
<table className="table-auto text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2"> {"1" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"5" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"25" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"50" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"75" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"95" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"99" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"99.999" |> React.string} </td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.01) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.05) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.25) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.5) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.75) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.95) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.99) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.Integral.yToX(0.99999) |> showFloat}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table className="table-auto text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2"> {"mean" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"standard deviation" |> React.string} </td>
|
||||||
|
<td className="px-4 py-2"> {"variance" |> React.string} </td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 border"> {distPlus |> DistPlus.T.mean |> showFloat} </td>
|
||||||
|
<td className="px-4 py-2 border">
|
||||||
|
{distPlus |> DistPlus.T.variance |> (r => r ** 0.5) |> showFloat}
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 border"> {distPlus |> DistPlus.T.variance |> showFloat} </td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
let adjustBoth = discreteProbabilityMassFraction => {
|
||||||
|
let yMaxDiscreteDomainFactor = discreteProbabilityMassFraction
|
||||||
|
let yMaxContinuousDomainFactor = 1.0 -. discreteProbabilityMassFraction
|
||||||
|
|
||||||
|
// use the bigger proportion, such that whichever is the bigger proportion, the yMax is 1.
|
||||||
|
|
||||||
|
let yMax = yMaxDiscreteDomainFactor > 0.5 ? yMaxDiscreteDomainFactor : yMaxContinuousDomainFactor
|
||||||
|
(yMax /. yMaxDiscreteDomainFactor, yMax /. yMaxContinuousDomainFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
module DistPlusChart = {
|
||||||
|
@react.component
|
||||||
|
let make = (~distPlus: DistTypes.distPlus, ~config: chartConfig, ~onHover) => {
|
||||||
|
open DistPlus
|
||||||
|
|
||||||
|
let discrete = distPlus |> T.toDiscrete |> E.O.fmap(Discrete.getShape)
|
||||||
|
let continuous = distPlus |> T.toContinuous |> E.O.fmap(Continuous.getShape)
|
||||||
|
|
||||||
|
// // We subtract a bit from the range to make sure that it fits. Maybe this should be done in d3 instead.
|
||||||
|
// let minX =
|
||||||
|
// switch (
|
||||||
|
// distPlus
|
||||||
|
// |> DistPlus.T.Integral.yToX(0.0001),
|
||||||
|
// range,
|
||||||
|
// ) {
|
||||||
|
// | (min, Some(range)) => Some(min -. range *. 0.001)
|
||||||
|
// | _ => None
|
||||||
|
// };
|
||||||
|
|
||||||
|
let minX = distPlus |> DistPlus.T.Integral.yToX(0.00001)
|
||||||
|
|
||||||
|
let maxX = distPlus |> DistPlus.T.Integral.yToX(0.99999)
|
||||||
|
|
||||||
|
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson
|
||||||
|
let discreteProbabilityMassFraction = distPlus |> DistPlus.T.toDiscreteProbabilityMassFraction
|
||||||
|
|
||||||
|
let (yMaxDiscreteDomainFactor, yMaxContinuousDomainFactor) = adjustBoth(
|
||||||
|
discreteProbabilityMassFraction,
|
||||||
|
)
|
||||||
|
|
||||||
|
<DistributionPlot
|
||||||
|
xScale={config.xLog ? "log" : "linear"}
|
||||||
|
yScale={config.yLog ? "log" : "linear"}
|
||||||
|
height={DistPlusPlotReducer.heightToPix(config.height)}
|
||||||
|
minX
|
||||||
|
maxX
|
||||||
|
yMaxDiscreteDomainFactor
|
||||||
|
yMaxContinuousDomainFactor
|
||||||
|
?discrete
|
||||||
|
?continuous
|
||||||
|
color=plotBlue
|
||||||
|
onHover
|
||||||
|
timeScale
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module IntegralChart = {
|
||||||
|
@react.component
|
||||||
|
let make = (~distPlus: DistTypes.distPlus, ~config: chartConfig, ~onHover) => {
|
||||||
|
let integral = distPlus.integralCache
|
||||||
|
let continuous = integral |> Continuous.toLinear |> E.O.fmap(Continuous.getShape)
|
||||||
|
let minX = distPlus |> DistPlus.T.Integral.yToX(0.00001)
|
||||||
|
|
||||||
|
let maxX = distPlus |> DistPlus.T.Integral.yToX(0.99999)
|
||||||
|
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson
|
||||||
|
<DistributionPlot
|
||||||
|
xScale={config.xLog ? "log" : "linear"}
|
||||||
|
yScale={config.yLog ? "log" : "linear"}
|
||||||
|
height={DistPlusPlotReducer.heightToPix(config.height)}
|
||||||
|
minX
|
||||||
|
maxX
|
||||||
|
?continuous
|
||||||
|
color=plotBlue
|
||||||
|
timeScale
|
||||||
|
onHover
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Chart = {
|
||||||
|
@react.component
|
||||||
|
let make = (~distPlus: DistTypes.distPlus, ~config: chartConfig, ~onHover) => {
|
||||||
|
let chart = React.useMemo2(
|
||||||
|
() =>
|
||||||
|
config.isCumulative
|
||||||
|
? <IntegralChart distPlus config onHover />
|
||||||
|
: <DistPlusChart distPlus config onHover />,
|
||||||
|
(distPlus, config),
|
||||||
|
)
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
open CssJs
|
||||||
|
style(. [ minHeight(#px(DistPlusPlotReducer.heightToPix(config.height))) ])
|
||||||
|
}>
|
||||||
|
chart
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let button = "bg-gray-300 hover:bg-gray-500 text-grey-darkest text-xs px-4 py-1"
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = (~distPlus: DistTypes.distPlus) => {
|
||||||
|
let (x, setX) = React.useState(() => 0.)
|
||||||
|
let (state, dispatch) = React.useReducer(DistPlusPlotReducer.reducer, DistPlusPlotReducer.init)
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{state.distributions
|
||||||
|
|> E.L.fmapi((index, config) =>
|
||||||
|
<div className="flex" key={string_of_int(index)}>
|
||||||
|
<div className="w-4/5"> <Chart distPlus config onHover={r => setX(_ => r)} /> </div>
|
||||||
|
<div className="w-1/5">
|
||||||
|
<div className="opacity-50 hover:opacity-100">
|
||||||
|
<button className=button onClick={_ => dispatch(CHANGE_X_LOG(index))}>
|
||||||
|
{(config.xLog ? "x-log" : "x-linear") |> React.string}
|
||||||
|
</button>
|
||||||
|
<button className=button onClick={_ => dispatch(CHANGE_Y_LOG(index))}>
|
||||||
|
{(config.yLog ? "y-log" : "y-linear") |> React.string}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className=button
|
||||||
|
onClick={_ => dispatch(CHANGE_IS_CUMULATIVE(index, !config.isCumulative))}>
|
||||||
|
{(config.isCumulative ? "cdf" : "pdf") |> React.string}
|
||||||
|
</button>
|
||||||
|
<button className=button onClick={_ => dispatch(HEIGHT_INCREMENT(index))}>
|
||||||
|
{"expand" |> React.string}
|
||||||
|
</button>
|
||||||
|
<button className=button onClick={_ => dispatch(HEIGHT_DECREMENT(index))}>
|
||||||
|
{"shrink" |> React.string}
|
||||||
|
</button>
|
||||||
|
{index != 0
|
||||||
|
? <button className=button onClick={_ => dispatch(REMOVE_DIST(index))}>
|
||||||
|
{"remove" |> React.string}
|
||||||
|
</button>
|
||||||
|
: React.null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|> E.L.toArray
|
||||||
|
|> React.array}
|
||||||
|
<div className="inline-flex opacity-50 hover:opacity-100">
|
||||||
|
<button className=button onClick={_ => dispatch(CHANGE_SHOW_PERCENTILES)}>
|
||||||
|
{"Percentiles" |> React.string}
|
||||||
|
</button>
|
||||||
|
<button className=button onClick={_ => dispatch(CHANGE_SHOW_STATS)}>
|
||||||
|
{"Debug Stats" |> React.string}
|
||||||
|
</button>
|
||||||
|
<button className=button onClick={_ => dispatch(CHANGE_SHOW_PARAMS)}>
|
||||||
|
{"Params" |> React.string}
|
||||||
|
</button>
|
||||||
|
<button className=button onClick={_ => dispatch(ADD_DIST)}>
|
||||||
|
{"Add" |> React.string}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{state.showParams ? showAsForm(distPlus) : React.null}
|
||||||
|
{state.showStats ? table(distPlus, x) : React.null}
|
||||||
|
{state.showPercentiles ? percentiles(distPlus) : React.null}
|
||||||
|
</div>
|
||||||
|
}
|
112
packages/playground/src/components/charts/DistPlusPlotReducer.re
Normal file
112
packages/playground/src/components/charts/DistPlusPlotReducer.re
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
type chartConfig = {
|
||||||
|
xLog: bool,
|
||||||
|
yLog: bool,
|
||||||
|
isCumulative: bool,
|
||||||
|
height: int,
|
||||||
|
};
|
||||||
|
|
||||||
|
type state = {
|
||||||
|
showStats: bool,
|
||||||
|
showPercentiles: bool,
|
||||||
|
showParams: bool,
|
||||||
|
distributions: list(chartConfig),
|
||||||
|
};
|
||||||
|
|
||||||
|
type action =
|
||||||
|
| CHANGE_SHOW_STATS
|
||||||
|
| CHANGE_SHOW_PARAMS
|
||||||
|
| CHANGE_SHOW_PERCENTILES
|
||||||
|
| REMOVE_DIST(int)
|
||||||
|
| ADD_DIST
|
||||||
|
| CHANGE_X_LOG(int)
|
||||||
|
| CHANGE_Y_LOG(int)
|
||||||
|
| CHANGE_IS_CUMULATIVE(int, bool)
|
||||||
|
| HEIGHT_INCREMENT(int)
|
||||||
|
| HEIGHT_DECREMENT(int);
|
||||||
|
|
||||||
|
let changeHeight = (currentHeight, foo: [ | `increment | `decrement]) =>
|
||||||
|
switch (currentHeight, foo) {
|
||||||
|
| (1, `decrement) => 1
|
||||||
|
| (2, `decrement) => 1
|
||||||
|
| (3, `decrement) => 2
|
||||||
|
| (4, `decrement) => 3
|
||||||
|
| (5, `decrement) => 4
|
||||||
|
| (1, `increment) => 2
|
||||||
|
| (2, `increment) => 3
|
||||||
|
| (3, `increment) => 4
|
||||||
|
| (4, `increment) => 5
|
||||||
|
| (5, `increment) => 5
|
||||||
|
| _ => 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let heightToPix =
|
||||||
|
fun
|
||||||
|
| 1 => 80
|
||||||
|
| 2 => 140
|
||||||
|
| 3 => 240
|
||||||
|
| 4 => 340
|
||||||
|
| 5 => 440
|
||||||
|
| _ => 140;
|
||||||
|
|
||||||
|
let distributionReducer = (index, state: list(chartConfig), action) => {
|
||||||
|
switch (action, E.L.get(state, index)) {
|
||||||
|
| (HEIGHT_INCREMENT(_), Some(dist)) =>
|
||||||
|
E.L.update(
|
||||||
|
{...dist, height: changeHeight(dist.height, `increment)},
|
||||||
|
index,
|
||||||
|
state,
|
||||||
|
)
|
||||||
|
| (HEIGHT_DECREMENT(_), Some(dist)) =>
|
||||||
|
E.L.update(
|
||||||
|
{...dist, height: changeHeight(dist.height, `decrement)},
|
||||||
|
index,
|
||||||
|
state,
|
||||||
|
)
|
||||||
|
| (CHANGE_IS_CUMULATIVE(_, isCumulative), Some(dist)) =>
|
||||||
|
E.L.update({...dist, isCumulative}, index, state)
|
||||||
|
| (CHANGE_X_LOG(_), Some(dist)) =>
|
||||||
|
E.L.update({...dist, xLog: !dist.xLog}, index, state)
|
||||||
|
| (CHANGE_Y_LOG(_), Some(dist)) =>
|
||||||
|
E.L.update({...dist, yLog: !dist.yLog}, index, state)
|
||||||
|
| (REMOVE_DIST(_), Some(_)) => E.L.remove(index, 1, state)
|
||||||
|
| (ADD_DIST, Some(_)) =>
|
||||||
|
E.L.append(
|
||||||
|
state,
|
||||||
|
[{yLog: false, xLog: false, isCumulative: false, height: 4}],
|
||||||
|
)
|
||||||
|
| _ => state
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let reducer = (state: state, action: action) =>
|
||||||
|
switch (action) {
|
||||||
|
| CHANGE_X_LOG(i)
|
||||||
|
| CHANGE_Y_LOG(i)
|
||||||
|
| CHANGE_IS_CUMULATIVE(i, _)
|
||||||
|
| HEIGHT_DECREMENT(i)
|
||||||
|
| REMOVE_DIST(i)
|
||||||
|
| HEIGHT_INCREMENT(i) => {
|
||||||
|
...state,
|
||||||
|
distributions: distributionReducer(i, state.distributions, action),
|
||||||
|
}
|
||||||
|
| ADD_DIST => {
|
||||||
|
...state,
|
||||||
|
distributions: distributionReducer(0, state.distributions, action),
|
||||||
|
}
|
||||||
|
| CHANGE_SHOW_STATS => {...state, showStats: !state.showStats}
|
||||||
|
| CHANGE_SHOW_PARAMS => {...state, showParams: !state.showParams}
|
||||||
|
| CHANGE_SHOW_PERCENTILES => {
|
||||||
|
...state,
|
||||||
|
showPercentiles: !state.showPercentiles,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let init = {
|
||||||
|
showStats: false,
|
||||||
|
showParams: false,
|
||||||
|
showPercentiles: false,
|
||||||
|
distributions: [
|
||||||
|
{yLog: false, xLog: false, isCumulative: false, height: 4},
|
||||||
|
{yLog: false, xLog: false, isCumulative: true, height: 1},
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,108 @@
|
||||||
|
module RawPlot = {
|
||||||
|
type primaryDistribution = option<{"xs": array<float>, "ys": array<float>}>
|
||||||
|
|
||||||
|
type discrete = option<{"xs": array<float>, "ys": array<float>}>
|
||||||
|
|
||||||
|
type props
|
||||||
|
type makeType = props => React.element
|
||||||
|
@obj external makeProps: (
|
||||||
|
~height: int=?,
|
||||||
|
~marginBottom: int=?,
|
||||||
|
~marginTop: int=?,
|
||||||
|
~maxX: float=?,
|
||||||
|
~minX: float=?,
|
||||||
|
~yMaxContinuousDomainFactor: float=?,
|
||||||
|
~yMaxDiscreteDomainFactor: float=?,
|
||||||
|
~onHover: float => (),
|
||||||
|
~continuous: option<{"xs": array<float>, "ys": array<float>}>=?,
|
||||||
|
~discrete: option<{"xs": array<float>, "ys": array<float>}>=?,
|
||||||
|
~xScale: string=?,
|
||||||
|
~yScale: string=?,
|
||||||
|
~showDistributionLines: bool=?,
|
||||||
|
~showDistributionYAxis: bool=?,
|
||||||
|
~showVerticalLine: bool=?,
|
||||||
|
~timeScale:Js.Null.t<{"unit": string, "zero": MomentRe.Moment.t}>=?,
|
||||||
|
~verticalLine: int=?,
|
||||||
|
~children: array<React.element>=?,
|
||||||
|
unit // This unit is a quirk of the type system. Apparently it must exist to have optional arguments in a type
|
||||||
|
) => props = ""
|
||||||
|
|
||||||
|
|
||||||
|
@module("./distPlotReact.js")
|
||||||
|
external make : makeType = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
module Styles = {
|
||||||
|
open CssJs
|
||||||
|
let textOverlay = style(. [position(#absolute)])
|
||||||
|
let mainText = style(. [ fontSize(#em(1.1))])
|
||||||
|
let secondaryText = style(. [fontSize(#em(0.9))])
|
||||||
|
|
||||||
|
let graph = chartColor =>
|
||||||
|
style(. [
|
||||||
|
position(#relative),
|
||||||
|
selector(. ".xAxis", [fontSize(#px(9))]),
|
||||||
|
selector(. ".xAxis .domain", [ display(#none) ]),
|
||||||
|
selector(. ".xAxis .tick line", [ display(#none) ]),
|
||||||
|
selector(. ".xAxis .tick text", [ color(#hex("7a8998")) ]),
|
||||||
|
selector(. ".chart .areaPath", [ SVG.fill(chartColor) ]),
|
||||||
|
selector(. ".lollipopsLine", [ SVG.stroke(#hex("bfcad4")) ]),
|
||||||
|
selector(. ".lollipopsCircle", [ SVG.stroke(#hex("bfcad4")), SVG.fill(#hex("bfcad4")) ]),
|
||||||
|
selector(. ".lollipopsXAxis .domain", [ display(#none) ]),
|
||||||
|
selector(. ".lollipopsXAxis .tick line", [ display(#none) ]),
|
||||||
|
selector(. ".lollipopsXAxis .tick text", [ display(#none) ]),
|
||||||
|
selector(.
|
||||||
|
".lollipopsTooltip",
|
||||||
|
[ position(#absolute),
|
||||||
|
textAlign(#center),
|
||||||
|
padding(px(2)),
|
||||||
|
backgroundColor(hex("bfcad4")),
|
||||||
|
borderRadius(px(3)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
selector(.
|
||||||
|
".lollipopsCircleMouseover",
|
||||||
|
[ SVG.fill(hex("ffa500")), SVG.stroke(#hex("fff")) ],
|
||||||
|
),
|
||||||
|
selector(. ".lollipopsLineMouseover", [ SVG.stroke(#hex("ffa500")) ]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = (
|
||||||
|
~color=#hex("111"),
|
||||||
|
~discrete=?,
|
||||||
|
~height=200,
|
||||||
|
~maxX=?,
|
||||||
|
~minX=?,
|
||||||
|
~yMaxDiscreteDomainFactor=?,
|
||||||
|
~yMaxContinuousDomainFactor=?,
|
||||||
|
~onHover: float => unit=_ => (),
|
||||||
|
~continuous=?,
|
||||||
|
~xScale=?,
|
||||||
|
~yScale=?,
|
||||||
|
~showDistributionLines=false,
|
||||||
|
~showDistributionYAxis=false,
|
||||||
|
~showVerticalLine=false,
|
||||||
|
~timeScale=?,
|
||||||
|
) =>
|
||||||
|
<div style={Styles.graph(color)}>
|
||||||
|
<RawPlot
|
||||||
|
?maxX
|
||||||
|
?minX
|
||||||
|
?yMaxDiscreteDomainFactor
|
||||||
|
?yMaxContinuousDomainFactor
|
||||||
|
?xScale
|
||||||
|
?yScale
|
||||||
|
?timeScale
|
||||||
|
discrete={discrete |> E.O.fmap(XYShape.T.toJs)}
|
||||||
|
height
|
||||||
|
marginBottom=50
|
||||||
|
marginTop=0
|
||||||
|
onHover
|
||||||
|
continuous={continuous |> E.O.fmap(XYShape.T.toJs)}
|
||||||
|
showDistributionLines
|
||||||
|
showDistributionYAxis
|
||||||
|
showVerticalLine
|
||||||
|
/>
|
||||||
|
</div>
|
|
@ -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 };
|
|
@ -0,0 +1,54 @@
|
||||||
|
@module("./PercentilesChart.js")
|
||||||
|
external percentilesChart: React.element = "PercentilesChart"
|
||||||
|
|
||||||
|
module Internal = {
|
||||||
|
type props
|
||||||
|
type makeType = props => React.element
|
||||||
|
type dataType = { "facet": array<
|
||||||
|
{
|
||||||
|
"p1": float,
|
||||||
|
"p10": float,
|
||||||
|
"p20": float,
|
||||||
|
"p30": float,
|
||||||
|
"p40": float,
|
||||||
|
"p5": float,
|
||||||
|
"p50": float,
|
||||||
|
"p60": float,
|
||||||
|
"p70": float,
|
||||||
|
"p80": float,
|
||||||
|
"p90": float,
|
||||||
|
"p95": float,
|
||||||
|
"p99": float,
|
||||||
|
"x": float,
|
||||||
|
}>}
|
||||||
|
@obj external makeProps: (~data: dataType, ~signalListeners: list<string>,~children:React.element, unit) => props = ""
|
||||||
|
|
||||||
|
@module("./PercentilesChart.js")
|
||||||
|
external make : makeType = "PercentilesChart"
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
@module("./PercentilesChart.js")
|
||||||
|
let make = (~dists: array<(float, DistTypes.distPlus)>, ~children=React.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=list{}>children</Internal>
|
||||||
|
}
|
|
@ -0,0 +1,658 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
const d3 = require('d3');
|
||||||
|
const moment = require('moment');
|
||||||
|
require('./styles.css');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param arr
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function exists(arr) {
|
||||||
|
return arr.find(num => _.isFinite(num));
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DistPlotD3 {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.attrs = {
|
||||||
|
svgWidth: 400,
|
||||||
|
svgHeight: 400,
|
||||||
|
marginTop: 5,
|
||||||
|
marginBottom: 5,
|
||||||
|
marginRight: 50,
|
||||||
|
marginLeft: 5,
|
||||||
|
|
||||||
|
container: null,
|
||||||
|
|
||||||
|
// X
|
||||||
|
minX: null,
|
||||||
|
maxX: null,
|
||||||
|
xScaleType: 'linear',
|
||||||
|
xScaleTimeOptions: null,
|
||||||
|
xScaleLogBase: 10,
|
||||||
|
|
||||||
|
// Y
|
||||||
|
minY: null,
|
||||||
|
maxY: null,
|
||||||
|
yScaleType: 'linear',
|
||||||
|
yScaleTimeOptions: null,
|
||||||
|
yScaleLogBase: 10,
|
||||||
|
|
||||||
|
xMinContinuousDomainFactor: 1,
|
||||||
|
xMaxContinuousDomainFactor: 1,
|
||||||
|
yMaxContinuousDomainFactor: 1,
|
||||||
|
yMaxDiscreteDomainFactor: 1,
|
||||||
|
|
||||||
|
showDistributionYAxis: false,
|
||||||
|
showDistributionLines: true,
|
||||||
|
|
||||||
|
areaColors: ['#E1E5EC', '#E1E5EC'],
|
||||||
|
verticalLine: 110,
|
||||||
|
showVerticalLine: true,
|
||||||
|
data: {
|
||||||
|
continuous: null,
|
||||||
|
discrete: null,
|
||||||
|
},
|
||||||
|
onHover: (e) => {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.calc = {
|
||||||
|
chartLeftMargin: null,
|
||||||
|
chartTopMargin: null,
|
||||||
|
chartWidth: null,
|
||||||
|
chartHeight: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.chart = null;
|
||||||
|
this.svg = null;
|
||||||
|
this._container = null;
|
||||||
|
|
||||||
|
this.formatDates = this.formatDates.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param value
|
||||||
|
* @returns {DistPlotD3}
|
||||||
|
*/
|
||||||
|
set(name, value) {
|
||||||
|
_.set(this.attrs, [name], value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data
|
||||||
|
* @returns {DistPlotD3}
|
||||||
|
*/
|
||||||
|
data(data) {
|
||||||
|
const continuousXs = _.get(data, 'continuous.xs', []);
|
||||||
|
const continuousYs = _.get(data, 'continuous.ys', []);
|
||||||
|
const discreteXs = _.get(data, 'discrete.xs', []);
|
||||||
|
const discreteYs = _.get(data, 'discrete.ys', []);
|
||||||
|
this.attrs.data = data;
|
||||||
|
this.attrs.data.continuous = {
|
||||||
|
xs: continuousXs,
|
||||||
|
ys: continuousYs,
|
||||||
|
};
|
||||||
|
this.attrs.data.discrete = {
|
||||||
|
xs: discreteXs,
|
||||||
|
ys: discreteYs,
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this._container = d3.select(this.attrs.container);
|
||||||
|
if (this._container.node() === null) {
|
||||||
|
throw new Error('Container for D3 is not defined.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!['log', 'linear'].includes(this.attrs.xScaleType)) {
|
||||||
|
throw new Error('X-scale type should be either "log" or "linear".');
|
||||||
|
}
|
||||||
|
if (!['log', 'linear'].includes(this.attrs.yScaleType)) {
|
||||||
|
throw new Error('Y-scale type should be either "log" or "linear".');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log Scale.
|
||||||
|
if (this.attrs.xScaleType === 'log') {
|
||||||
|
this.logFilter('continuous', (x, y) => x > 0);
|
||||||
|
this.logFilter('discrete', (x, y) => x > 0);
|
||||||
|
}
|
||||||
|
if (this.attrs.yScaleType === 'log') {
|
||||||
|
this.logFilter('continuous', (x, y) => y > 0);
|
||||||
|
this.logFilter('discrete', (x, y) => y > 0);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.attrs.xScaleType === 'log'
|
||||||
|
&& this.attrs.minX !== null
|
||||||
|
&& this.attrs.minX < 0
|
||||||
|
) {
|
||||||
|
console.warn('minX should be positive.');
|
||||||
|
this.attrs.minX = undefined;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.attrs.yScaleType === 'log'
|
||||||
|
&& this.attrs.minY !== null
|
||||||
|
&& this.attrs.minY < 0
|
||||||
|
) {
|
||||||
|
console.warn('minY should be positive.');
|
||||||
|
this.attrs.minY = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields.
|
||||||
|
const fields = [
|
||||||
|
'marginLeft', 'marginRight',
|
||||||
|
'marginTop', 'marginBottom',
|
||||||
|
'svgWidth', 'svgHeight',
|
||||||
|
'yMaxContinuousDomainFactor',
|
||||||
|
'yMaxDiscreteDomainFactor',
|
||||||
|
'xScaleLogBase', 'yScaleLogBase',
|
||||||
|
];
|
||||||
|
for (const field of fields) {
|
||||||
|
if (!_.isNumber(this.attrs[field])) {
|
||||||
|
throw new Error(`${field} should be a number.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the width from the DOM element.
|
||||||
|
const containerRect = this._container.node().getBoundingClientRect();
|
||||||
|
if (containerRect.width > 0) {
|
||||||
|
this.attrs.svgWidth = containerRect.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculated properties.
|
||||||
|
this.calc.chartLeftMargin = this.attrs.marginLeft;
|
||||||
|
this.calc.chartTopMargin = this.attrs.marginTop;
|
||||||
|
this.calc.chartWidth = this.attrs.svgWidth
|
||||||
|
- this.attrs.marginRight
|
||||||
|
- this.attrs.marginLeft;
|
||||||
|
this.calc.chartHeight = this.attrs.svgHeight
|
||||||
|
- this.attrs.marginBottom
|
||||||
|
- this.attrs.marginTop;
|
||||||
|
|
||||||
|
// Add svg.
|
||||||
|
this.svg = this._container
|
||||||
|
.createObject({ tag: 'svg', selector: 'svg-chart-container' })
|
||||||
|
.attr('width', '100%')
|
||||||
|
.attr('height', this.attrs.svgHeight)
|
||||||
|
.attr('pointer-events', 'none');
|
||||||
|
|
||||||
|
// Add container 'g' (empty) element.
|
||||||
|
this.chart = this.svg
|
||||||
|
.createObject({ tag: 'g', selector: 'chart' })
|
||||||
|
.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${this.calc.chartLeftMargin}, ${this.calc.chartTopMargin})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const common = this.getCommonThings();
|
||||||
|
if (this.hasDate('continuous')) {
|
||||||
|
this.addDistributionChart(common);
|
||||||
|
}
|
||||||
|
if (this.hasDate('discrete')) {
|
||||||
|
this.addLollipopsChart(common);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this._container.selectAll("*").remove();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getCommonThings() {
|
||||||
|
// Boundaries.
|
||||||
|
const xMin = exists([
|
||||||
|
this.attrs.minX,
|
||||||
|
d3.min(this.attrs.data.continuous.xs),
|
||||||
|
d3.min(this.attrs.data.discrete.xs),
|
||||||
|
]);
|
||||||
|
const xMax = exists([
|
||||||
|
this.attrs.maxX,
|
||||||
|
d3.max(this.attrs.data.continuous.xs),
|
||||||
|
d3.max(this.attrs.data.discrete.xs),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const yMin = exists([
|
||||||
|
this.attrs.minY,
|
||||||
|
d3.min(this.attrs.data.continuous.ys),
|
||||||
|
d3.min(this.attrs.data.discrete.ys),
|
||||||
|
]);
|
||||||
|
const yMax = exists([
|
||||||
|
this.attrs.maxY,
|
||||||
|
d3.max(this.attrs.data.continuous.ys),
|
||||||
|
d3.max(this.attrs.data.discrete.ys),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Errors.
|
||||||
|
if (!_.isFinite(xMin)) throw new Error('xMin is undefined');
|
||||||
|
if (!_.isFinite(xMax)) throw new Error('xMax is undefined');
|
||||||
|
if (!_.isFinite(yMin)) throw new Error('yMin is undefined');
|
||||||
|
if (!_.isFinite(yMax)) throw new Error('yMax is undefined');
|
||||||
|
|
||||||
|
// X-domains.
|
||||||
|
const xMinDomainFactor = _.get(this.attrs, 'xMinContinuousDomainFactor', 1);
|
||||||
|
const xMaxDomainFactor = _.get(this.attrs, 'xMaxContinuousDomainFactor', 1);
|
||||||
|
const yMinDomainFactor = _.get(this.attrs, 'yMinContinuousDomainFactor', 1);
|
||||||
|
const yMaxDomainFactor = _.get(this.attrs, 'yMaxContinuousDomainFactor', 1);
|
||||||
|
|
||||||
|
const xMinDomain = xMin * xMinDomainFactor;
|
||||||
|
const xMaxDomain = xMax * xMaxDomainFactor;
|
||||||
|
const yMinDomain = yMin * yMinDomainFactor;
|
||||||
|
const yMaxDomain = yMax * yMaxDomainFactor;
|
||||||
|
|
||||||
|
// X-scale.
|
||||||
|
const xScale = this.attrs.xScaleType === 'linear'
|
||||||
|
? d3.scaleLinear()
|
||||||
|
.domain([xMinDomain, xMaxDomain])
|
||||||
|
.range([0, this.calc.chartWidth])
|
||||||
|
: d3.scaleLog()
|
||||||
|
.base(this.attrs.xScaleLogBase)
|
||||||
|
.domain([xMinDomain, xMaxDomain])
|
||||||
|
.range([0, this.calc.chartWidth]);
|
||||||
|
|
||||||
|
// Y-scale.
|
||||||
|
const yScale = this.attrs.yScaleType === 'linear'
|
||||||
|
? d3.scaleLinear()
|
||||||
|
.domain([yMinDomain, yMaxDomain])
|
||||||
|
.range([this.calc.chartHeight, 0])
|
||||||
|
: d3.scaleLog()
|
||||||
|
.base(this.attrs.yScaleLogBase)
|
||||||
|
.domain([yMinDomain, yMaxDomain])
|
||||||
|
.range([this.calc.chartHeight, 0]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
xMin, xMax,
|
||||||
|
xScale, yScale,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param common
|
||||||
|
*/
|
||||||
|
addDistributionChart(common) {
|
||||||
|
const areaColorRange = d3.scaleOrdinal().range(this.attrs.areaColors);
|
||||||
|
const dataPoints = [this.getDataPoints('continuous')];
|
||||||
|
|
||||||
|
const { xMin, xMax, xScale, yScale } = common;
|
||||||
|
|
||||||
|
// X-axis.
|
||||||
|
let xAxis = null;
|
||||||
|
if (!!this.attrs.xScaleTimeOptions) {
|
||||||
|
// Calculates the projection on X-axis.
|
||||||
|
const zero = _.get(this.attrs, 'xScaleTimeOptions.zero', moment());
|
||||||
|
const unit = _.get(this.attrs, 'xScaleTimeOptions.unit', 'years');
|
||||||
|
const diff = Math.abs(xMax - xMin);
|
||||||
|
const left = zero.clone().add(xMin, unit);
|
||||||
|
const right = left.clone().add(diff, unit);
|
||||||
|
|
||||||
|
// X-time-scale.
|
||||||
|
const xScaleTime = d3.scaleTime()
|
||||||
|
.domain([left.toDate(), right.toDate()])
|
||||||
|
.nice()
|
||||||
|
.range([0, this.calc.chartWidth]);
|
||||||
|
|
||||||
|
xAxis = d3.axisBottom()
|
||||||
|
.scale(xScaleTime)
|
||||||
|
.ticks(this.getTimeTicksByStr(unit))
|
||||||
|
.tickFormat(this.formatDates);
|
||||||
|
} else {
|
||||||
|
xAxis = d3.axisBottom(xScale)
|
||||||
|
.ticks(3)
|
||||||
|
.tickFormat(d => {
|
||||||
|
if (Math.abs(d) < 1) {
|
||||||
|
return d3.format('.2')(d);
|
||||||
|
} else if (xMin > 1000 && xMax < 3000) {
|
||||||
|
// Condition which identifies years; 2019, 2020, 2021.
|
||||||
|
return d3.format('.0')(d);
|
||||||
|
} else {
|
||||||
|
const prefix = d3.formatPrefix('.0', d);
|
||||||
|
return prefix(d).replace('G', 'B');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y-axis.
|
||||||
|
const yAxis = d3.axisRight(yScale);
|
||||||
|
|
||||||
|
// Add axis.
|
||||||
|
this.chart
|
||||||
|
.createObject({ tag: 'g', selector: 'x-axis' })
|
||||||
|
.attr('transform', `translate(0, ${this.calc.chartHeight})`)
|
||||||
|
.call(xAxis);
|
||||||
|
|
||||||
|
if (this.attrs.showDistributionYAxis) {
|
||||||
|
this.chart
|
||||||
|
.createObject({ tag: 'g', selector: 'y-axis' })
|
||||||
|
.call(yAxis);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw area.
|
||||||
|
const area = d3.area()
|
||||||
|
.x(d => xScale(d.x))
|
||||||
|
.y1(d => yScale(d.y))
|
||||||
|
.y0(this.calc.chartHeight);
|
||||||
|
|
||||||
|
this.chart
|
||||||
|
.createObjectsWithData({
|
||||||
|
tag: 'path',
|
||||||
|
selector: 'area-path',
|
||||||
|
data: dataPoints,
|
||||||
|
})
|
||||||
|
.attr('d', area)
|
||||||
|
.attr('fill', (d, i) => areaColorRange(i))
|
||||||
|
.attr('opacity', (d, i) => i === 0 ? 0.7 : 0.5);
|
||||||
|
|
||||||
|
// Draw line.
|
||||||
|
if (this.attrs.showDistributionLines) {
|
||||||
|
const line = d3.line()
|
||||||
|
.x(d => xScale(d.x))
|
||||||
|
.y(d => yScale(d.y));
|
||||||
|
|
||||||
|
this.chart
|
||||||
|
.createObjectsWithData({
|
||||||
|
tag: 'path',
|
||||||
|
selector: 'line-path',
|
||||||
|
data: dataPoints,
|
||||||
|
})
|
||||||
|
.attr('d', line)
|
||||||
|
.attr('id', (d, i) => 'line-' + (i + 1))
|
||||||
|
.attr('opacity', (d, i) => i === 0 ? 0.7 : 1)
|
||||||
|
.attr('fill', 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.attrs.showVerticalLine) {
|
||||||
|
this.chart
|
||||||
|
.createObject({ tag: 'line', selector: 'v-line' })
|
||||||
|
.attr('x1', xScale(this.attrs.verticalLine))
|
||||||
|
.attr('x2', xScale(this.attrs.verticalLine))
|
||||||
|
.attr('y1', 0)
|
||||||
|
.attr('y2', this.calc.chartHeight)
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('stroke-dasharray', '6 6')
|
||||||
|
.attr('stroke', 'steelblue');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hoverLine = this.chart
|
||||||
|
.createObject({ tag: 'line', selector: 'hover-line' })
|
||||||
|
.attr('x1', 0)
|
||||||
|
.attr('x2', 0)
|
||||||
|
.attr('y1', 0)
|
||||||
|
.attr('y2', this.calc.chartHeight)
|
||||||
|
.attr('opacity', 0)
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('stroke-dasharray', '6 6')
|
||||||
|
.attr('stroke', '#22313F');
|
||||||
|
|
||||||
|
// Add drawing rectangle.
|
||||||
|
{
|
||||||
|
const context = this;
|
||||||
|
|
||||||
|
function mouseover() {
|
||||||
|
const mouse = d3.mouse(this);
|
||||||
|
hoverLine
|
||||||
|
.attr('opacity', 1)
|
||||||
|
.attr('x1', mouse[0])
|
||||||
|
.attr('x2', mouse[0]);
|
||||||
|
const xValue = xScale.invert(mouse[0]);
|
||||||
|
context.attrs.onHover(xValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseout() {
|
||||||
|
hoverLine.attr('opacity', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chart
|
||||||
|
.createObject({ tag: 'rect', selector: 'mouse-rect' })
|
||||||
|
.attr('width', this.calc.chartWidth)
|
||||||
|
.attr('height', this.calc.chartHeight)
|
||||||
|
.attr('fill', 'transparent')
|
||||||
|
.attr('pointer-events', 'all')
|
||||||
|
.on('mouseover', mouseover)
|
||||||
|
.on('mousemove', mouseover)
|
||||||
|
.on('mouseout', mouseout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} common
|
||||||
|
* @param {object} common.xScale
|
||||||
|
* @param {object} common.yScale
|
||||||
|
*/
|
||||||
|
addLollipopsChart(common) {
|
||||||
|
const data = this.getDataPoints('discrete');
|
||||||
|
|
||||||
|
const yMin = 0.;
|
||||||
|
const yMax = d3.max(this.attrs.data.discrete.ys);
|
||||||
|
|
||||||
|
// X axis.
|
||||||
|
this.chart.append('g')
|
||||||
|
.attr('class', 'lollipops-x-axis')
|
||||||
|
.attr('transform', `translate(0, ${this.calc.chartHeight})`)
|
||||||
|
.call(d3.axisBottom(common.xScale));
|
||||||
|
|
||||||
|
// Y-domain.
|
||||||
|
const yMinDomainFactor = _.get(this.attrs, 'yMinDiscreteDomainFactor', 1);
|
||||||
|
const yMaxDomainFactor = _.get(this.attrs, 'yMaxDiscreteDomainFactor', 1);
|
||||||
|
const yMinDomain = yMin * yMinDomainFactor;
|
||||||
|
const yMaxDomain = yMax * yMaxDomainFactor;
|
||||||
|
|
||||||
|
// Y-scale.
|
||||||
|
const yScale = this.attrs.yScaleType === 'linear'
|
||||||
|
? d3.scaleLinear()
|
||||||
|
.domain([yMinDomain, yMaxDomain])
|
||||||
|
.range([this.calc.chartHeight, 0])
|
||||||
|
: d3.scaleLog()
|
||||||
|
.base(this.attrs.yScaleLogBase)
|
||||||
|
.domain([yMinDomain, yMaxDomain])
|
||||||
|
.range([this.calc.chartHeight, 0]);
|
||||||
|
|
||||||
|
//
|
||||||
|
const yTicks = Math.floor(this.calc.chartHeight / 20);
|
||||||
|
const yAxis = d3.axisLeft(yScale).ticks(yTicks);
|
||||||
|
|
||||||
|
// Adds 'g' for an y-axis.
|
||||||
|
this.chart.append('g')
|
||||||
|
.attr('class', 'lollipops-y-axis')
|
||||||
|
.attr('transform', `translate(${this.calc.chartWidth}, 0)`)
|
||||||
|
.call(yAxis);
|
||||||
|
|
||||||
|
const thi$ = this;
|
||||||
|
|
||||||
|
function showTooltip(d) {
|
||||||
|
thi$.chart.select('.lollipops-line-' + d.id)
|
||||||
|
.classed('lollipops-line-mouseover', true);
|
||||||
|
thi$.chart.select('.lollipops-circle-' + d.id)
|
||||||
|
.classed('lollipops-circle-mouseover', true)
|
||||||
|
.attr('r', 6);
|
||||||
|
tooltip.transition()
|
||||||
|
.style('opacity', .9);
|
||||||
|
tooltip.html(`x: ${d.x}, y: ${(d.y * 100).toFixed(1)}%`)
|
||||||
|
.style('left', (common.xScale(d.x) + 60) + 'px')
|
||||||
|
.style('top', yScale(d.y) + 'px');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideTooltip(d) {
|
||||||
|
thi$.chart.select('.lollipops-line-' + d.id)
|
||||||
|
.classed('lollipops-line-mouseover', false);
|
||||||
|
thi$.chart.select('.lollipops-circle-' + d.id)
|
||||||
|
.classed('lollipops-circle-mouseover', false)
|
||||||
|
.attr('r', 4);
|
||||||
|
tooltip.transition()
|
||||||
|
.style('opacity', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lines.
|
||||||
|
this.chart.selectAll('lollipops-line')
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append('line')
|
||||||
|
.attr('class', 'lollipops-line')
|
||||||
|
.attr('class', d => 'lollipops-line lollipops-line-' + d.id)
|
||||||
|
.attr('x1', d => common.xScale(d.x))
|
||||||
|
.attr('x2', d => common.xScale(d.x))
|
||||||
|
.attr('y1', d => yScale(d.y))
|
||||||
|
.attr('y2', yScale(yMin));
|
||||||
|
|
||||||
|
// Define the div for the tooltip
|
||||||
|
const tooltip = this._container.append('div')
|
||||||
|
.attr('class', 'lollipop-tooltip')
|
||||||
|
.style('opacity', 0);
|
||||||
|
|
||||||
|
// Circles.
|
||||||
|
this.chart.selectAll('lollipops-circle')
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append('circle')
|
||||||
|
.attr('class', d => 'lollipops-circle lollipops-circle-' + d.id)
|
||||||
|
.attr('cx', d => common.xScale(d.x))
|
||||||
|
.attr('cy', d => yScale(d.y))
|
||||||
|
.attr('r', '4');
|
||||||
|
|
||||||
|
// Rectangles.
|
||||||
|
this.chart.selectAll('lollipops-rectangle')
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append('rect')
|
||||||
|
.attr('width', 30)
|
||||||
|
.attr('height', d => this.calc.chartHeight - yScale(d.y) + 10)
|
||||||
|
.attr('x', d => common.xScale(d.x) - 15)
|
||||||
|
.attr('y', d => yScale(d.y) - 10)
|
||||||
|
.attr('opacity', 0)
|
||||||
|
.attr('pointer-events', 'all')
|
||||||
|
.on('mouseover', showTooltip)
|
||||||
|
.on('mouseout', hideTooltip)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ts
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
formatDates(ts) {
|
||||||
|
return moment(ts).format('MMMM Do YYYY');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} unit
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getTimeTicksByStr(unit) {
|
||||||
|
switch (unit) {
|
||||||
|
case 'months':
|
||||||
|
return d3.timeMonth.every(4);
|
||||||
|
case 'quarters':
|
||||||
|
// It is temporary solution, but it works
|
||||||
|
// if the difference between edge dates is not
|
||||||
|
// much more than 10 units.
|
||||||
|
return d3.timeMonth.every(12);
|
||||||
|
case 'hours':
|
||||||
|
return d3.timeHour.every(10);
|
||||||
|
case 'days':
|
||||||
|
return d3.timeDay.every(7);
|
||||||
|
case 'seconds':
|
||||||
|
return d3.timeSecond.every(10);
|
||||||
|
case 'years':
|
||||||
|
return d3.timeYear.every(10);
|
||||||
|
case 'minutes':
|
||||||
|
return d3.timeMinute.every(10);
|
||||||
|
case 'weeks':
|
||||||
|
return d3.timeWeek.every(10);
|
||||||
|
case 'milliseconds':
|
||||||
|
return d3.timeMillisecond.every(10);
|
||||||
|
default:
|
||||||
|
return d3.timeYear.every(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {{x: number[], y: number[]}}
|
||||||
|
*/
|
||||||
|
getDataPoints(key) {
|
||||||
|
const dt = [];
|
||||||
|
const emptyShape = { xs: [], ys: [] };
|
||||||
|
const data = _.get(this.attrs.data, key, emptyShape);
|
||||||
|
const len = data.xs.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const x = data.xs[i];
|
||||||
|
const y = data.ys[i];
|
||||||
|
const id = i;
|
||||||
|
dt.push({ x, y, id });
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @param {function} pred
|
||||||
|
* @returns {{x: number[], y: number[]}}
|
||||||
|
*/
|
||||||
|
logFilter(key, pred) {
|
||||||
|
const xs = [];
|
||||||
|
const ys = [];
|
||||||
|
const emptyShape = { xs: [], ys: [] };
|
||||||
|
const data = _.get(this.attrs.data, key, emptyShape);
|
||||||
|
|
||||||
|
for (let i = 0, len = data.xs.length; i < len; i++) {
|
||||||
|
const x = data.xs[i];
|
||||||
|
const y = data.ys[i];
|
||||||
|
if (pred(x, y)) {
|
||||||
|
xs.push(x);
|
||||||
|
ys.push(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_.set(this.attrs.data, [key, 'xs'], xs);
|
||||||
|
_.set(this.attrs.data, [key, 'ys'], ys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
hasDate(key) {
|
||||||
|
const xs = _.get(this.attrs, ['data', key, 'xs']);
|
||||||
|
return !!_.size(xs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @docs: https://github.com/d3/d3-selection
|
||||||
|
* @param {object} params
|
||||||
|
* @param {string} params.selector
|
||||||
|
* @param {string} params.tag
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
d3.selection.prototype.createObject = function createObject(params) {
|
||||||
|
const selector = params.selector;
|
||||||
|
const tag = params.tag;
|
||||||
|
return this.insert(tag).attr('class', selector);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @docs: https://github.com/d3/d3-selection
|
||||||
|
* @param {object} params
|
||||||
|
* @param {string} params.selector
|
||||||
|
* @param {string} params.tag
|
||||||
|
* @param {*[]} params.data
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
d3.selection.prototype.createObjectsWithData = function createObjectsWithData(params) {
|
||||||
|
const selector = params.selector;
|
||||||
|
const tag = params.tag;
|
||||||
|
const data = params.data;
|
||||||
|
|
||||||
|
return this.selectAll('.' + selector)
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.insert(tag)
|
||||||
|
.attr('class', selector);
|
||||||
|
};
|
|
@ -0,0 +1,81 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useSize } from 'react-use';
|
||||||
|
import { DistPlotD3 } from './distPlotD3';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param min
|
||||||
|
* @param max
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function getRandomInt(min, max) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param props
|
||||||
|
* @returns {*}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function DistPlotReact(props) {
|
||||||
|
const containerRef = React.createRef();
|
||||||
|
const key = "cdf-chart-react-" + getRandomInt(0, 1000);
|
||||||
|
const style = !!props.width ? { width: props.width + "px" } : {};
|
||||||
|
|
||||||
|
const [sized, { width }] = useSize(() => {
|
||||||
|
return React.createElement("div", {
|
||||||
|
key: "resizable-div",
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
width: props.width,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
new DistPlotD3()
|
||||||
|
.set('svgWidth', width)
|
||||||
|
.set('svgHeight', props.height)
|
||||||
|
.set('maxX', props.maxX)
|
||||||
|
.set('minX', props.minX)
|
||||||
|
.set('onHover', props.onHover)
|
||||||
|
.set('marginBottom', props.marginBottom || 15)
|
||||||
|
.set('marginLeft', 30)
|
||||||
|
.set('marginRight', 30)
|
||||||
|
.set('marginTop', 5)
|
||||||
|
.set('showDistributionLines', props.showDistributionLines)
|
||||||
|
.set('showDistributionYAxis', props.showDistributionYAxis)
|
||||||
|
.set('verticalLine', props.verticalLine || 110)
|
||||||
|
.set('showVerticalLine', props.showVerticalLine)
|
||||||
|
.set('container', containerRef.current)
|
||||||
|
.set('xScaleType', props.xScale || 'linear')
|
||||||
|
.set('yScaleType', props.yScale || 'linear')
|
||||||
|
.set('xScaleTimeOptions', props.timeScale)
|
||||||
|
.set('yMaxContinuousDomainFactor', props.yMaxContinuousDomainFactor || 1)
|
||||||
|
.set('yMaxDiscreteDomainFactor', props.yMaxDiscreteDomainFactor || 1)
|
||||||
|
.data({
|
||||||
|
continuous: props.continuous,
|
||||||
|
discrete: props.discrete,
|
||||||
|
})
|
||||||
|
.render();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("distPlotD3 Error: ", e)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return React.createElement("div", {
|
||||||
|
style: {
|
||||||
|
paddingLeft: "10px",
|
||||||
|
paddingRight: "10px",
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
sized,
|
||||||
|
React.createElement("div", {
|
||||||
|
key,
|
||||||
|
style,
|
||||||
|
ref: containerRef,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DistPlotReact;
|
|
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
.lollipops-line-mouseover {
|
||||||
|
stroke-dasharray: 4;
|
||||||
|
animation: dash 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dash {
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 1000;
|
||||||
|
}
|
||||||
|
}
|
31
packages/playground/src/components/charts/NumberShower.res
Normal file
31
packages/playground/src/components/charts/NumberShower.res
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
module JS = {
|
||||||
|
@deriving(abstract)
|
||||||
|
type numberPresentation = {
|
||||||
|
value: string,
|
||||||
|
power: option<float>,
|
||||||
|
symbol: option<string>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@module("./numberShower.js")
|
||||||
|
external numberShow: (float, int) => numberPresentation = "numberShow"
|
||||||
|
}
|
||||||
|
|
||||||
|
let sup = {
|
||||||
|
open CssJs
|
||||||
|
style(. [ fontSize(#em(0.6)), verticalAlign(#super) ])
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = (~number, ~precision) => {
|
||||||
|
let numberWithPresentation = JS.numberShow(number, precision)
|
||||||
|
<span>
|
||||||
|
{JS.valueGet(numberWithPresentation) |> React.string}
|
||||||
|
{JS.symbolGet(numberWithPresentation) |> E.O.React.fmapOrNull(React.string)}
|
||||||
|
{JS.powerGet(numberWithPresentation) |> E.O.React.fmapOrNull(e =>
|
||||||
|
<span>
|
||||||
|
{j`\\u00b710` |> React.string}
|
||||||
|
<span style=sup> {e |> E.Float.toString |> React.string} </span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
}
|
65
packages/playground/src/components/charts/numberShower.js
Normal file
65
packages/playground/src/components/charts/numberShower.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// 105 -> 3
|
||||||
|
const orderOfMagnitudeNum = (n) => {
|
||||||
|
return Math.pow(10, n);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 105 -> 3
|
||||||
|
const orderOfMagnitude = (n) => {
|
||||||
|
return Math.floor(Math.log(n) / Math.LN10 + 0.000000001);
|
||||||
|
};
|
||||||
|
|
||||||
|
function withXSigFigs(number, sigFigs) {
|
||||||
|
const withPrecision = number.toPrecision(sigFigs);
|
||||||
|
const formatted = Number(withPrecision);
|
||||||
|
return `${formatted}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumberShower {
|
||||||
|
constructor(number, precision = 2) {
|
||||||
|
this.number = number;
|
||||||
|
this.precision = precision;
|
||||||
|
}
|
||||||
|
|
||||||
|
convert() {
|
||||||
|
const number = Math.abs(this.number);
|
||||||
|
const response = this.evaluate(number);
|
||||||
|
if (this.number < 0) {
|
||||||
|
response.value = '-' + response.value;
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
metricSystem(number, order) {
|
||||||
|
const newNumber = number / orderOfMagnitudeNum(order);
|
||||||
|
const precision = this.precision;
|
||||||
|
return `${withXSigFigs(newNumber, precision)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate(number) {
|
||||||
|
if (number === 0) {
|
||||||
|
return { value: this.metricSystem(0, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = orderOfMagnitude(number);
|
||||||
|
if (order < -2) {
|
||||||
|
return { value: this.metricSystem(number, order), power: order };
|
||||||
|
} else if (order < 4) {
|
||||||
|
return { value: this.metricSystem(number, 0) };
|
||||||
|
} else if (order < 6) {
|
||||||
|
return { value: this.metricSystem(number, 3), symbol: 'K' };
|
||||||
|
} else if (order < 9) {
|
||||||
|
return { value: this.metricSystem(number, 6), symbol: 'M' };
|
||||||
|
} else if (order < 12) {
|
||||||
|
return { value: this.metricSystem(number, 9), symbol: 'B' };
|
||||||
|
} else if (order < 15) {
|
||||||
|
return { value: this.metricSystem(number, 12), symbol: 'T' };
|
||||||
|
} else {
|
||||||
|
return { value: this.metricSystem(number, order), power: order };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numberShow(number, precision = 2) {
|
||||||
|
const ns = new NumberShower(number, precision);
|
||||||
|
return ns.convert();
|
||||||
|
}
|
171
packages/playground/src/distPlus/ProgramEvaluator.res
Normal file
171
packages/playground/src/distPlus/ProgramEvaluator.res
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
// TODO: This setup is more confusing than it should be, there's more work to do in cleanup here.
|
||||||
|
module Inputs = {
|
||||||
|
module SamplingInputs = {
|
||||||
|
type t = {
|
||||||
|
sampleCount: option<int>,
|
||||||
|
outputXYPoints: option<int>,
|
||||||
|
kernelWidth: option<float>,
|
||||||
|
shapeLength: option<int>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let defaultRecommendedLength = 100
|
||||||
|
let defaultShouldDownsample = true
|
||||||
|
|
||||||
|
type inputs = {
|
||||||
|
squiggleString: string,
|
||||||
|
samplingInputs: SamplingInputs.t,
|
||||||
|
environment: ExpressionTypes.ExpressionTree.environment,
|
||||||
|
}
|
||||||
|
|
||||||
|
let empty: SamplingInputs.t = {
|
||||||
|
sampleCount: None,
|
||||||
|
outputXYPoints: None,
|
||||||
|
kernelWidth: None,
|
||||||
|
shapeLength: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
let make = (
|
||||||
|
~samplingInputs=empty,
|
||||||
|
~squiggleString,
|
||||||
|
~environment=ExpressionTypes.ExpressionTree.Environment.empty,
|
||||||
|
(),
|
||||||
|
): inputs => {
|
||||||
|
samplingInputs: samplingInputs,
|
||||||
|
squiggleString: squiggleString,
|
||||||
|
environment: environment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type \"export" = [
|
||||||
|
| #DistPlus(ProbExample.DistPlus.t)
|
||||||
|
| #Float(float)
|
||||||
|
| #Function(
|
||||||
|
(array<string>, ProbExample.ExpressionTypes.ExpressionTree.node),
|
||||||
|
ProbExample.ExpressionTypes.ExpressionTree.environment,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
module Internals = {
|
||||||
|
let addVariable = (
|
||||||
|
{samplingInputs, squiggleString, environment}: Inputs.inputs,
|
||||||
|
str,
|
||||||
|
node,
|
||||||
|
): Inputs.inputs => {
|
||||||
|
samplingInputs: samplingInputs,
|
||||||
|
squiggleString: squiggleString,
|
||||||
|
environment: ExpressionTypes.ExpressionTree.Environment.update(environment, str, _ => Some(
|
||||||
|
node,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
|
type outputs = {
|
||||||
|
graph: ExpressionTypes.ExpressionTree.node,
|
||||||
|
shape: DistTypes.shape,
|
||||||
|
}
|
||||||
|
let makeOutputs = (graph, shape): outputs => {graph: graph, shape: shape}
|
||||||
|
|
||||||
|
let makeInputs = (inputs: Inputs.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.toLeaf(makeInputs(inputs), inputs.environment, node)
|
||||||
|
|
||||||
|
let runProgram = (inputs: Inputs.inputs, p: ExpressionTypes.Program.program) => {
|
||||||
|
let ins = ref(inputs)
|
||||||
|
p
|
||||||
|
|> E.A.fmap(x =>
|
||||||
|
switch x {
|
||||||
|
| #Assignment(name, node) =>
|
||||||
|
ins := addVariable(ins.contents, name, node)
|
||||||
|
None
|
||||||
|
| #Expression(node) =>
|
||||||
|
Some(runNode(ins.contents, node) |> E.R.fmap(r => (ins.contents.environment, r)))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.A.O.concatSomes
|
||||||
|
|> E.A.R.firstErrorOrOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputsToLeaf = (inputs: Inputs.inputs) =>
|
||||||
|
MathJsParser.fromString(inputs.squiggleString)
|
||||||
|
|> E.R.bind(_, g => runProgram(inputs, g))
|
||||||
|
|
||||||
|
let outputToDistPlus = (inputs: Inputs.inputs, shape: DistTypes.shape) =>
|
||||||
|
DistPlus.make(~shape, ~squiggleString=Some(inputs.squiggleString), ())
|
||||||
|
}
|
||||||
|
|
||||||
|
let renderIfNeeded = (inputs: Inputs.inputs, node: ExpressionTypes.ExpressionTree.node): result<
|
||||||
|
ExpressionTypes.ExpressionTree.node,
|
||||||
|
string,
|
||||||
|
> =>
|
||||||
|
node |> (
|
||||||
|
x =>
|
||||||
|
switch x {
|
||||||
|
| #Normalize(_) as n
|
||||||
|
| #SymbolicDist(_) as n =>
|
||||||
|
#Render(n)
|
||||||
|
|> Internals.runNode(inputs)
|
||||||
|
|> (
|
||||||
|
x =>
|
||||||
|
switch x {
|
||||||
|
| Ok(#RenderedDist(_)) as r => r
|
||||||
|
| Error(r) => Error(r)
|
||||||
|
| _ => Error("Didn't render, but intended to")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
| n => Ok(n)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Consider using ExpressionTypes.ExpressionTree.getFloat or similar in this function
|
||||||
|
let coersionToExportedTypes = (
|
||||||
|
inputs,
|
||||||
|
env: ProbExample.ExpressionTypes.ExpressionTree.environment,
|
||||||
|
node: ExpressionTypes.ExpressionTree.node,
|
||||||
|
): result<\"export", string> =>
|
||||||
|
node
|
||||||
|
|> renderIfNeeded(inputs)
|
||||||
|
|> E.R.bind(_, x =>
|
||||||
|
switch x {
|
||||||
|
| #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))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let rec mapM = (f, xs) =>
|
||||||
|
switch xs {
|
||||||
|
| list{} => Ok(list{})
|
||||||
|
| list{x, ...rest} =>
|
||||||
|
switch f(x) {
|
||||||
|
| Error(err) => Error(err)
|
||||||
|
| Ok(val) =>
|
||||||
|
switch mapM(f, rest) {
|
||||||
|
| Error(err) => Error(err)
|
||||||
|
| Ok(restList) => Ok(list{val, ...restList})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let evaluateProgram = (inputs: Inputs.inputs) =>
|
||||||
|
inputs |> Internals.inputsToLeaf |> E.R.bind(_, xs => mapM(((a, b)) => coersionToExportedTypes(inputs, a, b), (Array.to_list(xs))))
|
||||||
|
|
||||||
|
let evaluateFunction = (
|
||||||
|
inputs: Inputs.inputs,
|
||||||
|
fn: (array<string>, ExpressionTypes.ExpressionTree.node),
|
||||||
|
fnInputs,
|
||||||
|
) => {
|
||||||
|
let output = ExpressionTree.runFunction(
|
||||||
|
Internals.makeInputs(inputs),
|
||||||
|
inputs.environment,
|
||||||
|
fnInputs,
|
||||||
|
fn,
|
||||||
|
)
|
||||||
|
output |> E.R.bind(_, coersionToExportedTypes(inputs, inputs.environment))
|
||||||
|
}
|
|
@ -0,0 +1,298 @@
|
||||||
|
type pointMassesWithMoments = {
|
||||||
|
n: int,
|
||||||
|
masses: array(float),
|
||||||
|
means: array(float),
|
||||||
|
variances: array(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* This function takes a continuous distribution and efficiently approximates it as
|
||||||
|
point masses that have variances associated with them.
|
||||||
|
We estimate the means and variances from overlapping triangular distributions which we imagine are making up the
|
||||||
|
XYShape.
|
||||||
|
We can then use the algebra of random variables to "convolve" the point masses and their variances,
|
||||||
|
and finally reconstruct a new distribution from them, e.g. using a Fast Gauss Transform or Raykar et al. (2007). */
|
||||||
|
let toDiscretePointMassesFromTriangulars =
|
||||||
|
(~inverse=false, s: XYShape.T.t): pointMassesWithMoments => {
|
||||||
|
// TODO: what if there is only one point in the distribution?
|
||||||
|
let n = s |> XYShape.T.length;
|
||||||
|
// first, double up the leftmost and rightmost points:
|
||||||
|
let {xs, ys}: XYShape.T.t = s;
|
||||||
|
Js.Array.unshift(xs[0], xs) |> ignore;
|
||||||
|
Js.Array.unshift(ys[0], ys) |> ignore;
|
||||||
|
Js.Array.push(xs[n - 1], xs) |> ignore;
|
||||||
|
Js.Array.push(ys[n - 1], ys) |> ignore;
|
||||||
|
let n = E.A.length(xs);
|
||||||
|
// squares and neighbourly products of the xs
|
||||||
|
let xsSq: array(float) = Belt.Array.makeUninitializedUnsafe(n);
|
||||||
|
let xsProdN1: array(float) = Belt.Array.makeUninitializedUnsafe(n - 1);
|
||||||
|
let xsProdN2: array(float) = Belt.Array.makeUninitializedUnsafe(n - 2);
|
||||||
|
for (i in 0 to n - 1) {
|
||||||
|
Belt.Array.set(xsSq, i, xs[i] *. xs[i]) |> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
for (i in 0 to n - 2) {
|
||||||
|
Belt.Array.set(xsProdN1, i, xs[i] *. xs[i + 1]) |> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
for (i in 0 to n - 3) {
|
||||||
|
Belt.Array.set(xsProdN2, i, xs[i] *. xs[i + 2]) |> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
// means and variances
|
||||||
|
let masses: array(float) = Belt.Array.makeUninitializedUnsafe(n - 2); // doesn't include the fake first and last points
|
||||||
|
let means: array(float) = Belt.Array.makeUninitializedUnsafe(n - 2);
|
||||||
|
let variances: array(float) = Belt.Array.makeUninitializedUnsafe(n - 2);
|
||||||
|
|
||||||
|
if (inverse) {
|
||||||
|
for (i in 1 to n - 2) {
|
||||||
|
Belt.Array.set(masses, i - 1, (xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.)
|
||||||
|
|> ignore;
|
||||||
|
|
||||||
|
// this only works when the whole triange is either on the left or on the right of zero
|
||||||
|
let a = xs[i - 1];
|
||||||
|
let c = xs[i];
|
||||||
|
let b = xs[i + 1];
|
||||||
|
|
||||||
|
// These are the moments of the reciprocal of a triangular distribution, as symbolically integrated by Mathematica.
|
||||||
|
// They're probably pretty close to invMean ~ 1/mean = 3/(a+b+c) and invVar. But I haven't worked out
|
||||||
|
// the worst case error, so for now let's use these monster equations
|
||||||
|
let inverseMean =
|
||||||
|
2.
|
||||||
|
*. (a *. log(a /. c) /. (a -. c) +. b *. log(c /. b) /. (b -. c))
|
||||||
|
/. (a -. b);
|
||||||
|
let inverseVar =
|
||||||
|
2.
|
||||||
|
*. (log(c /. a) /. (a -. c) +. b *. log(b /. c) /. (b -. c))
|
||||||
|
/. (a -. b)
|
||||||
|
-. inverseMean
|
||||||
|
** 2.;
|
||||||
|
|
||||||
|
Belt.Array.set(means, i - 1, inverseMean) |> ignore;
|
||||||
|
|
||||||
|
Belt.Array.set(variances, i - 1, inverseVar) |> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
|
||||||
|
{n: n - 2, masses, means, variances};
|
||||||
|
} else {
|
||||||
|
for (i in 1 to n - 2) {
|
||||||
|
// area of triangle = width * height / 2
|
||||||
|
Belt.Array.set(masses, i - 1, (xs[i + 1] -. xs[i - 1]) *. ys[i] /. 2.)
|
||||||
|
|> ignore;
|
||||||
|
|
||||||
|
// means of triangle = (a + b + c) / 3
|
||||||
|
Belt.Array.set(means, i - 1, (xs[i - 1] +. xs[i] +. xs[i + 1]) /. 3.)
|
||||||
|
|> ignore;
|
||||||
|
|
||||||
|
// variance of triangle = (a^2 + b^2 + c^2 - ab - ac - bc) / 18
|
||||||
|
Belt.Array.set(
|
||||||
|
variances,
|
||||||
|
i - 1,
|
||||||
|
(
|
||||||
|
xsSq[i - 1]
|
||||||
|
+. xsSq[i]
|
||||||
|
+. xsSq[i + 1]
|
||||||
|
-. xsProdN1[i - 1]
|
||||||
|
-. xsProdN1[i]
|
||||||
|
-. xsProdN2[i - 1]
|
||||||
|
)
|
||||||
|
/. 18.,
|
||||||
|
)
|
||||||
|
|> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
{n: n - 2, masses, means, variances};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let combineShapesContinuousContinuous =
|
||||||
|
(
|
||||||
|
op: ExpressionTypes.algebraicOperation,
|
||||||
|
s1: DistTypes.xyShape,
|
||||||
|
s2: DistTypes.xyShape,
|
||||||
|
)
|
||||||
|
: DistTypes.xyShape => {
|
||||||
|
let t1n = s1 |> XYShape.T.length;
|
||||||
|
let t2n = s2 |> XYShape.T.length;
|
||||||
|
|
||||||
|
// if we add the two distributions, we should probably use normal filters.
|
||||||
|
// if we multiply the two distributions, we should probably use lognormal filters.
|
||||||
|
let t1m = toDiscretePointMassesFromTriangulars(s1);
|
||||||
|
let t2m =
|
||||||
|
switch (op) {
|
||||||
|
| `Divide => toDiscretePointMassesFromTriangulars(~inverse=true, s2)
|
||||||
|
| _ => toDiscretePointMassesFromTriangulars(~inverse=false, s2)
|
||||||
|
};
|
||||||
|
|
||||||
|
let combineMeansFn =
|
||||||
|
switch (op) {
|
||||||
|
| `Add => ((m1, m2) => m1 +. m2)
|
||||||
|
| `Subtract => ((m1, m2) => m1 -. m2)
|
||||||
|
| `Multiply => ((m1, m2) => m1 *. m2)
|
||||||
|
| `Divide => ((m1, mInv2) => m1 *. mInv2)
|
||||||
|
| `Exponentiate => ((m1, mInv2) => m1 ** mInv2)
|
||||||
|
}; // note: here, mInv2 = mean(1 / t2) ~= 1 / mean(t2)
|
||||||
|
|
||||||
|
// TODO: I don't know what the variances are for exponentatiation
|
||||||
|
// converts the variances and means of the two inputs into the variance of the output
|
||||||
|
let combineVariancesFn =
|
||||||
|
switch (op) {
|
||||||
|
| `Add => ((v1, v2, _, _) => v1 +. v2)
|
||||||
|
| `Subtract => ((v1, v2, _, _) => v1 +. v2)
|
||||||
|
| `Multiply => (
|
||||||
|
(v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.
|
||||||
|
)
|
||||||
|
| `Exponentiate =>
|
||||||
|
((v1, v2, m1, m2) => v1 *. v2 +. v1 *. m2 ** 2. +. v2 *. m1 ** 2.);
|
||||||
|
| `Divide => (
|
||||||
|
(v1, vInv2, m1, mInv2) =>
|
||||||
|
v1 *. vInv2 +. v1 *. mInv2 ** 2. +. vInv2 *. m1 ** 2.
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: If operating on two positive-domain distributions, we should take that into account
|
||||||
|
let outputMinX: ref(float) = ref(infinity);
|
||||||
|
let outputMaxX: ref(float) = ref(neg_infinity);
|
||||||
|
let masses: array(float) =
|
||||||
|
Belt.Array.makeUninitializedUnsafe(t1m.n * t2m.n);
|
||||||
|
let means: array(float) =
|
||||||
|
Belt.Array.makeUninitializedUnsafe(t1m.n * t2m.n);
|
||||||
|
let variances: array(float) =
|
||||||
|
Belt.Array.makeUninitializedUnsafe(t1m.n * t2m.n);
|
||||||
|
// then convolve the two sets of pointMassesWithMoments
|
||||||
|
for (i in 0 to t1m.n - 1) {
|
||||||
|
for (j in 0 to t2m.n - 1) {
|
||||||
|
let k = i * t2m.n + j;
|
||||||
|
Belt.Array.set(masses, k, t1m.masses[i] *. t2m.masses[j]) |> ignore;
|
||||||
|
|
||||||
|
let mean = combineMeansFn(t1m.means[i], t2m.means[j]);
|
||||||
|
let variance =
|
||||||
|
combineVariancesFn(
|
||||||
|
t1m.variances[i],
|
||||||
|
t2m.variances[j],
|
||||||
|
t1m.means[i],
|
||||||
|
t2m.means[j],
|
||||||
|
);
|
||||||
|
Belt.Array.set(means, k, mean) |> ignore;
|
||||||
|
Belt.Array.set(variances, k, variance) |> ignore;
|
||||||
|
// update bounds
|
||||||
|
let minX = mean -. 2. *. sqrt(variance) *. 1.644854;
|
||||||
|
let maxX = mean +. 2. *. sqrt(variance) *. 1.644854;
|
||||||
|
if (minX < outputMinX^) {
|
||||||
|
outputMinX := minX;
|
||||||
|
};
|
||||||
|
if (maxX > outputMaxX^) {
|
||||||
|
outputMaxX := maxX;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// we now want to create a set of target points. For now, let's just evenly distribute 200 points between
|
||||||
|
// between the outputMinX and outputMaxX
|
||||||
|
let nOut = 300;
|
||||||
|
let outputXs: array(float) =
|
||||||
|
E.A.Floats.range(outputMinX^, outputMaxX^, nOut);
|
||||||
|
let outputYs: array(float) = Belt.Array.make(nOut, 0.0);
|
||||||
|
// now, for each of the outputYs, accumulate from a Gaussian kernel over each input point.
|
||||||
|
for (j in 0 to E.A.length(masses) - 1) {
|
||||||
|
// go through all of the result points
|
||||||
|
if (variances[j] > 0. && masses[j] > 0.) {
|
||||||
|
for (i in 0 to E.A.length(outputXs) - 1) {
|
||||||
|
// go through all of the target points
|
||||||
|
let dx = outputXs[i] -. means[j];
|
||||||
|
let contribution =
|
||||||
|
masses[j]
|
||||||
|
*. exp(-. (dx ** 2.) /. (2. *. variances[j]))
|
||||||
|
/. sqrt(2. *. 3.14159276 *. variances[j]);
|
||||||
|
Belt.Array.set(outputYs, i, outputYs[i] +. contribution) |> ignore;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
{xs: outputXs, ys: outputYs};
|
||||||
|
};
|
||||||
|
|
||||||
|
let toDiscretePointMassesFromDiscrete =
|
||||||
|
(s: DistTypes.xyShape): pointMassesWithMoments => {
|
||||||
|
let {xs, ys}: XYShape.T.t = s;
|
||||||
|
let n = E.A.length(xs);
|
||||||
|
|
||||||
|
let masses: array(float) = Belt.Array.makeBy(n, i => ys[i]);
|
||||||
|
let means: array(float) = Belt.Array.makeBy(n, i => xs[i]);
|
||||||
|
let variances: array(float) = Belt.Array.makeBy(n, i => 0.0);
|
||||||
|
|
||||||
|
{n, masses, means, variances};
|
||||||
|
};
|
||||||
|
|
||||||
|
let combineShapesContinuousDiscrete =
|
||||||
|
(
|
||||||
|
op: ExpressionTypes.algebraicOperation,
|
||||||
|
continuousShape: DistTypes.xyShape,
|
||||||
|
discreteShape: DistTypes.xyShape,
|
||||||
|
)
|
||||||
|
: DistTypes.xyShape => {
|
||||||
|
let t1n = continuousShape |> XYShape.T.length;
|
||||||
|
let t2n = discreteShape |> XYShape.T.length;
|
||||||
|
|
||||||
|
// each x pair is added/subtracted
|
||||||
|
let fn = Operation.Algebraic.toFn(op);
|
||||||
|
|
||||||
|
let outXYShapes: array(array((float, float))) =
|
||||||
|
Belt.Array.makeUninitializedUnsafe(t2n);
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
| `Add
|
||||||
|
| `Subtract =>
|
||||||
|
for (j in 0 to t2n - 1) {
|
||||||
|
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
||||||
|
let dxyShape: array((float, float)) =
|
||||||
|
Belt.Array.makeUninitializedUnsafe(t1n);
|
||||||
|
for (i in 0 to t1n - 1) {
|
||||||
|
Belt.Array.set(
|
||||||
|
dxyShape,
|
||||||
|
i,
|
||||||
|
(
|
||||||
|
fn(continuousShape.xs[i], discreteShape.xs[j]),
|
||||||
|
continuousShape.ys[i] *. discreteShape.ys[j],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore;
|
||||||
|
();
|
||||||
|
}
|
||||||
|
| `Multiply
|
||||||
|
| `Exponentiate
|
||||||
|
| `Divide =>
|
||||||
|
for (j in 0 to t2n - 1) {
|
||||||
|
// creates a new continuous shape for each one of the discrete points, and collects them in outXYShapes.
|
||||||
|
let dxyShape: array((float, float)) =
|
||||||
|
Belt.Array.makeUninitializedUnsafe(t1n);
|
||||||
|
for (i in 0 to t1n - 1) {
|
||||||
|
Belt.Array.set(
|
||||||
|
dxyShape,
|
||||||
|
i,
|
||||||
|
(
|
||||||
|
fn(continuousShape.xs[i], discreteShape.xs[j]),
|
||||||
|
{continuousShape.ys[i] *. discreteShape.ys[j] /. discreteShape.xs[j]}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
Belt.Array.set(outXYShapes, j, dxyShape) |> ignore;
|
||||||
|
();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
outXYShapes
|
||||||
|
|> E.A.fmap(XYShape.T.fromZippedArray)
|
||||||
|
|> E.A.fold_left(
|
||||||
|
XYShape.PointwiseCombination.combine(
|
||||||
|
(+.),
|
||||||
|
XYShape.XtoY.continuousInterpolator(`Linear, `UseZero),
|
||||||
|
),
|
||||||
|
XYShape.T.empty,
|
||||||
|
);
|
||||||
|
};
|
332
packages/playground/src/distPlus/distribution/Continuous.re
Normal file
332
packages/playground/src/distPlus/distribution/Continuous.re
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
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 => {
|
||||||
|
xyShape,
|
||||||
|
interpolation,
|
||||||
|
integralSumCache,
|
||||||
|
integralCache,
|
||||||
|
};
|
||||||
|
let shapeMap =
|
||||||
|
(fn, {xyShape, interpolation, integralSumCache, integralCache}: t): t => {
|
||||||
|
xyShape: fn(xyShape),
|
||||||
|
interpolation,
|
||||||
|
integralSumCache,
|
||||||
|
integralCache,
|
||||||
|
};
|
||||||
|
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));
|
||||||
|
|
||||||
|
let emptyIntegral: DistTypes.continuousShape = {
|
||||||
|
xyShape: {
|
||||||
|
xs: [|neg_infinity|],
|
||||||
|
ys: [|0.0|],
|
||||||
|
},
|
||||||
|
interpolation: `Linear,
|
||||||
|
integralSumCache: Some(0.0),
|
||||||
|
integralCache: None,
|
||||||
|
};
|
||||||
|
let empty: DistTypes.continuousShape = {
|
||||||
|
xyShape: XYShape.T.empty,
|
||||||
|
interpolation: `Linear,
|
||||||
|
integralSumCache: Some(0.0),
|
||||||
|
integralCache: Some(emptyIntegral),
|
||||||
|
};
|
||||||
|
|
||||||
|
let stepwiseToLinear = (t: t): t =>
|
||||||
|
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,
|
||||||
|
fn: (float, float) => float,
|
||||||
|
t1: DistTypes.continuousShape,
|
||||||
|
t2: DistTypes.continuousShape,
|
||||||
|
)
|
||||||
|
: DistTypes.continuousShape => {
|
||||||
|
// If we're adding the distributions, and we know the total of each, then we
|
||||||
|
// can just sum them up. Otherwise, all bets are off.
|
||||||
|
let combinedIntegralSum =
|
||||||
|
Common.combineIntegralSums(
|
||||||
|
integralSumCachesFn,
|
||||||
|
t1.integralSumCache,
|
||||||
|
t2.integralSumCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: does it ever make sense to pointwise combine the integrals here?
|
||||||
|
// It could be done for pointwise additions, but is that ever needed?
|
||||||
|
|
||||||
|
// 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 extrapolation =
|
||||||
|
switch (distributionType) {
|
||||||
|
| `PDF => `UseZero
|
||||||
|
| `CDF => `UseOutermostPoints
|
||||||
|
};
|
||||||
|
|
||||||
|
let interpolator =
|
||||||
|
XYShape.XtoY.continuousInterpolator(t1.interpolation, extrapolation);
|
||||||
|
|
||||||
|
make(
|
||||||
|
~integralSumCache=combinedIntegralSum,
|
||||||
|
XYShape.PointwiseCombination.combine(
|
||||||
|
fn,
|
||||||
|
interpolator,
|
||||||
|
t1.xyShape,
|
||||||
|
t2.xyShape,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let toLinear = (t: t): option(t) => {
|
||||||
|
switch (t) {
|
||||||
|
| {interpolation: `Stepwise, xyShape, integralSumCache, integralCache} =>
|
||||||
|
xyShape
|
||||||
|
|> XYShape.Range.stepsToContinuous
|
||||||
|
|> E.O.fmap(make(~integralSumCache, ~integralCache))
|
||||||
|
| {interpolation: `Linear} => Some(t)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let shapeFn = (fn, t: t) => t |> getShape |> fn;
|
||||||
|
|
||||||
|
let updateIntegralSumCache = (integralSumCache, t: t): t => {
|
||||||
|
...t,
|
||||||
|
integralSumCache,
|
||||||
|
};
|
||||||
|
|
||||||
|
let updateIntegralCache = (integralCache, t: t): t => {...t, integralCache};
|
||||||
|
|
||||||
|
let reduce =
|
||||||
|
(
|
||||||
|
~integralSumCachesFn: (float, float) => option(float)=(_, _) => None,
|
||||||
|
~integralCachesFn: (t, t) => option(t)=(_, _) => None,
|
||||||
|
fn,
|
||||||
|
continuousShapes,
|
||||||
|
) =>
|
||||||
|
continuousShapes
|
||||||
|
|> E.A.fold_left(
|
||||||
|
combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn),
|
||||||
|
empty,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mapY =
|
||||||
|
(~integralSumCacheFn=_ => None, ~integralCacheFn=_ => None, ~fn, t: t) => {
|
||||||
|
make(
|
||||||
|
~interpolation=t.interpolation,
|
||||||
|
~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn),
|
||||||
|
~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn),
|
||||||
|
t |> getShape |> XYShape.T.mapY(fn),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
t
|
||||||
|
|> mapY(~fn=(r: float) => r *. scale)
|
||||||
|
|> updateIntegralSumCache(scaledIntegralSumCache)
|
||||||
|
|> updateIntegralCache(scaledIntegralCache);
|
||||||
|
};
|
||||||
|
|
||||||
|
module T =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.continuousShape;
|
||||||
|
type integral = DistTypes.continuousShape;
|
||||||
|
let minX = shapeFn(XYShape.T.minX);
|
||||||
|
let maxX = shapeFn(XYShape.T.maxX);
|
||||||
|
let mapY = mapY;
|
||||||
|
let updateIntegralCache = updateIntegralCache;
|
||||||
|
let toDiscreteProbabilityMassFraction = _ => 0.0;
|
||||||
|
let toShape = (t: t): DistTypes.shape => Continuous(t);
|
||||||
|
let xToY = (f, {interpolation, xyShape}: t) => {
|
||||||
|
(
|
||||||
|
switch (interpolation) {
|
||||||
|
| `Stepwise =>
|
||||||
|
xyShape |> XYShape.XtoY.stepwiseIncremental(f) |> E.O.default(0.0)
|
||||||
|
| `Linear => xyShape |> XYShape.XtoY.linear(f)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> DistTypes.MixedPoint.makeContinuous;
|
||||||
|
};
|
||||||
|
|
||||||
|
let truncate =
|
||||||
|
(leftCutoff: option(float), rightCutoff: option(float), t: t) => {
|
||||||
|
let lc = E.O.default(neg_infinity, leftCutoff);
|
||||||
|
let rc = E.O.default(infinity, rightCutoff);
|
||||||
|
let truncatedZippedPairs =
|
||||||
|
t
|
||||||
|
|> getShape
|
||||||
|
|> XYShape.T.zip
|
||||||
|
|> XYShape.Zipped.filterByX(x => x >= lc && x <= rc);
|
||||||
|
|
||||||
|
let leftNewPoint =
|
||||||
|
leftCutoff
|
||||||
|
|> E.O.dimap(lc => [|(lc -. epsilon_float, 0.)|], _ => [||]);
|
||||||
|
let rightNewPoint =
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This should work with stepwise plots.
|
||||||
|
let integral = t =>
|
||||||
|
switch (getShape(t) |> XYShape.T.isEmpty, t.integralCache) {
|
||||||
|
| (true, _) => emptyIntegral
|
||||||
|
| (false, Some(cache)) => cache
|
||||||
|
| (false, None) =>
|
||||||
|
t
|
||||||
|
|> getShape
|
||||||
|
|> XYShape.Range.integrateWithTriangles
|
||||||
|
|> E.O.toExt("This should not have happened")
|
||||||
|
|> make
|
||||||
|
};
|
||||||
|
|
||||||
|
let downsample = (length, t): t =>
|
||||||
|
t
|
||||||
|
|> shapeMap(
|
||||||
|
XYShape.XsConversion.proportionByProbabilityMass(
|
||||||
|
length,
|
||||||
|
integral(t).xyShape,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let integralEndY = (t: t) =>
|
||||||
|
t.integralSumCache |> E.O.default(t |> integral |> lastY);
|
||||||
|
let integralXtoY = (f, t: t) =>
|
||||||
|
t |> integral |> shapeFn(XYShape.XtoY.linear(f));
|
||||||
|
let integralYtoX = (f, t: t) =>
|
||||||
|
t |> integral |> shapeFn(XYShape.YtoX.linear(f));
|
||||||
|
let toContinuous = t => Some(t);
|
||||||
|
let toDiscrete = _ => None;
|
||||||
|
|
||||||
|
let normalize = (t: t): t => {
|
||||||
|
t
|
||||||
|
|> updateIntegralCache(Some(integral(t)))
|
||||||
|
|> scaleBy(~scale=1. /. integralEndY(t))
|
||||||
|
|> updateIntegralSumCache(Some(1.0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mean = (t: t) => {
|
||||||
|
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 2.0 /. 2.0;
|
||||||
|
let indefiniteIntegralLinear = (p, a, b) =>
|
||||||
|
a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0;
|
||||||
|
|
||||||
|
XYShape.Analysis.integrateContinuousShape(
|
||||||
|
~indefiniteIntegralStepwise,
|
||||||
|
~indefiniteIntegralLinear,
|
||||||
|
t,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let variance = (t: t): float =>
|
||||||
|
XYShape.Analysis.getVarianceDangerously(
|
||||||
|
t,
|
||||||
|
mean,
|
||||||
|
XYShape.Analysis.getMeanOfSquaresContinuousShape,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* This simply creates multiple copies of the continuous distribution, scaled and shifted according to
|
||||||
|
each discrete data point, and then adds them all together. */
|
||||||
|
let combineAlgebraicallyWithDiscrete =
|
||||||
|
(
|
||||||
|
op: ExpressionTypes.algebraicOperation,
|
||||||
|
t1: t,
|
||||||
|
t2: DistTypes.discreteShape,
|
||||||
|
) => {
|
||||||
|
let t1s = t1 |> getShape;
|
||||||
|
let t2s = t2.xyShape; // TODO would like to use Discrete.getShape here, but current file structure doesn't allow for that
|
||||||
|
|
||||||
|
if (XYShape.T.isEmpty(t1s) || XYShape.T.isEmpty(t2s)) {
|
||||||
|
empty;
|
||||||
|
} else {
|
||||||
|
let continuousAsLinear =
|
||||||
|
switch (t1.interpolation) {
|
||||||
|
| `Linear => t1
|
||||||
|
| `Stepwise => stepwiseToLinear(t1)
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let combineAlgebraically =
|
||||||
|
(op: ExpressionTypes.algebraicOperation, t1: t, t2: t) => {
|
||||||
|
let s1 = t1 |> getShape;
|
||||||
|
let s2 = t2 |> getShape;
|
||||||
|
let t1n = s1 |> XYShape.T.length;
|
||||||
|
let t2n = s2 |> XYShape.T.length;
|
||||||
|
if (t1n == 0 || t2n == 0) {
|
||||||
|
empty;
|
||||||
|
} else {
|
||||||
|
let combinedShape =
|
||||||
|
AlgebraicShapeCombination.combineShapesContinuousContinuous(op, s1, s2);
|
||||||
|
let combinedIntegralSum =
|
||||||
|
Common.combineIntegralSums(
|
||||||
|
(a, b) => Some(a *. b),
|
||||||
|
t1.integralSumCache,
|
||||||
|
t2.integralSumCache,
|
||||||
|
);
|
||||||
|
// return a new Continuous distribution
|
||||||
|
make(~integralSumCache=combinedIntegralSum, combinedShape);
|
||||||
|
};
|
||||||
|
};
|
232
packages/playground/src/distPlus/distribution/Discrete.re
Normal file
232
packages/playground/src/distPlus/distribution/Discrete.re
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
open Distributions;
|
||||||
|
|
||||||
|
type t = DistTypes.discreteShape;
|
||||||
|
|
||||||
|
let make = (~integralSumCache=None, ~integralCache=None, xyShape): t => {xyShape, integralSumCache, integralCache};
|
||||||
|
let shapeMap = (fn, {xyShape, integralSumCache, integralCache}: t): t => {
|
||||||
|
xyShape: fn(xyShape),
|
||||||
|
integralSumCache,
|
||||||
|
integralCache
|
||||||
|
};
|
||||||
|
let getShape = (t: t) => t.xyShape;
|
||||||
|
let oShapeMap = (fn, {xyShape, integralSumCache, integralCache}: t): option(t) =>
|
||||||
|
fn(xyShape) |> E.O.fmap(make(~integralSumCache, ~integralCache));
|
||||||
|
|
||||||
|
let emptyIntegral: DistTypes.continuousShape = {
|
||||||
|
xyShape: {xs: [|neg_infinity|], ys: [|0.0|]},
|
||||||
|
interpolation: `Stepwise,
|
||||||
|
integralSumCache: Some(0.0),
|
||||||
|
integralCache: None,
|
||||||
|
};
|
||||||
|
let empty: DistTypes.discreteShape = {
|
||||||
|
xyShape: XYShape.T.empty,
|
||||||
|
integralSumCache: Some(0.0),
|
||||||
|
integralCache: Some(emptyIntegral),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let shapeFn = (fn, t: t) => t |> getShape |> fn;
|
||||||
|
|
||||||
|
let lastY = (t: t) => t |> getShape |> XYShape.T.lastY;
|
||||||
|
|
||||||
|
let combinePointwise =
|
||||||
|
(
|
||||||
|
~integralSumCachesFn = (_, _) => None,
|
||||||
|
~integralCachesFn: (DistTypes.continuousShape, DistTypes.continuousShape) => option(DistTypes.continuousShape) = (_, _) => None,
|
||||||
|
fn,
|
||||||
|
t1: DistTypes.discreteShape,
|
||||||
|
t2: DistTypes.discreteShape,
|
||||||
|
)
|
||||||
|
: DistTypes.discreteShape => {
|
||||||
|
let combinedIntegralSum =
|
||||||
|
Common.combineIntegralSums(
|
||||||
|
integralSumCachesFn,
|
||||||
|
t1.integralSumCache,
|
||||||
|
t2.integralSumCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: does it ever make sense to pointwise combine the integrals here?
|
||||||
|
// It could be done for pointwise additions, but is that ever needed?
|
||||||
|
|
||||||
|
make(
|
||||||
|
~integralSumCache=combinedIntegralSum,
|
||||||
|
XYShape.PointwiseCombination.combine(
|
||||||
|
(+.),
|
||||||
|
XYShape.XtoY.discreteInterpolator,
|
||||||
|
t1.xyShape,
|
||||||
|
t2.xyShape,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let reduce =
|
||||||
|
(~integralSumCachesFn=(_, _) => None,
|
||||||
|
~integralCachesFn=(_, _) => None,
|
||||||
|
fn, discreteShapes)
|
||||||
|
: DistTypes.discreteShape =>
|
||||||
|
discreteShapes
|
||||||
|
|> E.A.fold_left(combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn), empty);
|
||||||
|
|
||||||
|
let updateIntegralSumCache = (integralSumCache, t: t): t => {
|
||||||
|
...t,
|
||||||
|
integralSumCache,
|
||||||
|
};
|
||||||
|
|
||||||
|
let updateIntegralCache = (integralCache, t: t): t => {
|
||||||
|
...t,
|
||||||
|
integralCache,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* This multiples all of the data points together and creates a new discrete distribution from the results.
|
||||||
|
Data points at the same xs get added together. It may be a good idea to downsample t1 and t2 before and/or the result after. */
|
||||||
|
let combineAlgebraically =
|
||||||
|
(op: ExpressionTypes.algebraicOperation, t1: t, t2: t): t => {
|
||||||
|
let t1s = t1 |> getShape;
|
||||||
|
let t2s = t2 |> getShape;
|
||||||
|
let t1n = t1s |> XYShape.T.length;
|
||||||
|
let t2n = t2s |> XYShape.T.length;
|
||||||
|
|
||||||
|
let combinedIntegralSum =
|
||||||
|
Common.combineIntegralSums(
|
||||||
|
(s1, s2) => Some(s1 *. s2),
|
||||||
|
t1.integralSumCache,
|
||||||
|
t2.integralSumCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fn = Operation.Algebraic.toFn(op);
|
||||||
|
let xToYMap = E.FloatFloatMap.empty();
|
||||||
|
|
||||||
|
for (i in 0 to t1n - 1) {
|
||||||
|
for (j in 0 to t2n - 1) {
|
||||||
|
let x = fn(t1s.xs[i], t2s.xs[j]);
|
||||||
|
let cv = xToYMap |> E.FloatFloatMap.get(x) |> E.O.default(0.);
|
||||||
|
let my = t1s.ys[i] *. t2s.ys[j];
|
||||||
|
let _ = Belt.MutableMap.set(xToYMap, x, cv +. my);
|
||||||
|
();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let rxys = xToYMap |> E.FloatFloatMap.toArray |> XYShape.Zipped.sortByX;
|
||||||
|
|
||||||
|
let combinedShape = XYShape.T.fromZippedArray(rxys);
|
||||||
|
|
||||||
|
make(~integralSumCache=combinedIntegralSum, combinedShape);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mapY = (~integralSumCacheFn=_ => None,
|
||||||
|
~integralCacheFn=_ => None,
|
||||||
|
~fn, t: t) => {
|
||||||
|
make(
|
||||||
|
~integralSumCache=t.integralSumCache |> E.O.bind(_, integralSumCacheFn),
|
||||||
|
~integralCache=t.integralCache |> E.O.bind(_, integralCacheFn),
|
||||||
|
t |> getShape |> XYShape.T.mapY(fn),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let scaleBy = (~scale=1.0, t: t): t => {
|
||||||
|
let scaledIntegralSumCache = t.integralSumCache |> E.O.fmap((*.)(scale));
|
||||||
|
let scaledIntegralCache = t.integralCache |> E.O.fmap(Continuous.scaleBy(~scale));
|
||||||
|
|
||||||
|
t
|
||||||
|
|> mapY(~fn=(r: float) => r *. scale)
|
||||||
|
|> updateIntegralSumCache(scaledIntegralSumCache)
|
||||||
|
|> updateIntegralCache(scaledIntegralCache)
|
||||||
|
};
|
||||||
|
|
||||||
|
module T =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.discreteShape;
|
||||||
|
type integral = DistTypes.continuousShape;
|
||||||
|
let integral = (t) =>
|
||||||
|
switch (getShape(t) |> XYShape.T.isEmpty, t.integralCache) {
|
||||||
|
| (true, _) => emptyIntegral
|
||||||
|
| (false, Some(c)) => c
|
||||||
|
| (false, None) => {
|
||||||
|
let ts = getShape(t);
|
||||||
|
// The first xy of this integral should always be the zero, to ensure nice plotting
|
||||||
|
let firstX = ts |> XYShape.T.minX;
|
||||||
|
let prependedZeroPoint: XYShape.T.t = {xs: [|firstX -. epsilon_float|], ys: [|0.|]};
|
||||||
|
let integralShape =
|
||||||
|
ts
|
||||||
|
|> XYShape.T.concat(prependedZeroPoint)
|
||||||
|
|> XYShape.T.accumulateYs((+.));
|
||||||
|
|
||||||
|
Continuous.make(~interpolation=`Stepwise, integralShape);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let integralEndY = (t: t) =>
|
||||||
|
t.integralSumCache
|
||||||
|
|> E.O.default(t |> integral |> Continuous.lastY);
|
||||||
|
let minX = shapeFn(XYShape.T.minX);
|
||||||
|
let maxX = shapeFn(XYShape.T.maxX);
|
||||||
|
let toDiscreteProbabilityMassFraction = _ => 1.0;
|
||||||
|
let mapY = mapY;
|
||||||
|
let updateIntegralCache = updateIntegralCache;
|
||||||
|
let toShape = (t: t): DistTypes.shape => Discrete(t);
|
||||||
|
let toContinuous = _ => None;
|
||||||
|
let toDiscrete = t => Some(t);
|
||||||
|
|
||||||
|
let normalize = (t: t): t => {
|
||||||
|
t
|
||||||
|
|> scaleBy(~scale=1. /. integralEndY(t))
|
||||||
|
|> updateIntegralSumCache(Some(1.0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let downsample = (i, t: t): t => {
|
||||||
|
// It's not clear how to downsample a set of discrete points in a meaningful way.
|
||||||
|
// The best we can do is to clip off the smallest values.
|
||||||
|
let currentLength = t |> getShape |> XYShape.T.length;
|
||||||
|
|
||||||
|
if (i < currentLength && i >= 1 && currentLength > 1) {
|
||||||
|
t
|
||||||
|
|> getShape
|
||||||
|
|> XYShape.T.zip
|
||||||
|
|> XYShape.Zipped.sortByY
|
||||||
|
|> Belt.Array.reverse
|
||||||
|
|> Belt.Array.slice(_, ~offset=0, ~len=i)
|
||||||
|
|> XYShape.Zipped.sortByX
|
||||||
|
|> XYShape.T.fromZippedArray
|
||||||
|
|> make;
|
||||||
|
} else {
|
||||||
|
t;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let truncate =
|
||||||
|
(leftCutoff: option(float), rightCutoff: option(float), t: t): t => {
|
||||||
|
t
|
||||||
|
|> getShape
|
||||||
|
|> XYShape.T.zip
|
||||||
|
|> XYShape.Zipped.filterByX(x =>
|
||||||
|
x >= E.O.default(neg_infinity, leftCutoff)
|
||||||
|
&& x <= E.O.default(infinity, rightCutoff)
|
||||||
|
)
|
||||||
|
|> XYShape.T.fromZippedArray
|
||||||
|
|> make;
|
||||||
|
};
|
||||||
|
|
||||||
|
let xToY = (f, t) =>
|
||||||
|
t
|
||||||
|
|> getShape
|
||||||
|
|> XYShape.XtoY.stepwiseIfAtX(f)
|
||||||
|
|> E.O.default(0.0)
|
||||||
|
|> DistTypes.MixedPoint.makeDiscrete;
|
||||||
|
|
||||||
|
let integralXtoY = (f, t) =>
|
||||||
|
t |> integral |> Continuous.getShape |> XYShape.XtoY.linear(f);
|
||||||
|
|
||||||
|
let integralYtoX = (f, t) =>
|
||||||
|
t |> integral |> Continuous.getShape |> XYShape.YtoX.linear(f);
|
||||||
|
|
||||||
|
let mean = (t: t): float => {
|
||||||
|
let s = getShape(t);
|
||||||
|
E.A.reducei(s.xs, 0.0, (acc, x, i) => acc +. x *. s.ys[i]);
|
||||||
|
};
|
||||||
|
let variance = (t: t): float => {
|
||||||
|
let getMeanOfSquares = t =>
|
||||||
|
t |> shapeMap(XYShape.Analysis.squareXYShape) |> mean;
|
||||||
|
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares);
|
||||||
|
};
|
||||||
|
});
|
129
packages/playground/src/distPlus/distribution/DistPlus.re
Normal file
129
packages/playground/src/distPlus/distribution/DistPlus.re
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
open DistTypes;
|
||||||
|
|
||||||
|
type t = DistTypes.distPlus;
|
||||||
|
|
||||||
|
let shapeIntegral = shape => Shape.T.Integral.get(shape);
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~shape,
|
||||||
|
~squiggleString,
|
||||||
|
~domain=Complete,
|
||||||
|
~unit=UnspecifiedDistribution,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
: t => {
|
||||||
|
let integral = shapeIntegral(shape);
|
||||||
|
{shape, domain, integralCache: integral, unit, squiggleString};
|
||||||
|
};
|
||||||
|
|
||||||
|
let update =
|
||||||
|
(
|
||||||
|
~shape=?,
|
||||||
|
~integralCache=?,
|
||||||
|
~domain=?,
|
||||||
|
~unit=?,
|
||||||
|
~squiggleString=?,
|
||||||
|
t: t,
|
||||||
|
) => {
|
||||||
|
shape: E.O.default(t.shape, shape),
|
||||||
|
integralCache: E.O.default(t.integralCache, integralCache),
|
||||||
|
domain: E.O.default(t.domain, domain),
|
||||||
|
unit: E.O.default(t.unit, unit),
|
||||||
|
squiggleString: E.O.default(t.squiggleString, squiggleString),
|
||||||
|
};
|
||||||
|
|
||||||
|
let updateShape = (shape, t) => {
|
||||||
|
let integralCache = shapeIntegral(shape);
|
||||||
|
update(~shape, ~integralCache, t);
|
||||||
|
};
|
||||||
|
|
||||||
|
let domainIncludedProbabilityMass = (t: t) =>
|
||||||
|
Domain.includedProbabilityMass(t.domain);
|
||||||
|
|
||||||
|
let domainIncludedProbabilityMassAdjustment = (t: t, f) =>
|
||||||
|
f *. Domain.includedProbabilityMass(t.domain);
|
||||||
|
|
||||||
|
let toShape = ({shape, _}: t) => shape;
|
||||||
|
|
||||||
|
let shapeFn = (fn, {shape}: t) => fn(shape);
|
||||||
|
|
||||||
|
module T =
|
||||||
|
Distributions.Dist({
|
||||||
|
type t = DistTypes.distPlus;
|
||||||
|
type integral = DistTypes.distPlus;
|
||||||
|
let toShape = toShape;
|
||||||
|
let toContinuous = shapeFn(Shape.T.toContinuous);
|
||||||
|
let toDiscrete = shapeFn(Shape.T.toDiscrete);
|
||||||
|
|
||||||
|
let normalize = (t: t): t => {
|
||||||
|
let normalizedShape = t |> toShape |> Shape.T.normalize;
|
||||||
|
t |> updateShape(normalizedShape);
|
||||||
|
};
|
||||||
|
|
||||||
|
let truncate = (leftCutoff, rightCutoff, t: t): t => {
|
||||||
|
let truncatedShape =
|
||||||
|
t
|
||||||
|
|> toShape
|
||||||
|
|> Shape.T.truncate(leftCutoff, rightCutoff);
|
||||||
|
|
||||||
|
t |> updateShape(truncatedShape);
|
||||||
|
};
|
||||||
|
|
||||||
|
let xToY = (f, t: t) =>
|
||||||
|
t
|
||||||
|
|> toShape
|
||||||
|
|> Shape.T.xToY(f)
|
||||||
|
|> MixedPoint.fmap(domainIncludedProbabilityMassAdjustment(t));
|
||||||
|
|
||||||
|
let minX = shapeFn(Shape.T.minX);
|
||||||
|
let maxX = shapeFn(Shape.T.maxX);
|
||||||
|
let toDiscreteProbabilityMassFraction =
|
||||||
|
shapeFn(Shape.T.toDiscreteProbabilityMassFraction);
|
||||||
|
|
||||||
|
// This bit is kind of awkward, could probably use rethinking.
|
||||||
|
let integral = (t: t) =>
|
||||||
|
updateShape(Continuous(t.integralCache), t);
|
||||||
|
|
||||||
|
let updateIntegralCache = (integralCache: option(DistTypes.continuousShape), t) =>
|
||||||
|
update(~integralCache=E.O.default(t.integralCache, integralCache), t);
|
||||||
|
|
||||||
|
let downsample = (i, t): t =>
|
||||||
|
updateShape(t |> toShape |> Shape.T.downsample(i), t);
|
||||||
|
// todo: adjust for limit, maybe?
|
||||||
|
let mapY =
|
||||||
|
(
|
||||||
|
~integralSumCacheFn=previousIntegralSum => None,
|
||||||
|
~integralCacheFn=previousIntegralCache => None,
|
||||||
|
~fn,
|
||||||
|
{shape, _} as t: t,
|
||||||
|
)
|
||||||
|
: t =>
|
||||||
|
Shape.T.mapY(~integralSumCacheFn, ~fn, shape)
|
||||||
|
|> updateShape(_, t);
|
||||||
|
|
||||||
|
// get the total of everything
|
||||||
|
let integralEndY = (t: t) => {
|
||||||
|
Shape.T.Integral.sum(
|
||||||
|
toShape(t),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Fix this below, obviously. Adjust for limits
|
||||||
|
let integralXtoY = (f, t: t) => {
|
||||||
|
Shape.T.Integral.xToY(
|
||||||
|
f,
|
||||||
|
toShape(t),
|
||||||
|
)
|
||||||
|
|> domainIncludedProbabilityMassAdjustment(t);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This part is broken when there is a limit, if this is supposed to be taken into account.
|
||||||
|
let integralYtoX = (f, t: t) => {
|
||||||
|
Shape.T.Integral.yToX(f, toShape(t));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mean = (t: t) => {
|
||||||
|
Shape.T.mean(t.shape);
|
||||||
|
};
|
||||||
|
let variance = (t: t) => Shape.T.variance(t.shape);
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
open DistTypes;
|
||||||
|
|
||||||
|
type t = DistTypes.distPlus;
|
||||||
|
|
||||||
|
let unitToJson = ({unit}: t) => unit |> DistTypes.DistributionUnit.toJson;
|
||||||
|
|
||||||
|
let timeVector = ({unit}: t) =>
|
||||||
|
switch (unit) {
|
||||||
|
| TimeDistribution(timeVector) => Some(timeVector)
|
||||||
|
| UnspecifiedDistribution => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let timeInVectorToX = (f: TimeTypes.timeInVector, t: t) => {
|
||||||
|
let timeVector = t |> timeVector;
|
||||||
|
timeVector |> E.O.fmap(TimeTypes.RelativeTimePoint.toXValue(_, f));
|
||||||
|
};
|
||||||
|
|
||||||
|
let xToY = (f: TimeTypes.timeInVector, t: t) => {
|
||||||
|
timeInVectorToX(f, t) |> E.O.fmap(DistPlus.T.xToY(_, t));
|
||||||
|
};
|
||||||
|
|
||||||
|
module Integral = {
|
||||||
|
include DistPlus.T.Integral;
|
||||||
|
let xToY = (f: TimeTypes.timeInVector, t: t) => {
|
||||||
|
timeInVectorToX(f, t)
|
||||||
|
|> E.O.fmap(x => DistPlus.T.Integral.xToY(x, t));
|
||||||
|
};
|
||||||
|
};
|
179
packages/playground/src/distPlus/distribution/DistTypes.re
Normal file
179
packages/playground/src/distPlus/distribution/DistTypes.re
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
type domainLimit = {
|
||||||
|
xPoint: float,
|
||||||
|
excludingProbabilityMass: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
type domain =
|
||||||
|
| Complete
|
||||||
|
| LeftLimited(domainLimit)
|
||||||
|
| RightLimited(domainLimit)
|
||||||
|
| LeftAndRightLimited(domainLimit, domainLimit);
|
||||||
|
|
||||||
|
type distributionType = [
|
||||||
|
| `PDF
|
||||||
|
| `CDF
|
||||||
|
];
|
||||||
|
|
||||||
|
type xyShape = {
|
||||||
|
xs: array(float),
|
||||||
|
ys: array(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
type interpolationStrategy = [
|
||||||
|
| `Stepwise
|
||||||
|
| `Linear
|
||||||
|
];
|
||||||
|
type extrapolationStrategy = [
|
||||||
|
| `UseZero
|
||||||
|
| `UseOutermostPoints
|
||||||
|
];
|
||||||
|
|
||||||
|
type interpolator = (xyShape, int, float) => float;
|
||||||
|
|
||||||
|
type continuousShape = {
|
||||||
|
xyShape,
|
||||||
|
interpolation: interpolationStrategy,
|
||||||
|
integralSumCache: option(float),
|
||||||
|
integralCache: option(continuousShape),
|
||||||
|
};
|
||||||
|
|
||||||
|
type discreteShape = {
|
||||||
|
xyShape,
|
||||||
|
integralSumCache: option(float),
|
||||||
|
integralCache: option(continuousShape),
|
||||||
|
};
|
||||||
|
|
||||||
|
type mixedShape = {
|
||||||
|
continuous: continuousShape,
|
||||||
|
discrete: discreteShape,
|
||||||
|
integralSumCache: option(float),
|
||||||
|
integralCache: option(continuousShape),
|
||||||
|
};
|
||||||
|
|
||||||
|
type shapeMonad('a, 'b, 'c) =
|
||||||
|
| Mixed('a)
|
||||||
|
| Discrete('b)
|
||||||
|
| Continuous('c);
|
||||||
|
|
||||||
|
type shape = shapeMonad(mixedShape, discreteShape, continuousShape);
|
||||||
|
|
||||||
|
module ShapeMonad = {
|
||||||
|
let fmap =
|
||||||
|
(t: shapeMonad('a, 'b, 'c), (fn1, fn2, fn3)): shapeMonad('d, 'e, 'f) =>
|
||||||
|
switch (t) {
|
||||||
|
| Mixed(m) => Mixed(fn1(m))
|
||||||
|
| Discrete(m) => Discrete(fn2(m))
|
||||||
|
| Continuous(m) => Continuous(fn3(m))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type generationSource =
|
||||||
|
| SquiggleString(string)
|
||||||
|
| Shape(shape);
|
||||||
|
|
||||||
|
type distributionUnit =
|
||||||
|
| UnspecifiedDistribution
|
||||||
|
| TimeDistribution(TimeTypes.timeVector);
|
||||||
|
|
||||||
|
type distPlus = {
|
||||||
|
shape,
|
||||||
|
domain,
|
||||||
|
integralCache: continuousShape,
|
||||||
|
unit: distributionUnit,
|
||||||
|
squiggleString: option(string),
|
||||||
|
};
|
||||||
|
|
||||||
|
module DistributionUnit = {
|
||||||
|
let toJson = (distributionUnit: distributionUnit) =>
|
||||||
|
switch (distributionUnit) {
|
||||||
|
| TimeDistribution({zero, unit}) =>
|
||||||
|
Js.Null.fromOption(
|
||||||
|
Some({"zero": zero, "unit": unit |> TimeTypes.TimeUnit.toString}),
|
||||||
|
)
|
||||||
|
| _ => Js.Null.fromOption(None)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Domain = {
|
||||||
|
let excludedProbabilityMass = (t: domain) => {
|
||||||
|
switch (t) {
|
||||||
|
| Complete => 0.0
|
||||||
|
| LeftLimited({excludingProbabilityMass}) => excludingProbabilityMass
|
||||||
|
| RightLimited({excludingProbabilityMass}) => excludingProbabilityMass
|
||||||
|
| LeftAndRightLimited(
|
||||||
|
{excludingProbabilityMass: l},
|
||||||
|
{excludingProbabilityMass: r},
|
||||||
|
) =>
|
||||||
|
l +. r
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let includedProbabilityMass = (t: domain) =>
|
||||||
|
1.0 -. excludedProbabilityMass(t);
|
||||||
|
|
||||||
|
let initialProbabilityMass = (t: domain) => {
|
||||||
|
switch (t) {
|
||||||
|
| Complete
|
||||||
|
| RightLimited(_) => 0.0
|
||||||
|
| LeftLimited({excludingProbabilityMass}) => excludingProbabilityMass
|
||||||
|
| LeftAndRightLimited({excludingProbabilityMass}, _) => excludingProbabilityMass
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let normalizeProbabilityMass = (t: domain) => {
|
||||||
|
1. /. excludedProbabilityMass(t);
|
||||||
|
};
|
||||||
|
|
||||||
|
let yPointToSubYPoint = (t: domain, yPoint) => {
|
||||||
|
switch (t) {
|
||||||
|
| Complete => Some(yPoint)
|
||||||
|
| LeftLimited({excludingProbabilityMass})
|
||||||
|
when yPoint < excludingProbabilityMass =>
|
||||||
|
None
|
||||||
|
| LeftLimited({excludingProbabilityMass})
|
||||||
|
when yPoint >= excludingProbabilityMass =>
|
||||||
|
Some(
|
||||||
|
(yPoint -. excludingProbabilityMass) /. includedProbabilityMass(t),
|
||||||
|
)
|
||||||
|
| RightLimited({excludingProbabilityMass})
|
||||||
|
when yPoint > 1. -. excludingProbabilityMass =>
|
||||||
|
None
|
||||||
|
| RightLimited({excludingProbabilityMass})
|
||||||
|
when yPoint <= 1. -. excludingProbabilityMass =>
|
||||||
|
Some(yPoint /. includedProbabilityMass(t))
|
||||||
|
| LeftAndRightLimited({excludingProbabilityMass: l}, _) when yPoint < l =>
|
||||||
|
None
|
||||||
|
| LeftAndRightLimited(_, {excludingProbabilityMass: r})
|
||||||
|
when yPoint > 1.0 -. r =>
|
||||||
|
None
|
||||||
|
| LeftAndRightLimited({excludingProbabilityMass: l}, _) =>
|
||||||
|
Some((yPoint -. l) /. includedProbabilityMass(t))
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type mixedPoint = {
|
||||||
|
continuous: float,
|
||||||
|
discrete: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
module MixedPoint = {
|
||||||
|
type t = mixedPoint;
|
||||||
|
let toContinuousValue = (t: t) => t.continuous;
|
||||||
|
let toDiscreteValue = (t: t) => t.discrete;
|
||||||
|
let makeContinuous = (continuous: float): t => {continuous, discrete: 0.0};
|
||||||
|
let makeDiscrete = (discrete: float): t => {continuous: 0.0, discrete};
|
||||||
|
|
||||||
|
let fmap = (fn: float => float, t: t) => {
|
||||||
|
continuous: fn(t.continuous),
|
||||||
|
discrete: fn(t.discrete),
|
||||||
|
};
|
||||||
|
|
||||||
|
let combine2 = (fn, c: t, d: t): t => {
|
||||||
|
continuous: fn(c.continuous, d.continuous),
|
||||||
|
discrete: fn(c.discrete, d.discrete),
|
||||||
|
};
|
||||||
|
|
||||||
|
let add = combine2((a, b) => a +. b);
|
||||||
|
};
|
|
@ -0,0 +1,84 @@
|
||||||
|
module type dist = {
|
||||||
|
type t;
|
||||||
|
type integral;
|
||||||
|
let minX: t => float;
|
||||||
|
let maxX: t => float;
|
||||||
|
let mapY:
|
||||||
|
(~integralSumCacheFn: float => option(float)=?, ~integralCacheFn: DistTypes.continuousShape => option(DistTypes.continuousShape)=?, ~fn: float => float, t) => t;
|
||||||
|
let xToY: (float, t) => DistTypes.mixedPoint;
|
||||||
|
let toShape: t => DistTypes.shape;
|
||||||
|
let toContinuous: t => option(DistTypes.continuousShape);
|
||||||
|
let toDiscrete: t => option(DistTypes.discreteShape);
|
||||||
|
let normalize: t => t;
|
||||||
|
let toDiscreteProbabilityMassFraction: t => float;
|
||||||
|
let downsample: (int, t) => t;
|
||||||
|
let truncate: (option(float), option(float), t) => t;
|
||||||
|
|
||||||
|
let updateIntegralCache: (option(DistTypes.continuousShape), t) => t;
|
||||||
|
|
||||||
|
let integral: (t) => integral;
|
||||||
|
let integralEndY: (t) => float;
|
||||||
|
let integralXtoY: (float, t) => float;
|
||||||
|
let integralYtoX: (float, t) => float;
|
||||||
|
|
||||||
|
let mean: t => float;
|
||||||
|
let variance: t => float;
|
||||||
|
};
|
||||||
|
|
||||||
|
module Dist = (T: dist) => {
|
||||||
|
type t = T.t;
|
||||||
|
type integral = T.integral;
|
||||||
|
let minX = T.minX;
|
||||||
|
let maxX = T.maxX;
|
||||||
|
let integral = T.integral;
|
||||||
|
let xTotalRange = (t: t) => maxX(t) -. minX(t);
|
||||||
|
let mapY = T.mapY;
|
||||||
|
let xToY = T.xToY;
|
||||||
|
let downsample = T.downsample;
|
||||||
|
let toShape = T.toShape;
|
||||||
|
let toDiscreteProbabilityMassFraction = T.toDiscreteProbabilityMassFraction;
|
||||||
|
let toContinuous = T.toContinuous;
|
||||||
|
let toDiscrete = T.toDiscrete;
|
||||||
|
let normalize = T.normalize;
|
||||||
|
let truncate = T.truncate;
|
||||||
|
let mean = T.mean;
|
||||||
|
let variance = T.variance;
|
||||||
|
|
||||||
|
let updateIntegralCache = T.updateIntegralCache;
|
||||||
|
|
||||||
|
module Integral = {
|
||||||
|
type t = T.integral;
|
||||||
|
let get = T.integral;
|
||||||
|
let xToY = T.integralXtoY;
|
||||||
|
let yToX = T.integralYtoX;
|
||||||
|
let sum = T.integralEndY;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Common = {
|
||||||
|
let combineIntegralSums =
|
||||||
|
(
|
||||||
|
combineFn: (float, float) => option(float),
|
||||||
|
t1IntegralSumCache: option(float),
|
||||||
|
t2IntegralSumCache: option(float),
|
||||||
|
) => {
|
||||||
|
switch (t1IntegralSumCache, t2IntegralSumCache) {
|
||||||
|
| (None, _)
|
||||||
|
| (_, None) => None
|
||||||
|
| (Some(s1), Some(s2)) => combineFn(s1, s2)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let combineIntegrals =
|
||||||
|
(
|
||||||
|
combineFn: (DistTypes.continuousShape, DistTypes.continuousShape) => option(DistTypes.continuousShape),
|
||||||
|
t1IntegralCache: option(DistTypes.continuousShape),
|
||||||
|
t2IntegralCache: option(DistTypes.continuousShape),
|
||||||
|
) => {
|
||||||
|
switch (t1IntegralCache, t2IntegralCache) {
|
||||||
|
| (None, _)
|
||||||
|
| (_, None) => None
|
||||||
|
| (Some(s1), Some(s2)) => combineFn(s1, s2)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
332
packages/playground/src/distPlus/distribution/Mixed.re
Normal file
332
packages/playground/src/distPlus/distribution/Mixed.re
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
open Distributions;
|
||||||
|
|
||||||
|
type t = DistTypes.mixedShape;
|
||||||
|
let make = (~integralSumCache=None, ~integralCache=None, ~continuous, ~discrete): t => {continuous, discrete, integralSumCache, integralCache};
|
||||||
|
|
||||||
|
let totalLength = (t: t): int => {
|
||||||
|
let continuousLength =
|
||||||
|
t.continuous |> Continuous.getShape |> XYShape.T.length;
|
||||||
|
let discreteLength = t.discrete |> Discrete.getShape |> XYShape.T.length;
|
||||||
|
|
||||||
|
continuousLength + discreteLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
let scaleBy = (~scale=1.0, t: t): t => {
|
||||||
|
let scaledDiscrete = Discrete.scaleBy(~scale, t.discrete);
|
||||||
|
let scaledContinuous = Continuous.scaleBy(~scale, t.continuous);
|
||||||
|
let scaledIntegralCache = E.O.bind(t.integralCache, v => Some(Continuous.scaleBy(~scale, v)));
|
||||||
|
let scaledIntegralSumCache = E.O.bind(t.integralSumCache, s => Some(s *. scale));
|
||||||
|
make(~discrete=scaledDiscrete, ~continuous=scaledContinuous, ~integralSumCache=scaledIntegralSumCache, ~integralCache=scaledIntegralCache);
|
||||||
|
};
|
||||||
|
|
||||||
|
let toContinuous = ({continuous}: t) => Some(continuous);
|
||||||
|
let toDiscrete = ({discrete}: t) => Some(discrete);
|
||||||
|
|
||||||
|
let updateIntegralCache = (integralCache, t: t): t => {
|
||||||
|
...t,
|
||||||
|
integralCache,
|
||||||
|
};
|
||||||
|
|
||||||
|
module T =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.mixedShape;
|
||||||
|
type integral = DistTypes.continuousShape;
|
||||||
|
let minX = ({continuous, discrete}: t) => {
|
||||||
|
min(Continuous.T.minX(continuous), Discrete.T.minX(discrete));
|
||||||
|
};
|
||||||
|
let maxX = ({continuous, discrete}: t) =>
|
||||||
|
max(Continuous.T.maxX(continuous), Discrete.T.maxX(discrete));
|
||||||
|
let toShape = (t: t): DistTypes.shape => Mixed(t);
|
||||||
|
|
||||||
|
let updateIntegralCache = updateIntegralCache;
|
||||||
|
|
||||||
|
let toContinuous = toContinuous;
|
||||||
|
let toDiscrete = toDiscrete;
|
||||||
|
|
||||||
|
let truncate =
|
||||||
|
(
|
||||||
|
leftCutoff: option(float),
|
||||||
|
rightCutoff: option(float),
|
||||||
|
{discrete, continuous}: t,
|
||||||
|
) => {
|
||||||
|
let truncatedContinuous =
|
||||||
|
Continuous.T.truncate(leftCutoff, rightCutoff, continuous);
|
||||||
|
let truncatedDiscrete =
|
||||||
|
Discrete.T.truncate(leftCutoff, rightCutoff, discrete);
|
||||||
|
|
||||||
|
make(~integralSumCache=None, ~integralCache=None, ~discrete=truncatedDiscrete, ~continuous=truncatedContinuous);
|
||||||
|
};
|
||||||
|
|
||||||
|
let normalize = (t: t): t => {
|
||||||
|
let continuousIntegral = Continuous.T.Integral.get(t.continuous);
|
||||||
|
let discreteIntegral = Discrete.T.Integral.get(t.discrete);
|
||||||
|
|
||||||
|
let continuous = t.continuous |> Continuous.updateIntegralCache(Some(continuousIntegral));
|
||||||
|
let discrete = t.discrete |> Discrete.updateIntegralCache(Some(discreteIntegral));
|
||||||
|
|
||||||
|
let continuousIntegralSum =
|
||||||
|
Continuous.T.Integral.sum(continuous);
|
||||||
|
let discreteIntegralSum =
|
||||||
|
Discrete.T.Integral.sum(discrete);
|
||||||
|
let totalIntegralSum = continuousIntegralSum +. discreteIntegralSum;
|
||||||
|
|
||||||
|
let newContinuousSum = continuousIntegralSum /. totalIntegralSum;
|
||||||
|
let newDiscreteSum = discreteIntegralSum /. totalIntegralSum;
|
||||||
|
|
||||||
|
let normalizedContinuous =
|
||||||
|
continuous
|
||||||
|
|> Continuous.scaleBy(~scale=newContinuousSum /. continuousIntegralSum)
|
||||||
|
|> Continuous.updateIntegralSumCache(Some(newContinuousSum));
|
||||||
|
let normalizedDiscrete =
|
||||||
|
discrete
|
||||||
|
|> Discrete.scaleBy(~scale=newDiscreteSum /. discreteIntegralSum)
|
||||||
|
|> Discrete.updateIntegralSumCache(Some(newDiscreteSum));
|
||||||
|
|
||||||
|
make(~integralSumCache=Some(1.0), ~integralCache=None, ~continuous=normalizedContinuous, ~discrete=normalizedDiscrete);
|
||||||
|
};
|
||||||
|
|
||||||
|
let xToY = (x, t: t) => {
|
||||||
|
// This evaluates the mixedShape at x, interpolating if necessary.
|
||||||
|
// Note that we normalize entire mixedShape first.
|
||||||
|
let {continuous, discrete}: t = normalize(t);
|
||||||
|
let c = Continuous.T.xToY(x, continuous);
|
||||||
|
let d = Discrete.T.xToY(x, discrete);
|
||||||
|
DistTypes.MixedPoint.add(c, d); // "add" here just combines the two values into a single MixedPoint.
|
||||||
|
};
|
||||||
|
|
||||||
|
let toDiscreteProbabilityMassFraction = ({discrete, continuous}: t) => {
|
||||||
|
let discreteIntegralSum =
|
||||||
|
Discrete.T.Integral.sum(discrete);
|
||||||
|
let continuousIntegralSum =
|
||||||
|
Continuous.T.Integral.sum(continuous);
|
||||||
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
||||||
|
|
||||||
|
discreteIntegralSum /. totalIntegralSum;
|
||||||
|
};
|
||||||
|
|
||||||
|
let downsample = (count, t: t): t => {
|
||||||
|
// We will need to distribute the new xs fairly between the discrete and continuous shapes.
|
||||||
|
// The easiest way to do this is to simply go by the previous probability masses.
|
||||||
|
|
||||||
|
let discreteIntegralSum =
|
||||||
|
Discrete.T.Integral.sum(t.discrete);
|
||||||
|
let continuousIntegralSum =
|
||||||
|
Continuous.T.Integral.sum(t.continuous);
|
||||||
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
||||||
|
|
||||||
|
// TODO: figure out what to do when the totalIntegralSum is zero.
|
||||||
|
|
||||||
|
let downsampledDiscrete =
|
||||||
|
Discrete.T.downsample(
|
||||||
|
int_of_float(
|
||||||
|
float_of_int(count) *. (discreteIntegralSum /. totalIntegralSum),
|
||||||
|
),
|
||||||
|
t.discrete,
|
||||||
|
);
|
||||||
|
|
||||||
|
let downsampledContinuous =
|
||||||
|
Continuous.T.downsample(
|
||||||
|
int_of_float(
|
||||||
|
float_of_int(count) *. (continuousIntegralSum /. totalIntegralSum),
|
||||||
|
),
|
||||||
|
t.continuous,
|
||||||
|
);
|
||||||
|
|
||||||
|
{...t, discrete: downsampledDiscrete, continuous: downsampledContinuous};
|
||||||
|
};
|
||||||
|
|
||||||
|
let integral = (t: t) => {
|
||||||
|
switch (t.integralCache) {
|
||||||
|
| Some(cache) => cache
|
||||||
|
| None =>
|
||||||
|
// note: if the underlying shapes aren't normalized, then these integrals won't be either -- but that's the way it should be.
|
||||||
|
let continuousIntegral = Continuous.T.Integral.get(t.continuous);
|
||||||
|
let discreteIntegral = Continuous.stepwiseToLinear(Discrete.T.Integral.get(t.discrete));
|
||||||
|
|
||||||
|
Continuous.make(
|
||||||
|
XYShape.PointwiseCombination.combine(
|
||||||
|
(+.),
|
||||||
|
XYShape.XtoY.continuousInterpolator(`Linear, `UseOutermostPoints),
|
||||||
|
Continuous.getShape(continuousIntegral),
|
||||||
|
Continuous.getShape(discreteIntegral),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let integralEndY = (t: t) => {
|
||||||
|
t |> integral |> Continuous.lastY;
|
||||||
|
};
|
||||||
|
|
||||||
|
let integralXtoY = (f, t) => {
|
||||||
|
t |> integral |> Continuous.getShape |> XYShape.XtoY.linear(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
let integralYtoX = (f, t) => {
|
||||||
|
t |> integral |> Continuous.getShape |> XYShape.YtoX.linear(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This pipes all ys (continuous and discrete) through fn.
|
||||||
|
// If mapY is a linear operation, we might be able to update the integralSumCaches as well;
|
||||||
|
// if not, they'll be set to None.
|
||||||
|
let mapY =
|
||||||
|
(
|
||||||
|
~integralSumCacheFn=previousIntegralSum => None,
|
||||||
|
~integralCacheFn=previousIntegral => None,
|
||||||
|
~fn,
|
||||||
|
t: t,
|
||||||
|
)
|
||||||
|
: t => {
|
||||||
|
let yMappedDiscrete: DistTypes.discreteShape =
|
||||||
|
t.discrete
|
||||||
|
|> Discrete.T.mapY(~fn)
|
||||||
|
|> Discrete.updateIntegralSumCache(E.O.bind(t.discrete.integralSumCache, integralSumCacheFn))
|
||||||
|
|> Discrete.updateIntegralCache(E.O.bind(t.discrete.integralCache, integralCacheFn));
|
||||||
|
|
||||||
|
let yMappedContinuous: DistTypes.continuousShape =
|
||||||
|
t.continuous
|
||||||
|
|> Continuous.T.mapY(~fn)
|
||||||
|
|> Continuous.updateIntegralSumCache(E.O.bind(t.continuous.integralSumCache, integralSumCacheFn))
|
||||||
|
|> Continuous.updateIntegralCache(E.O.bind(t.continuous.integralCache, integralCacheFn));
|
||||||
|
|
||||||
|
{
|
||||||
|
discrete: yMappedDiscrete,
|
||||||
|
continuous: yMappedContinuous,
|
||||||
|
integralSumCache: E.O.bind(t.integralSumCache, integralSumCacheFn),
|
||||||
|
integralCache: E.O.bind(t.integralCache, integralCacheFn),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let mean = ({discrete, continuous}: t): float => {
|
||||||
|
let discreteMean = Discrete.T.mean(discrete);
|
||||||
|
let continuousMean = Continuous.T.mean(continuous);
|
||||||
|
|
||||||
|
// the combined mean is the weighted sum of the two:
|
||||||
|
let discreteIntegralSum = Discrete.T.Integral.sum(discrete);
|
||||||
|
let continuousIntegralSum = Continuous.T.Integral.sum(continuous);
|
||||||
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
||||||
|
|
||||||
|
(
|
||||||
|
discreteMean
|
||||||
|
*. discreteIntegralSum
|
||||||
|
+. continuousMean
|
||||||
|
*. continuousIntegralSum
|
||||||
|
)
|
||||||
|
/. totalIntegralSum;
|
||||||
|
};
|
||||||
|
|
||||||
|
let variance = ({discrete, continuous} as t: t): float => {
|
||||||
|
// the combined mean is the weighted sum of the two:
|
||||||
|
let discreteIntegralSum = Discrete.T.Integral.sum(discrete);
|
||||||
|
let continuousIntegralSum = Continuous.T.Integral.sum(continuous);
|
||||||
|
let totalIntegralSum = discreteIntegralSum +. continuousIntegralSum;
|
||||||
|
|
||||||
|
let getMeanOfSquares = ({discrete, continuous}: t) => {
|
||||||
|
let discreteMean =
|
||||||
|
discrete
|
||||||
|
|> Discrete.shapeMap(XYShape.Analysis.squareXYShape)
|
||||||
|
|> Discrete.T.mean;
|
||||||
|
let continuousMean =
|
||||||
|
continuous |> XYShape.Analysis.getMeanOfSquaresContinuousShape;
|
||||||
|
(
|
||||||
|
discreteMean
|
||||||
|
*. discreteIntegralSum
|
||||||
|
+. continuousMean
|
||||||
|
*. continuousIntegralSum
|
||||||
|
)
|
||||||
|
/. totalIntegralSum;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (discreteIntegralSum /. totalIntegralSum) {
|
||||||
|
| 1.0 => Discrete.T.variance(discrete)
|
||||||
|
| 0.0 => Continuous.T.variance(continuous)
|
||||||
|
| _ =>
|
||||||
|
XYShape.Analysis.getVarianceDangerously(t, mean, getMeanOfSquares)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let combineAlgebraically =
|
||||||
|
(op: ExpressionTypes.algebraicOperation, t1: t, t2: t)
|
||||||
|
: t => {
|
||||||
|
// Discrete convolution can cause a huge increase in the number of samples,
|
||||||
|
// so we'll first downsample.
|
||||||
|
|
||||||
|
// An alternative (to be explored in the future) may be to first perform the full convolution and then to downsample the result;
|
||||||
|
// to use non-uniform fast Fourier transforms (for addition only), add web workers or gpu.js, etc. ...
|
||||||
|
|
||||||
|
// we have to figure out where to downsample, and how to effectively
|
||||||
|
//let downsampleIfTooLarge = (t: t) => {
|
||||||
|
// let sqtl = sqrt(float_of_int(totalLength(t)));
|
||||||
|
// sqtl > 10 ? T.downsample(int_of_float(sqtl), t) : t;
|
||||||
|
//};
|
||||||
|
|
||||||
|
let t1d = t1;
|
||||||
|
let t2d = t2;
|
||||||
|
|
||||||
|
// continuous (*) continuous => continuous, but also
|
||||||
|
// discrete (*) continuous => continuous (and vice versa). We have to take care of all combos and then combine them:
|
||||||
|
let ccConvResult =
|
||||||
|
Continuous.combineAlgebraically(
|
||||||
|
op,
|
||||||
|
t1.continuous,
|
||||||
|
t2.continuous,
|
||||||
|
);
|
||||||
|
let dcConvResult =
|
||||||
|
Continuous.combineAlgebraicallyWithDiscrete(
|
||||||
|
op,
|
||||||
|
t2.continuous,
|
||||||
|
t1.discrete,
|
||||||
|
);
|
||||||
|
let cdConvResult =
|
||||||
|
Continuous.combineAlgebraicallyWithDiscrete(
|
||||||
|
op,
|
||||||
|
t1.continuous,
|
||||||
|
t2.discrete,
|
||||||
|
);
|
||||||
|
let continuousConvResult =
|
||||||
|
Continuous.reduce((+.), [|ccConvResult, dcConvResult, cdConvResult|]);
|
||||||
|
|
||||||
|
// ... finally, discrete (*) discrete => discrete, obviously:
|
||||||
|
let discreteConvResult =
|
||||||
|
Discrete.combineAlgebraically(op, t1.discrete, t2.discrete);
|
||||||
|
|
||||||
|
let combinedIntegralSum =
|
||||||
|
Common.combineIntegralSums(
|
||||||
|
(a, b) => Some(a *. b),
|
||||||
|
t1.integralSumCache,
|
||||||
|
t2.integralSumCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
{discrete: discreteConvResult, continuous: continuousConvResult, integralSumCache: combinedIntegralSum, integralCache: None};
|
||||||
|
};
|
||||||
|
|
||||||
|
let combinePointwise = (~integralSumCachesFn = (_, _) => None, ~integralCachesFn = (_, _) => None, fn, t1: t, t2: t): t => {
|
||||||
|
let reducedDiscrete =
|
||||||
|
[|t1, t2|]
|
||||||
|
|> E.A.fmap(toDiscrete)
|
||||||
|
|> E.A.O.concatSomes
|
||||||
|
|> Discrete.reduce(~integralSumCachesFn, ~integralCachesFn, fn);
|
||||||
|
|
||||||
|
let reducedContinuous =
|
||||||
|
[|t1, t2|]
|
||||||
|
|> E.A.fmap(toContinuous)
|
||||||
|
|> E.A.O.concatSomes
|
||||||
|
|> Continuous.reduce(~integralSumCachesFn, ~integralCachesFn, fn);
|
||||||
|
|
||||||
|
let combinedIntegralSum =
|
||||||
|
Common.combineIntegralSums(
|
||||||
|
integralSumCachesFn,
|
||||||
|
t1.integralSumCache,
|
||||||
|
t2.integralSumCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
let combinedIntegral =
|
||||||
|
Common.combineIntegrals(
|
||||||
|
integralCachesFn,
|
||||||
|
t1.integralCache,
|
||||||
|
t2.integralCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
make(~integralSumCache=combinedIntegralSum, ~integralCache=combinedIntegral, ~discrete=reducedDiscrete, ~continuous=reducedContinuous);
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
type assumption =
|
||||||
|
| ADDS_TO_1
|
||||||
|
| ADDS_TO_CORRECT_PROBABILITY;
|
||||||
|
|
||||||
|
type assumptions = {
|
||||||
|
continuous: assumption,
|
||||||
|
discrete: assumption,
|
||||||
|
discreteProbabilityMass: option(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
let buildSimple = (~continuous: option(DistTypes.continuousShape), ~discrete: option(DistTypes.discreteShape)): option(DistTypes.shape) => {
|
||||||
|
let continuous = continuous |> E.O.default(Continuous.make(~integralSumCache=Some(0.0), {xs: [||], ys: [||]}));
|
||||||
|
let discrete = discrete |> E.O.default(Discrete.make(~integralSumCache=Some(0.0), {xs: [||], ys: [||]}));
|
||||||
|
let cLength =
|
||||||
|
continuous
|
||||||
|
|> Continuous.getShape
|
||||||
|
|> XYShape.T.xs
|
||||||
|
|> E.A.length;
|
||||||
|
let dLength = discrete |> Discrete.getShape |> XYShape.T.xs |> E.A.length;
|
||||||
|
switch (cLength, dLength) {
|
||||||
|
| (0 | 1, 0) => None
|
||||||
|
| (0 | 1, _) => Some(Discrete(discrete))
|
||||||
|
| (_, 0) => Some(Continuous(continuous))
|
||||||
|
| (_, _) =>
|
||||||
|
let mixedDist =
|
||||||
|
Mixed.make(
|
||||||
|
~integralSumCache=None,
|
||||||
|
~integralCache=None,
|
||||||
|
~continuous,
|
||||||
|
~discrete,
|
||||||
|
);
|
||||||
|
Some(Mixed(mixedDist));
|
||||||
|
};
|
||||||
|
};
|
240
packages/playground/src/distPlus/distribution/Shape.re
Normal file
240
packages/playground/src/distPlus/distribution/Shape.re
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
open Distributions;
|
||||||
|
|
||||||
|
type t = DistTypes.shape;
|
||||||
|
let mapToAll = ((fn1, fn2, fn3), t: t) =>
|
||||||
|
switch (t) {
|
||||||
|
| Mixed(m) => fn1(m)
|
||||||
|
| Discrete(m) => fn2(m)
|
||||||
|
| Continuous(m) => fn3(m)
|
||||||
|
};
|
||||||
|
|
||||||
|
let fmap = ((fn1, fn2, fn3), t: t): t =>
|
||||||
|
switch (t) {
|
||||||
|
| Mixed(m) => Mixed(fn1(m))
|
||||||
|
| Discrete(m) => Discrete(fn2(m))
|
||||||
|
| Continuous(m) => Continuous(fn3(m))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let toMixed =
|
||||||
|
mapToAll((
|
||||||
|
m => m,
|
||||||
|
d => Mixed.make(~integralSumCache=d.integralSumCache, ~integralCache=d.integralCache, ~discrete=d, ~continuous=Continuous.empty),
|
||||||
|
c => Mixed.make(~integralSumCache=c.integralSumCache, ~integralCache=c.integralCache, ~discrete=Discrete.empty, ~continuous=c),
|
||||||
|
));
|
||||||
|
|
||||||
|
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;
|
||||||
|
| (Continuous(m1), Discrete(m2))
|
||||||
|
| (Discrete(m2), Continuous(m1)) =>
|
||||||
|
Continuous.combineAlgebraicallyWithDiscrete(op, m1, m2) |> Continuous.T.toShape
|
||||||
|
| (Discrete(m1), Discrete(m2)) =>
|
||||||
|
Discrete.combineAlgebraically(op, m1, m2) |> Discrete.T.toShape
|
||||||
|
| (m1, m2) =>
|
||||||
|
Mixed.combineAlgebraically(
|
||||||
|
op,
|
||||||
|
toMixed(m1),
|
||||||
|
toMixed(m2),
|
||||||
|
)
|
||||||
|
|> Mixed.T.toShape
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let combinePointwise =
|
||||||
|
(~integralSumCachesFn: (float, float) => option(float) = (_, _) => None,
|
||||||
|
~integralCachesFn: (DistTypes.continuousShape, DistTypes.continuousShape) => option(DistTypes.continuousShape) = (_, _) => None,
|
||||||
|
fn,
|
||||||
|
t1: t,
|
||||||
|
t2: t) =>
|
||||||
|
switch (t1, t2) {
|
||||||
|
| (Continuous(m1), Continuous(m2)) =>
|
||||||
|
DistTypes.Continuous(
|
||||||
|
Continuous.combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn, m1, m2),
|
||||||
|
)
|
||||||
|
| (Discrete(m1), Discrete(m2)) =>
|
||||||
|
DistTypes.Discrete(
|
||||||
|
Discrete.combinePointwise(~integralSumCachesFn, ~integralCachesFn, fn, m1, m2),
|
||||||
|
)
|
||||||
|
| (m1, m2) =>
|
||||||
|
DistTypes.Mixed(
|
||||||
|
Mixed.combinePointwise(
|
||||||
|
~integralSumCachesFn,
|
||||||
|
~integralCachesFn,
|
||||||
|
fn,
|
||||||
|
toMixed(m1),
|
||||||
|
toMixed(m2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
module T =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.shape;
|
||||||
|
type integral = DistTypes.continuousShape;
|
||||||
|
|
||||||
|
let xToY = (f: float) =>
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.xToY(f),
|
||||||
|
Discrete.T.xToY(f),
|
||||||
|
Continuous.T.xToY(f),
|
||||||
|
));
|
||||||
|
|
||||||
|
let toShape = (t: t) => t;
|
||||||
|
|
||||||
|
let toContinuous = t => None;
|
||||||
|
let toDiscrete = t => None;
|
||||||
|
|
||||||
|
let downsample = (i, t) =>
|
||||||
|
fmap(
|
||||||
|
(
|
||||||
|
Mixed.T.downsample(i),
|
||||||
|
Discrete.T.downsample(i),
|
||||||
|
Continuous.T.downsample(i),
|
||||||
|
),
|
||||||
|
t,
|
||||||
|
);
|
||||||
|
|
||||||
|
let truncate = (leftCutoff, rightCutoff, t): t =>
|
||||||
|
fmap(
|
||||||
|
(
|
||||||
|
Mixed.T.truncate(leftCutoff, rightCutoff),
|
||||||
|
Discrete.T.truncate(leftCutoff, rightCutoff),
|
||||||
|
Continuous.T.truncate(leftCutoff, rightCutoff),
|
||||||
|
),
|
||||||
|
t,
|
||||||
|
);
|
||||||
|
|
||||||
|
let toDiscreteProbabilityMassFraction = t => 0.0;
|
||||||
|
|
||||||
|
let normalize =
|
||||||
|
fmap((
|
||||||
|
Mixed.T.normalize,
|
||||||
|
Discrete.T.normalize,
|
||||||
|
Continuous.T.normalize
|
||||||
|
));
|
||||||
|
|
||||||
|
let updateIntegralCache = (integralCache, t: t): t =>
|
||||||
|
fmap((
|
||||||
|
Mixed.T.updateIntegralCache(integralCache),
|
||||||
|
Discrete.T.updateIntegralCache(integralCache),
|
||||||
|
Continuous.T.updateIntegralCache(integralCache),
|
||||||
|
), t);
|
||||||
|
|
||||||
|
let toContinuous =
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.toContinuous,
|
||||||
|
Discrete.T.toContinuous,
|
||||||
|
Continuous.T.toContinuous,
|
||||||
|
));
|
||||||
|
let toDiscrete =
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.toDiscrete,
|
||||||
|
Discrete.T.toDiscrete,
|
||||||
|
Continuous.T.toDiscrete,
|
||||||
|
));
|
||||||
|
|
||||||
|
let toDiscreteProbabilityMassFraction =
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.toDiscreteProbabilityMassFraction,
|
||||||
|
Discrete.T.toDiscreteProbabilityMassFraction,
|
||||||
|
Continuous.T.toDiscreteProbabilityMassFraction,
|
||||||
|
));
|
||||||
|
|
||||||
|
let minX = mapToAll((Mixed.T.minX, Discrete.T.minX, Continuous.T.minX));
|
||||||
|
let integral =
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.Integral.get,
|
||||||
|
Discrete.T.Integral.get,
|
||||||
|
Continuous.T.Integral.get,
|
||||||
|
));
|
||||||
|
let integralEndY =
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.Integral.sum,
|
||||||
|
Discrete.T.Integral.sum,
|
||||||
|
Continuous.T.Integral.sum,
|
||||||
|
));
|
||||||
|
let integralXtoY = (f) => {
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.Integral.xToY(f),
|
||||||
|
Discrete.T.Integral.xToY(f),
|
||||||
|
Continuous.T.Integral.xToY(f),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let integralYtoX = (f) => {
|
||||||
|
mapToAll((
|
||||||
|
Mixed.T.Integral.yToX(f),
|
||||||
|
Discrete.T.Integral.yToX(f),
|
||||||
|
Continuous.T.Integral.yToX(f),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let maxX = mapToAll((Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
|
||||||
|
let mapY = (~integralSumCacheFn=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) {
|
||||||
|
| Mixed(m) => Mixed.T.mean(m)
|
||||||
|
| Discrete(m) => Discrete.T.mean(m)
|
||||||
|
| Continuous(m) => Continuous.T.mean(m)
|
||||||
|
};
|
||||||
|
|
||||||
|
let variance = (t: t): float =>
|
||||||
|
switch (t) {
|
||||||
|
| Mixed(m) => Mixed.T.variance(m)
|
||||||
|
| Discrete(m) => Discrete.T.variance(m)
|
||||||
|
| Continuous(m) => Continuous.T.variance(m)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let pdf = (f: float, t: t) => {
|
||||||
|
let mixedPoint: DistTypes.mixedPoint = T.xToY(f, t);
|
||||||
|
mixedPoint.continuous +. mixedPoint.discrete;
|
||||||
|
};
|
||||||
|
|
||||||
|
let inv = T.Integral.yToX;
|
||||||
|
let cdf = T.Integral.xToY;
|
||||||
|
|
||||||
|
let doN = (n, fn) => {
|
||||||
|
let items = Belt.Array.make(n, 0.0);
|
||||||
|
for (x in 0 to n - 1) {
|
||||||
|
let _ = Belt.Array.set(items, x, fn());
|
||||||
|
();
|
||||||
|
};
|
||||||
|
items;
|
||||||
|
};
|
||||||
|
|
||||||
|
let sample = (t: t): float => {
|
||||||
|
let randomItem = Random.float(1.);
|
||||||
|
let bar = t |> T.Integral.yToX(randomItem);
|
||||||
|
bar;
|
||||||
|
};
|
||||||
|
|
||||||
|
let isFloat = (t:t) => switch(t){
|
||||||
|
| Discrete({xyShape: {xs: [|_|], ys: [|1.0|]}}) => true
|
||||||
|
| _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
let sampleNRendered = (n, dist) => {
|
||||||
|
let integralCache = T.Integral.get(dist);
|
||||||
|
let distWithUpdatedIntegralCache = T.updateIntegralCache(Some(integralCache), dist);
|
||||||
|
|
||||||
|
doN(n, () => sample(distWithUpdatedIntegralCache));
|
||||||
|
};
|
||||||
|
|
||||||
|
let operate = (distToFloatOp: ExpressionTypes.distToFloatOperation, s): float =>
|
||||||
|
switch (distToFloatOp) {
|
||||||
|
| `Pdf(f) => pdf(f, s)
|
||||||
|
| `Cdf(f) => pdf(f, s)
|
||||||
|
| `Inv(f) => inv(f, s)
|
||||||
|
| `Sample => sample(s)
|
||||||
|
| `Mean => T.mean(s)
|
||||||
|
};
|
83
packages/playground/src/distPlus/distribution/TimeTypes.res
Normal file
83
packages/playground/src/distPlus/distribution/TimeTypes.res
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
type timeUnit = [
|
||||||
|
| #days
|
||||||
|
| #hours
|
||||||
|
| #milliseconds
|
||||||
|
| #minutes
|
||||||
|
| #months
|
||||||
|
| #quarters
|
||||||
|
| #seconds
|
||||||
|
| #weeks
|
||||||
|
| #years
|
||||||
|
]
|
||||||
|
|
||||||
|
type timeVector = {
|
||||||
|
zero: MomentRe.Moment.t,
|
||||||
|
unit: timeUnit,
|
||||||
|
}
|
||||||
|
|
||||||
|
type timePoint = {
|
||||||
|
timeVector: timeVector,
|
||||||
|
value: float,
|
||||||
|
}
|
||||||
|
|
||||||
|
module TimeUnit = {
|
||||||
|
let toString = (timeUnit: timeUnit) =>
|
||||||
|
switch timeUnit {
|
||||||
|
| #days => "days"
|
||||||
|
| #hours => "hours"
|
||||||
|
| #milliseconds => "milliseconds"
|
||||||
|
| #minutes => "minutes"
|
||||||
|
| #months => "months"
|
||||||
|
| #quarters => "quarters"
|
||||||
|
| #seconds => "seconds"
|
||||||
|
| #weeks => "weeks"
|
||||||
|
| #years => "years"
|
||||||
|
}
|
||||||
|
|
||||||
|
let ofString = (timeUnit: string) =>
|
||||||
|
switch timeUnit {
|
||||||
|
| "days" => #days
|
||||||
|
| "hours" => #hours
|
||||||
|
| "milliseconds" => #milliseconds
|
||||||
|
| "minutes" => #minutes
|
||||||
|
| "months" => #months
|
||||||
|
| "quarters" => #quarters
|
||||||
|
| "seconds" => #seconds
|
||||||
|
| "weeks" => #weeks
|
||||||
|
| "years" => #years
|
||||||
|
| _ => Js.Exn.raiseError("TimeUnit is unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module TimePoint = {
|
||||||
|
let fromTimeVector = (timeVector, value): timePoint => {timeVector: timeVector, value: value}
|
||||||
|
|
||||||
|
let toMoment = (timePoint: timePoint) =>
|
||||||
|
timePoint.timeVector.zero |> MomentRe.Moment.add(
|
||||||
|
~duration=MomentRe.duration(timePoint.value, timePoint.timeVector.unit),
|
||||||
|
)
|
||||||
|
|
||||||
|
let fromMoment = (timeVector: timeVector, moment: MomentRe.Moment.t) =>
|
||||||
|
MomentRe.diff(timeVector.zero, moment, timeVector.unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
type timeInVector =
|
||||||
|
| Time(MomentRe.Moment.t)
|
||||||
|
| XValue(float)
|
||||||
|
|
||||||
|
module RelativeTimePoint = {
|
||||||
|
let toTime = (timeVector: timeVector, timeInVector: timeInVector) =>
|
||||||
|
switch timeInVector {
|
||||||
|
| Time(r) => r
|
||||||
|
| XValue(r) =>
|
||||||
|
timeVector.zero |> MomentRe.Moment.add(~duration=MomentRe.duration(r, timeVector.unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
let _timeToX = (time, timeStart, timeUnit) => MomentRe.diff(time, timeStart, timeUnit)
|
||||||
|
|
||||||
|
let toXValue = (timeVector: timeVector, timeInVector: timeInVector) =>
|
||||||
|
switch timeInVector {
|
||||||
|
| Time(r) => _timeToX(r, timeVector.zero, timeVector.unit)
|
||||||
|
| XValue(r) => r
|
||||||
|
}
|
||||||
|
}
|
504
packages/playground/src/distPlus/distribution/XYShape.re
Normal file
504
packages/playground/src/distPlus/distribution/XYShape.re
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
open DistTypes;
|
||||||
|
|
||||||
|
let interpolate =
|
||||||
|
(xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float)
|
||||||
|
: float => {
|
||||||
|
let minProportion = (xMax -. xIntended) /. (xMax -. xMin);
|
||||||
|
let maxProportion = (xIntended -. xMin) /. (xMax -. xMin);
|
||||||
|
yMin *. minProportion +. yMax *. maxProportion;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Make sure that shapes cannot be empty.
|
||||||
|
let extImp = E.O.toExt("Tried to perform an operation on an empty XYShape.");
|
||||||
|
|
||||||
|
module T = {
|
||||||
|
type t = xyShape;
|
||||||
|
let toXyShape = (t: t): xyShape => t;
|
||||||
|
type ts = array(xyShape);
|
||||||
|
let xs = (t: t) => t.xs;
|
||||||
|
let ys = (t: t) => t.ys;
|
||||||
|
let length = (t: t) => E.A.length(t.xs);
|
||||||
|
let empty = {xs: [||], ys: [||]};
|
||||||
|
let isEmpty = (t: t) => length(t) == 0;
|
||||||
|
let minX = (t: t) => t |> xs |> E.A.Sorted.min |> extImp;
|
||||||
|
let maxX = (t: t) => t |> xs |> E.A.Sorted.max |> extImp;
|
||||||
|
let firstY = (t: t) => t |> ys |> E.A.first |> extImp;
|
||||||
|
let lastY = (t: t) => t |> ys |> E.A.last |> extImp;
|
||||||
|
let xTotalRange = (t: t) => maxX(t) -. minX(t);
|
||||||
|
let mapX = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys};
|
||||||
|
let mapY = (fn, t: t): t => {xs: t.xs, ys: E.A.fmap(fn, t.ys)};
|
||||||
|
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys);
|
||||||
|
let fromArray = ((xs, ys)): t => {xs, ys};
|
||||||
|
let fromArrays = (xs, ys): t => {xs, ys};
|
||||||
|
let accumulateYs = (fn, p: t) => {
|
||||||
|
fromArray((p.xs, E.A.accumulate(fn, p.ys)));
|
||||||
|
};
|
||||||
|
let concat = (t1: t, t2: t) => {
|
||||||
|
let cxs = Array.concat([t1.xs, t2.xs]);
|
||||||
|
let cys = Array.concat([t1.ys, t2.ys]);
|
||||||
|
{xs: cxs, ys: cys};
|
||||||
|
};
|
||||||
|
let fromZippedArray = (pairs: array((float, float))): t =>
|
||||||
|
pairs |> Belt.Array.unzip |> fromArray;
|
||||||
|
let equallyDividedXs = (t: t, newLength) => {
|
||||||
|
E.A.Floats.range(minX(t), maxX(t), newLength);
|
||||||
|
};
|
||||||
|
let toJs = (t: t) => {
|
||||||
|
{"xs": t.xs, "ys": t.ys};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Ts = {
|
||||||
|
type t = T.ts;
|
||||||
|
let minX = (t: t) => t |> E.A.fmap(T.minX) |> E.A.min |> extImp;
|
||||||
|
let maxX = (t: t) => t |> E.A.fmap(T.maxX) |> E.A.max |> extImp;
|
||||||
|
let equallyDividedXs = (t: t, newLength) => {
|
||||||
|
E.A.Floats.range(minX(t), maxX(t), newLength);
|
||||||
|
};
|
||||||
|
let allXs = (t: t) => t |> E.A.fmap(T.xs) |> E.A.Sorted.concatMany;
|
||||||
|
};
|
||||||
|
|
||||||
|
module Pairs = {
|
||||||
|
let x = fst;
|
||||||
|
let y = snd;
|
||||||
|
let first = (t: T.t) => (T.minX(t), T.firstY(t));
|
||||||
|
let last = (t: T.t) => (T.maxX(t), T.lastY(t));
|
||||||
|
|
||||||
|
let getBy = (t: T.t, fn) => t |> T.zip |> E.A.getBy(_, fn);
|
||||||
|
|
||||||
|
let firstAtOrBeforeXValue = (xValue, t: T.t) => {
|
||||||
|
let zipped = T.zip(t);
|
||||||
|
let firstIndex =
|
||||||
|
zipped |> Belt.Array.getIndexBy(_, ((x, _)) => x > xValue);
|
||||||
|
let previousIndex =
|
||||||
|
switch (firstIndex) {
|
||||||
|
| None => Some(Array.length(zipped) - 1)
|
||||||
|
| Some(0) => None
|
||||||
|
| Some(n) => Some(n - 1)
|
||||||
|
};
|
||||||
|
previousIndex |> Belt.Option.flatMap(_, Belt.Array.get(zipped));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module YtoX = {
|
||||||
|
let linear = (y: float, t: T.t): float => {
|
||||||
|
let firstHigherIndex =
|
||||||
|
E.A.Sorted.binarySearchFirstElementGreaterIndex(T.ys(t), y);
|
||||||
|
let foundX =
|
||||||
|
switch (firstHigherIndex) {
|
||||||
|
| `overMax => T.maxX(t)
|
||||||
|
| `underMin => T.minX(t)
|
||||||
|
| `firstHigher(firstHigherIndex) =>
|
||||||
|
let lowerOrEqualIndex =
|
||||||
|
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
|
||||||
|
let (_xs, _ys) = (T.xs(t), T.ys(t));
|
||||||
|
let needsInterpolation = _ys[lowerOrEqualIndex] != y;
|
||||||
|
if (needsInterpolation) {
|
||||||
|
interpolate(
|
||||||
|
_ys[lowerOrEqualIndex],
|
||||||
|
_ys[firstHigherIndex],
|
||||||
|
_xs[lowerOrEqualIndex],
|
||||||
|
_xs[firstHigherIndex],
|
||||||
|
y,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_xs[lowerOrEqualIndex];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
foundX;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module XtoY = {
|
||||||
|
let stepwiseIncremental = (f, t: T.t) =>
|
||||||
|
Pairs.firstAtOrBeforeXValue(f, t) |> E.O.fmap(Pairs.y);
|
||||||
|
|
||||||
|
let stepwiseIfAtX = (f: float, t: T.t) => {
|
||||||
|
Pairs.getBy(t, ((x: float, _)) => {x == f}) |> E.O.fmap(Pairs.y);
|
||||||
|
};
|
||||||
|
|
||||||
|
let linear = (x: float, t: T.t): float => {
|
||||||
|
let firstHigherIndex =
|
||||||
|
E.A.Sorted.binarySearchFirstElementGreaterIndex(T.xs(t), x);
|
||||||
|
let n =
|
||||||
|
switch (firstHigherIndex) {
|
||||||
|
| `overMax => T.lastY(t)
|
||||||
|
| `underMin => T.firstY(t)
|
||||||
|
| `firstHigher(firstHigherIndex) =>
|
||||||
|
let lowerOrEqualIndex =
|
||||||
|
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
|
||||||
|
let (_xs, _ys) = (T.xs(t), T.ys(t));
|
||||||
|
let needsInterpolation = _xs[lowerOrEqualIndex] != x;
|
||||||
|
if (needsInterpolation) {
|
||||||
|
interpolate(
|
||||||
|
_xs[lowerOrEqualIndex],
|
||||||
|
_xs[firstHigherIndex],
|
||||||
|
_ys[lowerOrEqualIndex],
|
||||||
|
_ys[firstHigherIndex],
|
||||||
|
x,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_ys[lowerOrEqualIndex];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
n;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Returns a between-points-interpolating function that can be used with PointwiseCombination.combine.
|
||||||
|
Interpolation can either be stepwise (using the value on the left) or linear. Extrapolation can be `UseZero or `UseOutermostPoints. */
|
||||||
|
let continuousInterpolator = (interpolation: DistTypes.interpolationStrategy, extrapolation: DistTypes.extrapolationStrategy): interpolator => {
|
||||||
|
switch (interpolation, extrapolation) {
|
||||||
|
| (`Linear, `UseZero) => (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
0.0
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let x1 = t.xs[leftIndex];
|
||||||
|
let x2 = t.xs[leftIndex + 1];
|
||||||
|
let y1 = t.ys[leftIndex];
|
||||||
|
let y2 = t.ys[leftIndex + 1];
|
||||||
|
let fraction = (x -. x1) /. (x2 -. x1);
|
||||||
|
y1 *. (1. -. fraction) +. y2 *. fraction;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| (`Linear, `UseOutermostPoints) => (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
t.ys[0];
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
t.ys[T.length(t) - 1]
|
||||||
|
} else {
|
||||||
|
let x1 = t.xs[leftIndex];
|
||||||
|
let x2 = t.xs[leftIndex + 1];
|
||||||
|
let y1 = t.ys[leftIndex];
|
||||||
|
let y2 = t.ys[leftIndex + 1];
|
||||||
|
let fraction = (x -. x1) /. (x2 -. x1);
|
||||||
|
y1 *. (1. -. fraction) +. y2 *. fraction;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| (`Stepwise, `UseZero) => (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
0.0
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
t.ys[leftIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| (`Stepwise, `UseOutermostPoints) => (t: T.t, leftIndex: int, x: float) => {
|
||||||
|
if (leftIndex < 0) {
|
||||||
|
t.ys[0];
|
||||||
|
} else if (leftIndex >= T.length(t) - 1) {
|
||||||
|
t.ys[T.length(t) - 1]
|
||||||
|
} else {
|
||||||
|
t.ys[leftIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Returns a between-points-interpolating function that can be used with PointwiseCombination.combine.
|
||||||
|
For discrete distributions, the probability density between points is zero, so we just return zero here. */
|
||||||
|
let discreteInterpolator: interpolator = (t: T.t, leftIndex: int, x: float) => 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
module XsConversion = {
|
||||||
|
let _replaceWithXs = (newXs: array(float), t: T.t): T.t => {
|
||||||
|
let newYs = Belt.Array.map(newXs, XtoY.linear(_, t));
|
||||||
|
{xs: newXs, ys: newYs};
|
||||||
|
};
|
||||||
|
|
||||||
|
let equallyDivideXByMass = (newLength: int, integral: T.t) =>
|
||||||
|
E.A.Floats.range(0.0, 1.0, newLength)
|
||||||
|
|> E.A.fmap(YtoX.linear(_, integral));
|
||||||
|
|
||||||
|
let proportionEquallyOverX = (newLength: int, t: T.t): T.t => {
|
||||||
|
T.equallyDividedXs(t, newLength) |> _replaceWithXs(_, t);
|
||||||
|
};
|
||||||
|
|
||||||
|
let proportionByProbabilityMass =
|
||||||
|
(newLength: int, integral: T.t, t: T.t): T.t => {
|
||||||
|
integral
|
||||||
|
|> equallyDivideXByMass(newLength) // creates a new set of xs at evenly spaced percentiles
|
||||||
|
|> _replaceWithXs(_, t); // linearly interpolates new ys for the new xs
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Zipped = {
|
||||||
|
type zipped = array((float, float));
|
||||||
|
let compareYs = ((_, y1), (_, y2)) => y1 > y2 ? 1 : 0;
|
||||||
|
let compareXs = ((x1, _), (x2, _)) => x1 > x2 ? 1 : 0;
|
||||||
|
let sortByY = (t: zipped) => t |> E.A.stableSortBy(_, compareYs);
|
||||||
|
let sortByX = (t: zipped) => t |> E.A.stableSortBy(_, compareXs);
|
||||||
|
let filterByX = (testFn: (float => bool), t: zipped) => t |> E.A.filter(((x, _)) => testFn(x));
|
||||||
|
};
|
||||||
|
|
||||||
|
module PointwiseCombination = {
|
||||||
|
|
||||||
|
// t1Interpolator and t2Interpolator are functions from XYShape.XtoY, e.g. linearBetweenPointsExtrapolateFlat.
|
||||||
|
let combine = [%raw {| // : (float => float => float, T.t, T.t, bool) => T.t
|
||||||
|
// This function combines two xyShapes by looping through both of them simultaneously.
|
||||||
|
// It always moves on to the next smallest x, whether that's in the first or second input's xs,
|
||||||
|
// and interpolates the value on the other side, thus accumulating xs and ys.
|
||||||
|
// This is written in raw JS because this can still be a bottleneck, and using refs for the i and j indices is quite painful.
|
||||||
|
|
||||||
|
function(fn, interpolator, t1, t2) {
|
||||||
|
let t1n = t1.xs.length;
|
||||||
|
let t2n = t2.xs.length;
|
||||||
|
let outX = [];
|
||||||
|
let outY = [];
|
||||||
|
let i = -1;
|
||||||
|
let j = -1;
|
||||||
|
|
||||||
|
while (i <= t1n - 1 && j <= t2n - 1) {
|
||||||
|
let x, ya, yb;
|
||||||
|
if (j == t2n - 1 && i < t1n - 1 ||
|
||||||
|
t1.xs[i+1] < t2.xs[j+1]) { // if a has to catch up to b, or if b is already done
|
||||||
|
i++;
|
||||||
|
|
||||||
|
x = t1.xs[i];
|
||||||
|
ya = t1.ys[i];
|
||||||
|
|
||||||
|
yb = interpolator(t2, j, x);
|
||||||
|
} else if (i == t1n - 1 && j < t2n - 1 ||
|
||||||
|
t1.xs[i+1] > t2.xs[j+1]) { // if b has to catch up to a, or if a is already done
|
||||||
|
j++;
|
||||||
|
|
||||||
|
x = t2.xs[j];
|
||||||
|
yb = t2.ys[j];
|
||||||
|
|
||||||
|
ya = interpolator(t1, i, x);
|
||||||
|
} else if (i < t1n - 1 && j < t2n && t1.xs[i+1] === t2.xs[j+1]) { // if they happen to be equal, move both ahead
|
||||||
|
i++;
|
||||||
|
j++;
|
||||||
|
x = t1.xs[i];
|
||||||
|
ya = t1.ys[i];
|
||||||
|
yb = t2.ys[j];
|
||||||
|
} else if (i === t1n - 1 && j === t2n - 1) {
|
||||||
|
// finished!
|
||||||
|
i = t1n;
|
||||||
|
j = t2n;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
console.log("Error!", i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
outX.push(x);
|
||||||
|
outY.push(fn(ya, yb));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {xs: outX, ys: outY};
|
||||||
|
}
|
||||||
|
|}];
|
||||||
|
|
||||||
|
let combineEvenXs =
|
||||||
|
(
|
||||||
|
~fn,
|
||||||
|
~xToYSelection,
|
||||||
|
sampleCount,
|
||||||
|
t1: T.t,
|
||||||
|
t2: T.t,
|
||||||
|
) => {
|
||||||
|
|
||||||
|
switch ((E.A.length(t1.xs), E.A.length(t2.xs))) {
|
||||||
|
| (0, 0) => T.empty
|
||||||
|
| (0, _) => t2
|
||||||
|
| (_, 0) => t1
|
||||||
|
| (_, _) => {
|
||||||
|
let allXs = Ts.equallyDividedXs([|t1, t2|], sampleCount);
|
||||||
|
|
||||||
|
let allYs = allXs |> E.A.fmap(x => fn(xToYSelection(x, t1), xToYSelection(x, t2)));
|
||||||
|
|
||||||
|
T.fromArrays(allXs, allYs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: I'd bet this is pretty slow. Maybe it would be faster to intersperse Xs and Ys separately.
|
||||||
|
let intersperse = (t1: T.t, t2: T.t) => {
|
||||||
|
E.A.intersperse(T.zip(t1), T.zip(t2)) |> T.fromZippedArray;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// I'm really not sure this part is actually what we want at this point.
|
||||||
|
module Range = {
|
||||||
|
// ((lastX, lastY), (nextX, nextY))
|
||||||
|
type zippedRange = ((float, float), (float, float));
|
||||||
|
|
||||||
|
let toT = T.fromZippedArray;
|
||||||
|
let nextX = ((_, (nextX, _)): zippedRange) => nextX;
|
||||||
|
|
||||||
|
let rangePointAssumingSteps = (((_, lastY), (nextX, _)): zippedRange) => (
|
||||||
|
nextX,
|
||||||
|
lastY,
|
||||||
|
);
|
||||||
|
|
||||||
|
let rangeAreaAssumingTriangles =
|
||||||
|
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
|
||||||
|
(nextX -. lastX) *. (lastY +. nextY) /. 2.;
|
||||||
|
|
||||||
|
//Todo: figure out how to without making new array.
|
||||||
|
let rangeAreaAssumingTrapezoids =
|
||||||
|
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
|
||||||
|
(nextX -. lastX)
|
||||||
|
*. (Js.Math.min_float(lastY, nextY) +. (lastY +. nextY) /. 2.);
|
||||||
|
|
||||||
|
let delta_y_over_delta_x =
|
||||||
|
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
|
||||||
|
(nextY -. lastY) /. (nextX -. lastX);
|
||||||
|
|
||||||
|
let mapYsBasedOnRanges = (fn, t) =>
|
||||||
|
Belt.Array.zip(t.xs, t.ys)
|
||||||
|
|> E.A.toRanges
|
||||||
|
|> E.R.toOption
|
||||||
|
|> E.O.fmap(r => r |> Belt.Array.map(_, r => (nextX(r), fn(r))));
|
||||||
|
|
||||||
|
// This code is messy, in part because I'm trying to make things easy on garbage collection here.
|
||||||
|
// It's using triangles instead of trapezoids right now.
|
||||||
|
let integrateWithTriangles = ({xs, ys}) => {
|
||||||
|
let length = E.A.length(xs);
|
||||||
|
let cumulativeY = Belt.Array.make(length, 0.0);
|
||||||
|
for (x in 0 to E.A.length(xs) - 2) {
|
||||||
|
let _ =
|
||||||
|
Belt.Array.set(
|
||||||
|
cumulativeY,
|
||||||
|
x + 1,
|
||||||
|
(xs[x + 1] -. xs[x]) // dx
|
||||||
|
*. ((ys[x] +. ys[x + 1]) /. 2.) // (1/2) * (avgY)
|
||||||
|
+. cumulativeY[x],
|
||||||
|
);
|
||||||
|
();
|
||||||
|
};
|
||||||
|
Some({xs, ys: cumulativeY});
|
||||||
|
};
|
||||||
|
|
||||||
|
let derivative = mapYsBasedOnRanges(delta_y_over_delta_x);
|
||||||
|
|
||||||
|
let stepwiseToLinear = ({xs, ys}: T.t): T.t => {
|
||||||
|
// adds points at the bottom of each step.
|
||||||
|
let length = E.A.length(xs);
|
||||||
|
let newXs: array(float) = Belt.Array.makeUninitializedUnsafe(2 * length);
|
||||||
|
let newYs: array(float) = Belt.Array.makeUninitializedUnsafe(2 * length);
|
||||||
|
|
||||||
|
Belt.Array.set(newXs, 0, xs[0] -. epsilon_float) |> ignore;
|
||||||
|
Belt.Array.set(newYs, 0, 0.) |> ignore;
|
||||||
|
Belt.Array.set(newXs, 1, xs[0]) |> ignore;
|
||||||
|
Belt.Array.set(newYs, 1, ys[0]) |> ignore;
|
||||||
|
|
||||||
|
for (i in 1 to E.A.length(xs) - 1) {
|
||||||
|
Belt.Array.set(newXs, i * 2, xs[i] -. epsilon_float) |> ignore;
|
||||||
|
Belt.Array.set(newYs, i * 2, ys[i-1]) |> ignore;
|
||||||
|
Belt.Array.set(newXs, i * 2 + 1, xs[i]) |> ignore;
|
||||||
|
Belt.Array.set(newYs, i * 2 + 1, ys[i]) |> ignore;
|
||||||
|
();
|
||||||
|
};
|
||||||
|
|
||||||
|
{xs: newXs, ys: newYs};
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: I think this isn't needed by any functions anymore.
|
||||||
|
let stepsToContinuous = t => {
|
||||||
|
// TODO: It would be nicer if this the diff didn't change the first element, and also maybe if there were a more elegant way of doing this.
|
||||||
|
let diff = T.xTotalRange(t) |> (r => r *. 0.00001);
|
||||||
|
let items =
|
||||||
|
switch (E.A.toRanges(Belt.Array.zip(t.xs, t.ys))) {
|
||||||
|
| Ok(items) =>
|
||||||
|
Some(
|
||||||
|
items
|
||||||
|
|> Belt.Array.map(_, rangePointAssumingSteps)
|
||||||
|
|> T.fromZippedArray
|
||||||
|
|> PointwiseCombination.intersperse(t |> T.mapX(e => e +. diff)),
|
||||||
|
)
|
||||||
|
| _ => Some(t)
|
||||||
|
};
|
||||||
|
let first = items |> E.O.fmap(T.zip) |> E.O.bind(_, E.A.get(_, 0));
|
||||||
|
switch (items, first) {
|
||||||
|
| (Some(items), Some((0.0, _))) => Some(items)
|
||||||
|
| (Some(items), Some((firstX, _))) =>
|
||||||
|
let all = E.A.append([|(firstX, 0.0)|], items |> T.zip);
|
||||||
|
all |> T.fromZippedArray |> E.O.some;
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let pointLogScore = (prediction, answer) =>
|
||||||
|
switch (answer) {
|
||||||
|
| 0. => 0.0
|
||||||
|
| answer => answer *. Js.Math.log2(Js.Math.abs_float(prediction /. answer))
|
||||||
|
};
|
||||||
|
|
||||||
|
let logScorePoint = (sampleCount, t1, t2) =>
|
||||||
|
PointwiseCombination.combineEvenXs(
|
||||||
|
~fn=pointLogScore,
|
||||||
|
~xToYSelection=XtoY.linear,
|
||||||
|
sampleCount,
|
||||||
|
t1,
|
||||||
|
t2,
|
||||||
|
)
|
||||||
|
|> Range.integrateWithTriangles
|
||||||
|
|> E.O.fmap(T.accumulateYs((+.)))
|
||||||
|
|> E.O.fmap(Pairs.last)
|
||||||
|
|> E.O.fmap(Pairs.y);
|
||||||
|
|
||||||
|
module Analysis = {
|
||||||
|
let integrateContinuousShape =
|
||||||
|
(
|
||||||
|
~indefiniteIntegralStepwise=(p, h1) => h1 *. p,
|
||||||
|
~indefiniteIntegralLinear=(p, a, b) => a *. p +. b *. p ** 2.0 /. 2.0,
|
||||||
|
t: DistTypes.continuousShape,
|
||||||
|
)
|
||||||
|
: float => {
|
||||||
|
let xs = t.xyShape.xs;
|
||||||
|
let ys = t.xyShape.ys;
|
||||||
|
|
||||||
|
E.A.reducei(
|
||||||
|
xs,
|
||||||
|
0.0,
|
||||||
|
(acc, _x, i) => {
|
||||||
|
let areaUnderIntegral =
|
||||||
|
// TODO Take this switch statement out of the loop body
|
||||||
|
switch (t.interpolation, i) {
|
||||||
|
| (_, 0) => 0.0
|
||||||
|
| (`Stepwise, _) =>
|
||||||
|
indefiniteIntegralStepwise(xs[i], ys[i - 1])
|
||||||
|
-. indefiniteIntegralStepwise(xs[i - 1], ys[i - 1])
|
||||||
|
| (`Linear, _) =>
|
||||||
|
let x1 = xs[i - 1];
|
||||||
|
let x2 = xs[i];
|
||||||
|
if (x1 == x2) {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let h1 = ys[i - 1];
|
||||||
|
let h2 = ys[i];
|
||||||
|
let b = (h1 -. h2) /. (x1 -. x2);
|
||||||
|
let a = h1 -. b *. x1;
|
||||||
|
indefiniteIntegralLinear(x2, a, b)
|
||||||
|
-. indefiniteIntegralLinear(x1, a, b);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
acc +. areaUnderIntegral;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let getMeanOfSquaresContinuousShape = (t: DistTypes.continuousShape) => {
|
||||||
|
let indefiniteIntegralLinear = (p, a, b) =>
|
||||||
|
a *. p ** 3.0 /. 3.0 +. b *. p ** 4.0 /. 4.0;
|
||||||
|
let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 3.0 /. 3.0;
|
||||||
|
integrateContinuousShape(
|
||||||
|
~indefiniteIntegralStepwise,
|
||||||
|
~indefiniteIntegralLinear,
|
||||||
|
t,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let getVarianceDangerously =
|
||||||
|
(t: 't, mean: 't => float, getMeanOfSquares: 't => float): float => {
|
||||||
|
let meanSquared = mean(t) ** 2.0;
|
||||||
|
let meanOfSquares = getMeanOfSquares(t);
|
||||||
|
meanOfSquares -. meanSquared;
|
||||||
|
};
|
||||||
|
|
||||||
|
let squareXYShape = T.mapX(x => x ** 2.0)
|
||||||
|
};
|
|
@ -206,6 +206,7 @@ module Truncate = {
|
||||||
|
|
||||||
module Normalize = {
|
module Normalize = {
|
||||||
let rec operationToLeaf = (evaluationParams, t: node): result(node, string) => {
|
let rec operationToLeaf = (evaluationParams, t: node): result(node, string) => {
|
||||||
|
Js.log2("normalize", t);
|
||||||
switch (t) {
|
switch (t) {
|
||||||
| `RenderedDist(s) => Ok(`RenderedDist(Shape.T.normalize(s)))
|
| `RenderedDist(s) => Ok(`RenderedDist(Shape.T.normalize(s)))
|
||||||
| `SymbolicDist(_) => Ok(t)
|
| `SymbolicDist(_) => Ok(t)
|
|
@ -344,6 +344,7 @@ let fromString2 = str => {
|
||||||
});
|
});
|
||||||
|
|
||||||
let value = E.R.bind(mathJsParse, MathAdtToDistDst.run);
|
let value = E.R.bind(mathJsParse, MathAdtToDistDst.run);
|
||||||
|
Js.log2(mathJsParse, value);
|
||||||
value;
|
value;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
const math = require("mathjs");
|
||||||
|
|
||||||
|
function parseMath(f) {
|
||||||
|
return JSON.parse(JSON.stringify(math.parse(f)))
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parseMath,
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
const pdfast = require('pdfast');
|
||||||
|
const _ = require("lodash");
|
||||||
|
|
||||||
|
const samplesToContinuousPdf = (
|
||||||
|
samples,
|
||||||
|
size,
|
||||||
|
width,
|
||||||
|
min = false,
|
||||||
|
max = false,
|
||||||
|
) => {
|
||||||
|
let _samples = _.filter(samples, _.isFinite);
|
||||||
|
if (_.isFinite(min)) { _samples = _.filter(_samples, r => r > min) };
|
||||||
|
if (_.isFinite(max)) { _samples = _.filter(_samples, r => r < max) };
|
||||||
|
let pdf = pdfast.create(_samples, { size, width });
|
||||||
|
return {xs: pdf.map(r => r.x), ys: pdf.map(r => r.y)};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
samplesToContinuousPdf,
|
||||||
|
};
|
460
packages/playground/src/distPlus/utility/E.res
Normal file
460
packages/playground/src/distPlus/utility/E.res
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
open Rationale.Function.Infix
|
||||||
|
|
||||||
|
module FloatFloatMap = {
|
||||||
|
module Id = Belt.Id.MakeComparable({
|
||||||
|
type t = float
|
||||||
|
let cmp: (float, float) => int = Pervasives.compare
|
||||||
|
})
|
||||||
|
|
||||||
|
type t = Belt.MutableMap.t<Id.t, float, Id.identity>
|
||||||
|
|
||||||
|
let fromArray = (ar: array<(float, float)>) => Belt.MutableMap.fromArray(ar, ~id=module(Id))
|
||||||
|
let toArray = (t: t) => Belt.MutableMap.toArray(t)
|
||||||
|
let empty = () => Belt.MutableMap.make(~id=module(Id))
|
||||||
|
let increment = (el, t: t) =>
|
||||||
|
Belt.MutableMap.update(t, el, x =>
|
||||||
|
switch x {
|
||||||
|
| Some(n) => Some(n +. 1.0)
|
||||||
|
| None => Some(1.0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let get = (el, t: t) => Belt.MutableMap.get(t, el)
|
||||||
|
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
|
||||||
|
let toA = a => [a]
|
||||||
|
let id = e => e
|
||||||
|
}
|
||||||
|
|
||||||
|
module O = {
|
||||||
|
let dimap = (sFn, rFn, e) =>
|
||||||
|
switch e {
|
||||||
|
| Some(r) => sFn(r)
|
||||||
|
| None => rFn()
|
||||||
|
}
|
||||||
|
()
|
||||||
|
let fmap = Rationale.Option.fmap
|
||||||
|
let bind = Rationale.Option.bind
|
||||||
|
let default = Rationale.Option.default
|
||||||
|
let isSome = Rationale.Option.isSome
|
||||||
|
let isNone = Rationale.Option.isNone
|
||||||
|
let toExn = Rationale.Option.toExn
|
||||||
|
let some = Rationale.Option.some
|
||||||
|
let firstSome = Rationale.Option.firstSome
|
||||||
|
let toExt = Rationale.Option.toExn
|
||||||
|
let flatApply = (fn, b) => Rationale.Option.apply(fn, Some(b)) |> Rationale.Option.flatten
|
||||||
|
|
||||||
|
let toBool = opt =>
|
||||||
|
switch opt {
|
||||||
|
| Some(_) => true
|
||||||
|
| _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
let ffmap = (fn, r) =>
|
||||||
|
switch r {
|
||||||
|
| Some(sm) => fn(sm)
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
|
||||||
|
let toString = opt =>
|
||||||
|
switch opt {
|
||||||
|
| Some(s) => s
|
||||||
|
| _ => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let toResult = (error, e) =>
|
||||||
|
switch e {
|
||||||
|
| Some(r) => Belt.Result.Ok(r)
|
||||||
|
| None => Error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let compare = (compare, f1: option<float>, f2: option<float>) =>
|
||||||
|
switch (f1, f2) {
|
||||||
|
| (Some(f1), Some(f2)) => Some(compare(f1, f2) ? f1 : f2)
|
||||||
|
| (Some(f1), None) => Some(f1)
|
||||||
|
| (None, Some(f2)) => Some(f2)
|
||||||
|
| (None, None) => None
|
||||||
|
}
|
||||||
|
|
||||||
|
let min = compare(\"<")
|
||||||
|
let max = compare(\">")
|
||||||
|
module React = {
|
||||||
|
let defaultNull = default(React.null)
|
||||||
|
let fmapOrNull = fn => \"||>"(fmap(fn), default(React.null))
|
||||||
|
let flatten = default(React.null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
module F = {
|
||||||
|
let apply = (a, e) => a |> e
|
||||||
|
|
||||||
|
let flatten2Callbacks = (fn1, fn2, fnlast) =>
|
||||||
|
fn1(response1 => fn2(response2 => fnlast(response1, response2)))
|
||||||
|
|
||||||
|
let flatten3Callbacks = (fn1, fn2, fn3, fnlast) =>
|
||||||
|
fn1(response1 => fn2(response2 => fn3(response3 => fnlast(response1, response2, response3))))
|
||||||
|
|
||||||
|
let flatten4Callbacks = (fn1, fn2, fn3, fn4, fnlast) =>
|
||||||
|
fn1(response1 =>
|
||||||
|
fn2(response2 =>
|
||||||
|
fn3(response3 => fn4(response4 => fnlast(response1, response2, response3, response4)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module Bool = {
|
||||||
|
type t = bool
|
||||||
|
let toString = (t: t) => t ? "TRUE" : "FALSE"
|
||||||
|
let fromString = str => str == "TRUE" ? true : false
|
||||||
|
|
||||||
|
module O = {
|
||||||
|
let toBool = opt =>
|
||||||
|
switch opt {
|
||||||
|
| Some(true) => true
|
||||||
|
| _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Float = {
|
||||||
|
let with2DigitsPrecision = Js.Float.toPrecisionWithPrecision(_, ~digits=2)
|
||||||
|
let with3DigitsPrecision = Js.Float.toPrecisionWithPrecision(_, ~digits=3)
|
||||||
|
let toFixed = Js.Float.toFixed
|
||||||
|
let toString = Js.Float.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
module I = {
|
||||||
|
let increment = n => n + 1
|
||||||
|
let decrement = n => n - 1
|
||||||
|
let toString = Js.Int.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
/* R for Result */
|
||||||
|
module R = {
|
||||||
|
let result = Rationale.Result.result
|
||||||
|
let id = e => e |> result(U.id, U.id)
|
||||||
|
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)
|
||||||
|
| (_, Error(e)) => Error(e)
|
||||||
|
| (Ok(a), Ok(b)) => Ok((a, b))
|
||||||
|
}
|
||||||
|
let toOption = (e: Belt.Result.t<'a, 'b>) =>
|
||||||
|
switch e {
|
||||||
|
| 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> =>
|
||||||
|
try Some(fn(s)) catch {
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
|
||||||
|
module S = {
|
||||||
|
let safe_float = float_of_string->safe_fn_of_string
|
||||||
|
let safe_int = int_of_string->safe_fn_of_string
|
||||||
|
let default = (defaultStr, str) => str == "" ? defaultStr : str
|
||||||
|
}
|
||||||
|
|
||||||
|
module J = {
|
||||||
|
let toString = \"||>"(Js.Json.decodeString, O.default(""))
|
||||||
|
let fromString = Js.Json.string
|
||||||
|
let fromNumber = Js.Json.number
|
||||||
|
|
||||||
|
module O = {
|
||||||
|
let fromString = (str: string) =>
|
||||||
|
switch str {
|
||||||
|
| "" => None
|
||||||
|
| _ => Some(Js.Json.string(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
let toString = (str: option<'a>) =>
|
||||||
|
switch str {
|
||||||
|
| Some(str) => Some(str |> \"||>"(Js.Json.decodeString, O.default("")))
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module M = {
|
||||||
|
let format = MomentRe.Moment.format
|
||||||
|
let format_standard = "MMM DD, YYYY HH:mm"
|
||||||
|
let format_simple = "L"
|
||||||
|
/* TODO: Figure out better name */
|
||||||
|
let goFormat_simple = MomentRe.Moment.format(format_simple)
|
||||||
|
let goFormat_standard = MomentRe.Moment.format(format_standard)
|
||||||
|
let toUtc = MomentRe.momentUtc
|
||||||
|
let toJSON = MomentRe.Moment.toJSON
|
||||||
|
let momentDefaultFormat = MomentRe.momentDefaultFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
module JsDate = {
|
||||||
|
let fromString = Js.Date.fromString
|
||||||
|
let now = Js.Date.now
|
||||||
|
let make = Js.Date.make
|
||||||
|
let valueOf = Js.Date.valueOf
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List */
|
||||||
|
module L = {
|
||||||
|
let fmap = List.map
|
||||||
|
let get = Belt.List.get
|
||||||
|
let toArray = Array.of_list
|
||||||
|
let fmapi = List.mapi
|
||||||
|
let concat = List.concat
|
||||||
|
let drop = Rationale.RList.drop
|
||||||
|
let remove = Rationale.RList.remove
|
||||||
|
let find = List.find
|
||||||
|
let filter = List.filter
|
||||||
|
let for_all = List.for_all
|
||||||
|
let exists = List.exists
|
||||||
|
let sort = List.sort
|
||||||
|
let length = List.length
|
||||||
|
let filter_opt = Rationale.RList.filter_opt
|
||||||
|
let uniqBy = Rationale.RList.uniqBy
|
||||||
|
let join = Rationale.RList.join
|
||||||
|
let head = Rationale.RList.head
|
||||||
|
let uniq = Rationale.RList.uniq
|
||||||
|
let flatten = List.flatten
|
||||||
|
let last = Rationale.RList.last
|
||||||
|
let append = List.append
|
||||||
|
let getBy = Belt.List.getBy
|
||||||
|
let dropLast = Rationale.RList.dropLast
|
||||||
|
let contains = Rationale.RList.contains
|
||||||
|
let without = Rationale.RList.without
|
||||||
|
let update = Rationale.RList.update
|
||||||
|
let iter = List.iter
|
||||||
|
let findIndex = Rationale.RList.findIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A for Array */
|
||||||
|
module A = {
|
||||||
|
let fmap = Array.map
|
||||||
|
let fmapi = Array.mapi
|
||||||
|
let to_list = Array.to_list
|
||||||
|
let of_list = Array.of_list
|
||||||
|
let length = Array.length
|
||||||
|
let append = Array.append
|
||||||
|
// let empty = [||];
|
||||||
|
let unsafe_get = Array.unsafe_get
|
||||||
|
let get = Belt.Array.get
|
||||||
|
let getBy = Belt.Array.getBy
|
||||||
|
let last = a => get(a, length(a) - 1)
|
||||||
|
let first = get(_, 0)
|
||||||
|
let hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome
|
||||||
|
let fold_left = Array.fold_left
|
||||||
|
let fold_right = Array.fold_right
|
||||||
|
let concatMany = Belt.Array.concatMany
|
||||||
|
let keepMap = Belt.Array.keepMap
|
||||||
|
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))
|
||||||
|
let max = a => get(a, 0) |> O.fmap(first => Belt.Array.reduce(a, first, (i, j) => i > j ? i : j))
|
||||||
|
let stableSortBy = Belt.SortArray.stableSortBy
|
||||||
|
let toRanges = (a: array<'a>) =>
|
||||||
|
switch a |> Belt.Array.length {
|
||||||
|
| 0
|
||||||
|
| 1 =>
|
||||||
|
Belt.Result.Error("Must be at least 2 elements")
|
||||||
|
| n =>
|
||||||
|
Belt.Array.makeBy(n - 1, r => r)
|
||||||
|
|> Belt.Array.map(_, index => (
|
||||||
|
Belt.Array.getUnsafe(a, index),
|
||||||
|
Belt.Array.getUnsafe(a, index + 1),
|
||||||
|
))
|
||||||
|
|> 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? */
|
||||||
|
let uniq = r => asList(L.uniq, r)
|
||||||
|
|
||||||
|
//intersperse([1,2,3], [10,11,12]) => [1,10,2,11,3,12]
|
||||||
|
let intersperse = (a: array<'a>, b: array<'a>) => {
|
||||||
|
let items: ref<array<'a>> = ref([])
|
||||||
|
|
||||||
|
Belt.Array.forEachWithIndex(a, (i, item) =>
|
||||||
|
switch Belt.Array.get(b, i) {
|
||||||
|
| Some(r) => items := append(items.contents, [item, r])
|
||||||
|
| None => items := append(items.contents, [item])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
items.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is like map, but
|
||||||
|
//accumulate((a,b) => a + b, [1,2,3]) => [1, 3, 5]
|
||||||
|
let accumulate = (fn: ('a, 'a) => 'a, items: array<'a>) => {
|
||||||
|
let length = items |> length
|
||||||
|
let empty = Belt.Array.make(length, items |> unsafe_get(_, 0))
|
||||||
|
Belt.Array.forEachWithIndex(items, (index, element) => {
|
||||||
|
let item = switch index {
|
||||||
|
| 0 => element
|
||||||
|
| index => fn(element, unsafe_get(empty, index - 1))
|
||||||
|
}
|
||||||
|
let _ = Belt.Array.set(empty, index, item)
|
||||||
|
})
|
||||||
|
empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo: Is -1 still the indicator that this is false (as is true with
|
||||||
|
// @todo: js findIndex)? Wasn't sure.
|
||||||
|
let findIndex = (e, i) =>
|
||||||
|
Js.Array.findIndex(e, i) |> (
|
||||||
|
r =>
|
||||||
|
switch r {
|
||||||
|
| -1 => None
|
||||||
|
| r => Some(r)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let filter = (o, e) => Js.Array.filter(o, e)
|
||||||
|
|
||||||
|
module O = {
|
||||||
|
let concatSomes = (optionals: array<option<'a>>): array<'a> =>
|
||||||
|
optionals
|
||||||
|
|> Js.Array.filter(Rationale.Option.isSome)
|
||||||
|
|> Js.Array.map(Rationale.Option.toExn("Warning: This should not have happened"))
|
||||||
|
let defaultEmpty = (o: option<array<'a>>): array<'a> =>
|
||||||
|
switch o {
|
||||||
|
| Some(o) => o
|
||||||
|
| None => []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module R = {
|
||||||
|
let firstErrorOrOpen = (results: array<Belt.Result.t<'a, 'b>>): Belt.Result.t<
|
||||||
|
array<'a>,
|
||||||
|
'b,
|
||||||
|
> => {
|
||||||
|
let bringErrorUp = switch results |> Belt.Array.getBy(_, Belt.Result.isError) {
|
||||||
|
| Some(Belt.Result.Error(err)) => Belt.Result.Error(err)
|
||||||
|
| Some(Belt.Result.Ok(_)) => Belt.Result.Ok(results)
|
||||||
|
| None => Belt.Result.Ok(results)
|
||||||
|
}
|
||||||
|
let forceOpen = (r: array<Belt.Result.t<'a, 'b>>): array<'a> =>
|
||||||
|
r |> Belt.Array.map(_, r => Belt.Result.getExn(r))
|
||||||
|
bringErrorUp |> Belt.Result.map(_, forceOpen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Sorted = {
|
||||||
|
let min = first
|
||||||
|
let max = last
|
||||||
|
let range = (~min=min, ~max=max, a) =>
|
||||||
|
switch (min(a), max(a)) {
|
||||||
|
| (Some(min), Some(max)) => Some(max -. min)
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
let binarySearchFirstElementGreaterIndex = (ar: array<'a>, el: 'a) => {
|
||||||
|
let el = Belt.SortArray.binarySearchBy(ar, el, compare)
|
||||||
|
let el = el < 0 ? el * -1 - 1 : el
|
||||||
|
switch el {
|
||||||
|
| e if e >= length(ar) => #overMax
|
||||||
|
| e if e == 0 => #underMin
|
||||||
|
| e => #firstHigher(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let concat = (t1: array<'a>, t2: array<'a>) => {
|
||||||
|
let ts = Belt.Array.concat(t1, t2)
|
||||||
|
ts |> Array.fast_sort(compare)
|
||||||
|
ts
|
||||||
|
}
|
||||||
|
|
||||||
|
let concatMany = (t1: array<array<'a>>) => {
|
||||||
|
let ts = Belt.Array.concatMany(t1)
|
||||||
|
ts |> Array.fast_sort(compare)
|
||||||
|
ts
|
||||||
|
}
|
||||||
|
|
||||||
|
module Floats = {
|
||||||
|
let makeIncrementalUp = (a, b) =>
|
||||||
|
Array.make(b - a + 1, a) |> Array.mapi((i, c) => c + i) |> Belt.Array.map(_, float_of_int)
|
||||||
|
|
||||||
|
let makeIncrementalDown = (a, b) =>
|
||||||
|
Array.make(a - b + 1, a) |> Array.mapi((i, c) => c - i) |> Belt.Array.map(_, float_of_int)
|
||||||
|
|
||||||
|
let split = (sortedArray: array<float>) => {
|
||||||
|
let continuous = []
|
||||||
|
let discrete = FloatFloatMap.empty()
|
||||||
|
Belt.Array.forEachWithIndex(sortedArray, (index, element) => {
|
||||||
|
let maxIndex = (sortedArray |> Array.length) - 1
|
||||||
|
let possiblySimilarElements = switch index {
|
||||||
|
| 0 => [index + 1]
|
||||||
|
| n if n == maxIndex => [index - 1]
|
||||||
|
| _ => [index - 1, index + 1]
|
||||||
|
} |> Belt.Array.map(_, r => sortedArray[r])
|
||||||
|
let hasSimilarElement = Belt.Array.some(possiblySimilarElements, r => r == element)
|
||||||
|
hasSimilarElement
|
||||||
|
? FloatFloatMap.increment(element, discrete)
|
||||||
|
: {
|
||||||
|
let _ = Js.Array.push(element, continuous)
|
||||||
|
}
|
||||||
|
()
|
||||||
|
})
|
||||||
|
|
||||||
|
(continuous, discrete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Floats = {
|
||||||
|
let sum = Belt.Array.reduce(_, 0., (i, j) => i +. j)
|
||||||
|
let mean = a => sum(a) /. (Array.length(a) |> float_of_int)
|
||||||
|
let random = Js.Math.random_int
|
||||||
|
|
||||||
|
exception RangeError(string)
|
||||||
|
let range = (min: float, max: float, n: int): array<float> =>
|
||||||
|
switch n {
|
||||||
|
| 0 => []
|
||||||
|
| 1 => [min]
|
||||||
|
| 2 => [min, max]
|
||||||
|
| _ if min == max => Belt.Array.make(n, min)
|
||||||
|
| _ if n < 0 => raise(RangeError("n must be greater than 0"))
|
||||||
|
| _ if min > max => raise(RangeError("Min value is less then max value"))
|
||||||
|
| _ =>
|
||||||
|
let diff = (max -. min) /. Belt.Float.fromInt(n - 1)
|
||||||
|
Belt.Array.makeBy(n, i => min +. Belt.Float.fromInt(i) *. diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module JsArray = {
|
||||||
|
let concatSomes = (optionals: Js.Array.t<option<'a>>): Js.Array.t<'a> =>
|
||||||
|
optionals
|
||||||
|
|> Js.Array.filter(Rationale.Option.isSome)
|
||||||
|
|> Js.Array.map(Rationale.Option.toExn("Warning: This should not have happened"))
|
||||||
|
let filter = Js.Array.filter
|
||||||
|
}
|
16
packages/playground/src/index.html
Normal file
16
packages/playground/src/index.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Squiggle Language</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,900" rel="stylesheet">
|
||||||
|
<link href="./styles/index.css" rel="stylesheet">
|
||||||
|
<script src="./Index.bs.js" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app" style="height: 100%"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
19
packages/playground/src/pages/Home.res
Normal file
19
packages/playground/src/pages/Home.res
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
module Styles = {
|
||||||
|
open CssJs
|
||||||
|
let h3 = style(. [ fontSize(#em(1.5)), marginBottom(#em(1.5)) ])
|
||||||
|
let card = style(. [ marginTop(#em(2.)), marginBottom(#em(2.)) ])
|
||||||
|
}
|
||||||
|
|
||||||
|
module Table = {
|
||||||
|
module TableStyles = {
|
||||||
|
open CssJs
|
||||||
|
let row = style(. [ display(#flex), height(#em(4.)) ])
|
||||||
|
let col = (~f=1.0, ()) => style(. [ flex(#num(f)) ])
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = () => <> </>
|
||||||
|
}
|
||||||
|
|
||||||
|
@react.component
|
||||||
|
let make = () => <div> <div style=Styles.card> <Table /> </div> </div>
|
5
packages/playground/src/styles/index.css
Normal file
5
packages/playground/src/styles/index.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
@tailwind base;
|
||||||
|
|
||||||
|
@tailwind components;
|
||||||
|
|
||||||
|
@tailwind utilities;
|
67342
packages/playground/src/styles/tailwind.css
Normal file
67342
packages/playground/src/styles/tailwind.css
Normal file
File diff suppressed because it is too large
Load Diff
694
packages/playground/tailwind.js
Normal file
694
packages/playground/tailwind.js
Normal file
|
@ -0,0 +1,694 @@
|
||||||
|
module.exports = {
|
||||||
|
prefix: '',
|
||||||
|
important: false,
|
||||||
|
separator: ':',
|
||||||
|
theme: {
|
||||||
|
screens: {
|
||||||
|
sm: '640px',
|
||||||
|
md: '768px',
|
||||||
|
lg: '1024px',
|
||||||
|
xl: '1280px',
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
transparent: 'transparent',
|
||||||
|
|
||||||
|
black: '#000',
|
||||||
|
white: '#fff',
|
||||||
|
|
||||||
|
gray: {
|
||||||
|
100: '#f7fafc',
|
||||||
|
200: '#edf2f7',
|
||||||
|
300: '#e2e8f0',
|
||||||
|
400: '#cbd5e0',
|
||||||
|
500: '#a0aec0',
|
||||||
|
600: '#718096',
|
||||||
|
700: '#4a5568',
|
||||||
|
800: '#2d3748',
|
||||||
|
900: '#1a202c',
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
100: '#fff5f5',
|
||||||
|
200: '#fed7d7',
|
||||||
|
300: '#feb2b2',
|
||||||
|
400: '#fc8181',
|
||||||
|
500: '#f56565',
|
||||||
|
600: '#e53e3e',
|
||||||
|
700: '#c53030',
|
||||||
|
800: '#9b2c2c',
|
||||||
|
900: '#742a2a',
|
||||||
|
},
|
||||||
|
orange: {
|
||||||
|
100: '#fffaf0',
|
||||||
|
200: '#feebc8',
|
||||||
|
300: '#fbd38d',
|
||||||
|
400: '#f6ad55',
|
||||||
|
500: '#ed8936',
|
||||||
|
600: '#dd6b20',
|
||||||
|
700: '#c05621',
|
||||||
|
800: '#9c4221',
|
||||||
|
900: '#7b341e',
|
||||||
|
},
|
||||||
|
yellow: {
|
||||||
|
100: '#fffff0',
|
||||||
|
200: '#fefcbf',
|
||||||
|
300: '#faf089',
|
||||||
|
400: '#f6e05e',
|
||||||
|
500: '#ecc94b',
|
||||||
|
600: '#d69e2e',
|
||||||
|
700: '#b7791f',
|
||||||
|
800: '#975a16',
|
||||||
|
900: '#744210',
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
100: '#f0fff4',
|
||||||
|
200: '#c6f6d5',
|
||||||
|
300: '#9ae6b4',
|
||||||
|
400: '#68d391',
|
||||||
|
500: '#48bb78',
|
||||||
|
600: '#38a169',
|
||||||
|
700: '#2f855a',
|
||||||
|
800: '#276749',
|
||||||
|
900: '#22543d',
|
||||||
|
},
|
||||||
|
teal: {
|
||||||
|
100: '#e6fffa',
|
||||||
|
200: '#b2f5ea',
|
||||||
|
300: '#81e6d9',
|
||||||
|
400: '#4fd1c5',
|
||||||
|
500: '#38b2ac',
|
||||||
|
600: '#319795',
|
||||||
|
700: '#2c7a7b',
|
||||||
|
800: '#285e61',
|
||||||
|
900: '#234e52',
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
100: '#ebf8ff',
|
||||||
|
200: '#bee3f8',
|
||||||
|
300: '#90cdf4',
|
||||||
|
400: '#63b3ed',
|
||||||
|
500: '#4299e1',
|
||||||
|
600: '#3182ce',
|
||||||
|
700: '#2b6cb0',
|
||||||
|
800: '#2c5282',
|
||||||
|
900: '#2a4365',
|
||||||
|
},
|
||||||
|
indigo: {
|
||||||
|
100: '#ebf4ff',
|
||||||
|
200: '#c3dafe',
|
||||||
|
300: '#a3bffa',
|
||||||
|
400: '#7f9cf5',
|
||||||
|
500: '#667eea',
|
||||||
|
600: '#5a67d8',
|
||||||
|
700: '#4c51bf',
|
||||||
|
800: '#434190',
|
||||||
|
900: '#3c366b',
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
100: '#faf5ff',
|
||||||
|
200: '#e9d8fd',
|
||||||
|
300: '#d6bcfa',
|
||||||
|
400: '#b794f4',
|
||||||
|
500: '#9f7aea',
|
||||||
|
600: '#805ad5',
|
||||||
|
700: '#6b46c1',
|
||||||
|
800: '#553c9a',
|
||||||
|
900: '#44337a',
|
||||||
|
},
|
||||||
|
pink: {
|
||||||
|
100: '#fff5f7',
|
||||||
|
200: '#fed7e2',
|
||||||
|
300: '#fbb6ce',
|
||||||
|
400: '#f687b3',
|
||||||
|
500: '#ed64a6',
|
||||||
|
600: '#d53f8c',
|
||||||
|
700: '#b83280',
|
||||||
|
800: '#97266d',
|
||||||
|
900: '#702459',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
px: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'1': '0.25rem',
|
||||||
|
'2': '0.5rem',
|
||||||
|
'3': '0.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'16': '4rem',
|
||||||
|
'20': '5rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '8rem',
|
||||||
|
'40': '10rem',
|
||||||
|
'48': '12rem',
|
||||||
|
'56': '14rem',
|
||||||
|
'64': '16rem',
|
||||||
|
},
|
||||||
|
backgroundColor: theme => theme('colors'),
|
||||||
|
backgroundPosition: {
|
||||||
|
bottom: 'bottom',
|
||||||
|
center: 'center',
|
||||||
|
left: 'left',
|
||||||
|
'left-bottom': 'left bottom',
|
||||||
|
'left-top': 'left top',
|
||||||
|
right: 'right',
|
||||||
|
'right-bottom': 'right bottom',
|
||||||
|
'right-top': 'right top',
|
||||||
|
top: 'top',
|
||||||
|
},
|
||||||
|
backgroundSize: {
|
||||||
|
auto: 'auto',
|
||||||
|
cover: 'cover',
|
||||||
|
contain: 'contain',
|
||||||
|
},
|
||||||
|
borderColor: theme => ({
|
||||||
|
...theme('colors'),
|
||||||
|
default: theme('colors.gray.300', 'currentColor'),
|
||||||
|
}),
|
||||||
|
borderRadius: {
|
||||||
|
none: '0',
|
||||||
|
sm: '0.125rem',
|
||||||
|
default: '0.25rem',
|
||||||
|
md: '0.375rem',
|
||||||
|
lg: '0.5rem',
|
||||||
|
full: '9999px',
|
||||||
|
},
|
||||||
|
borderWidth: {
|
||||||
|
default: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'2': '2px',
|
||||||
|
'4': '4px',
|
||||||
|
'8': '8px',
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
xs: '0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||||
|
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||||
|
default: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
||||||
|
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||||
|
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
||||||
|
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
||||||
|
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||||
|
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
|
||||||
|
outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
|
||||||
|
none: 'none',
|
||||||
|
},
|
||||||
|
container: {},
|
||||||
|
cursor: {
|
||||||
|
auto: 'auto',
|
||||||
|
default: 'default',
|
||||||
|
pointer: 'pointer',
|
||||||
|
wait: 'wait',
|
||||||
|
text: 'text',
|
||||||
|
move: 'move',
|
||||||
|
'not-allowed': 'not-allowed',
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
current: 'currentColor',
|
||||||
|
},
|
||||||
|
flex: {
|
||||||
|
'1': '1 1 0%',
|
||||||
|
auto: '1 1 auto',
|
||||||
|
initial: '0 1 auto',
|
||||||
|
none: 'none',
|
||||||
|
},
|
||||||
|
flexGrow: {
|
||||||
|
'0': '0',
|
||||||
|
default: '1',
|
||||||
|
},
|
||||||
|
flexShrink: {
|
||||||
|
'0': '0',
|
||||||
|
default: '1',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: [
|
||||||
|
'system-ui',
|
||||||
|
'-apple-system',
|
||||||
|
'BlinkMacSystemFont',
|
||||||
|
'"Segoe UI"',
|
||||||
|
'Roboto',
|
||||||
|
'"Helvetica Neue"',
|
||||||
|
'Arial',
|
||||||
|
'"Noto Sans"',
|
||||||
|
'sans-serif',
|
||||||
|
'"Apple Color Emoji"',
|
||||||
|
'"Segoe UI Emoji"',
|
||||||
|
'"Segoe UI Symbol"',
|
||||||
|
'"Noto Color Emoji"',
|
||||||
|
],
|
||||||
|
serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
|
||||||
|
mono: ['Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'],
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
xs: '0.75rem',
|
||||||
|
sm: '0.875rem',
|
||||||
|
base: '1rem',
|
||||||
|
lg: '1.125rem',
|
||||||
|
xl: '1.25rem',
|
||||||
|
'2xl': '1.5rem',
|
||||||
|
'3xl': '1.875rem',
|
||||||
|
'4xl': '2.25rem',
|
||||||
|
'5xl': '3rem',
|
||||||
|
'6xl': '4rem',
|
||||||
|
},
|
||||||
|
fontWeight: {
|
||||||
|
hairline: '100',
|
||||||
|
thin: '200',
|
||||||
|
light: '300',
|
||||||
|
normal: '400',
|
||||||
|
medium: '500',
|
||||||
|
semibold: '600',
|
||||||
|
bold: '700',
|
||||||
|
extrabold: '800',
|
||||||
|
black: '900',
|
||||||
|
},
|
||||||
|
height: theme => ({
|
||||||
|
auto: 'auto',
|
||||||
|
...theme('spacing'),
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh',
|
||||||
|
}),
|
||||||
|
inset: {
|
||||||
|
'0': '0',
|
||||||
|
auto: 'auto',
|
||||||
|
},
|
||||||
|
letterSpacing: {
|
||||||
|
tighter: '-0.05em',
|
||||||
|
tight: '-0.025em',
|
||||||
|
normal: '0',
|
||||||
|
wide: '0.025em',
|
||||||
|
wider: '0.05em',
|
||||||
|
widest: '0.1em',
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
none: '1',
|
||||||
|
tight: '1.25',
|
||||||
|
snug: '1.375',
|
||||||
|
normal: '1.5',
|
||||||
|
relaxed: '1.625',
|
||||||
|
loose: '2',
|
||||||
|
'3': '.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'7': '1.75rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'9': '2.25rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
},
|
||||||
|
listStyleType: {
|
||||||
|
none: 'none',
|
||||||
|
disc: 'disc',
|
||||||
|
decimal: 'decimal',
|
||||||
|
},
|
||||||
|
margin: (theme, { negative }) => ({
|
||||||
|
auto: 'auto',
|
||||||
|
...theme('spacing'),
|
||||||
|
...negative(theme('spacing')),
|
||||||
|
}),
|
||||||
|
maxHeight: {
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh',
|
||||||
|
},
|
||||||
|
maxWidth: (theme, { breakpoints }) => ({
|
||||||
|
none: 'none',
|
||||||
|
xs: '20rem',
|
||||||
|
sm: '24rem',
|
||||||
|
md: '28rem',
|
||||||
|
lg: '32rem',
|
||||||
|
xl: '36rem',
|
||||||
|
'2xl': '42rem',
|
||||||
|
'3xl': '48rem',
|
||||||
|
'4xl': '56rem',
|
||||||
|
'5xl': '64rem',
|
||||||
|
'6xl': '72rem',
|
||||||
|
full: '100%',
|
||||||
|
...breakpoints(theme('screens')),
|
||||||
|
}),
|
||||||
|
minHeight: {
|
||||||
|
'0': '0',
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh',
|
||||||
|
},
|
||||||
|
minWidth: {
|
||||||
|
'0': '0',
|
||||||
|
full: '100%',
|
||||||
|
},
|
||||||
|
objectPosition: {
|
||||||
|
bottom: 'bottom',
|
||||||
|
center: 'center',
|
||||||
|
left: 'left',
|
||||||
|
'left-bottom': 'left bottom',
|
||||||
|
'left-top': 'left top',
|
||||||
|
right: 'right',
|
||||||
|
'right-bottom': 'right bottom',
|
||||||
|
'right-top': 'right top',
|
||||||
|
top: 'top',
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
'0': '0',
|
||||||
|
'25': '0.25',
|
||||||
|
'50': '0.5',
|
||||||
|
'75': '0.75',
|
||||||
|
'100': '1',
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
first: '-9999',
|
||||||
|
last: '9999',
|
||||||
|
none: '0',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
'8': '8',
|
||||||
|
'9': '9',
|
||||||
|
'10': '10',
|
||||||
|
'11': '11',
|
||||||
|
'12': '12',
|
||||||
|
},
|
||||||
|
padding: theme => theme('spacing'),
|
||||||
|
placeholderColor: theme => theme('colors'),
|
||||||
|
stroke: {
|
||||||
|
current: 'currentColor',
|
||||||
|
},
|
||||||
|
strokeWidth: {
|
||||||
|
'0': '0',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
},
|
||||||
|
textColor: theme => theme('colors'),
|
||||||
|
width: theme => ({
|
||||||
|
auto: 'auto',
|
||||||
|
...theme('spacing'),
|
||||||
|
'1/2': '50%',
|
||||||
|
'1/3': '33.333333%',
|
||||||
|
'2/3': '66.666667%',
|
||||||
|
'1/4': '25%',
|
||||||
|
'2/4': '50%',
|
||||||
|
'3/4': '75%',
|
||||||
|
'1/5': '20%',
|
||||||
|
'2/5': '40%',
|
||||||
|
'3/5': '60%',
|
||||||
|
'4/5': '80%',
|
||||||
|
'1/6': '16.666667%',
|
||||||
|
'2/6': '33.333333%',
|
||||||
|
'3/6': '50%',
|
||||||
|
'4/6': '66.666667%',
|
||||||
|
'5/6': '83.333333%',
|
||||||
|
'1/12': '8.333333%',
|
||||||
|
'2/12': '16.666667%',
|
||||||
|
'3/12': '25%',
|
||||||
|
'4/12': '33.333333%',
|
||||||
|
'5/12': '41.666667%',
|
||||||
|
'6/12': '50%',
|
||||||
|
'7/12': '58.333333%',
|
||||||
|
'8/12': '66.666667%',
|
||||||
|
'9/12': '75%',
|
||||||
|
'10/12': '83.333333%',
|
||||||
|
'11/12': '91.666667%',
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vw',
|
||||||
|
}),
|
||||||
|
zIndex: {
|
||||||
|
auto: 'auto',
|
||||||
|
'0': '0',
|
||||||
|
'10': '10',
|
||||||
|
'20': '20',
|
||||||
|
'30': '30',
|
||||||
|
'40': '40',
|
||||||
|
'50': '50',
|
||||||
|
},
|
||||||
|
gap: theme => theme('spacing'),
|
||||||
|
gridTemplateColumns: {
|
||||||
|
none: 'none',
|
||||||
|
'1': 'repeat(1, minmax(0, 1fr))',
|
||||||
|
'2': 'repeat(2, minmax(0, 1fr))',
|
||||||
|
'3': 'repeat(3, minmax(0, 1fr))',
|
||||||
|
'4': 'repeat(4, minmax(0, 1fr))',
|
||||||
|
'5': 'repeat(5, minmax(0, 1fr))',
|
||||||
|
'6': 'repeat(6, minmax(0, 1fr))',
|
||||||
|
'7': 'repeat(7, minmax(0, 1fr))',
|
||||||
|
'8': 'repeat(8, minmax(0, 1fr))',
|
||||||
|
'9': 'repeat(9, minmax(0, 1fr))',
|
||||||
|
'10': 'repeat(10, minmax(0, 1fr))',
|
||||||
|
'11': 'repeat(11, minmax(0, 1fr))',
|
||||||
|
'12': 'repeat(12, minmax(0, 1fr))',
|
||||||
|
},
|
||||||
|
gridColumn: {
|
||||||
|
auto: 'auto',
|
||||||
|
'span-1': 'span 1 / span 1',
|
||||||
|
'span-2': 'span 2 / span 2',
|
||||||
|
'span-3': 'span 3 / span 3',
|
||||||
|
'span-4': 'span 4 / span 4',
|
||||||
|
'span-5': 'span 5 / span 5',
|
||||||
|
'span-6': 'span 6 / span 6',
|
||||||
|
'span-7': 'span 7 / span 7',
|
||||||
|
'span-8': 'span 8 / span 8',
|
||||||
|
'span-9': 'span 9 / span 9',
|
||||||
|
'span-10': 'span 10 / span 10',
|
||||||
|
'span-11': 'span 11 / span 11',
|
||||||
|
'span-12': 'span 12 / span 12',
|
||||||
|
},
|
||||||
|
gridColumnStart: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
'8': '8',
|
||||||
|
'9': '9',
|
||||||
|
'10': '10',
|
||||||
|
'11': '11',
|
||||||
|
'12': '12',
|
||||||
|
'13': '13',
|
||||||
|
},
|
||||||
|
gridColumnEnd: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
'8': '8',
|
||||||
|
'9': '9',
|
||||||
|
'10': '10',
|
||||||
|
'11': '11',
|
||||||
|
'12': '12',
|
||||||
|
'13': '13',
|
||||||
|
},
|
||||||
|
gridTemplateRows: {
|
||||||
|
none: 'none',
|
||||||
|
'1': 'repeat(1, minmax(0, 1fr))',
|
||||||
|
'2': 'repeat(2, minmax(0, 1fr))',
|
||||||
|
'3': 'repeat(3, minmax(0, 1fr))',
|
||||||
|
'4': 'repeat(4, minmax(0, 1fr))',
|
||||||
|
'5': 'repeat(5, minmax(0, 1fr))',
|
||||||
|
'6': 'repeat(6, minmax(0, 1fr))',
|
||||||
|
},
|
||||||
|
gridRow: {
|
||||||
|
auto: 'auto',
|
||||||
|
'span-1': 'span 1 / span 1',
|
||||||
|
'span-2': 'span 2 / span 2',
|
||||||
|
'span-3': 'span 3 / span 3',
|
||||||
|
'span-4': 'span 4 / span 4',
|
||||||
|
'span-5': 'span 5 / span 5',
|
||||||
|
'span-6': 'span 6 / span 6',
|
||||||
|
},
|
||||||
|
gridRowStart: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
},
|
||||||
|
gridRowEnd: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
},
|
||||||
|
transformOrigin: {
|
||||||
|
center: 'center',
|
||||||
|
top: 'top',
|
||||||
|
'top-right': 'top right',
|
||||||
|
right: 'right',
|
||||||
|
'bottom-right': 'bottom right',
|
||||||
|
bottom: 'bottom',
|
||||||
|
'bottom-left': 'bottom left',
|
||||||
|
left: 'left',
|
||||||
|
'top-left': 'top left',
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
'0': '0',
|
||||||
|
'50': '.5',
|
||||||
|
'75': '.75',
|
||||||
|
'90': '.9',
|
||||||
|
'95': '.95',
|
||||||
|
'100': '1',
|
||||||
|
'105': '1.05',
|
||||||
|
'110': '1.1',
|
||||||
|
'125': '1.25',
|
||||||
|
'150': '1.5',
|
||||||
|
},
|
||||||
|
rotate: {
|
||||||
|
'-180': '-180deg',
|
||||||
|
'-90': '-90deg',
|
||||||
|
'-45': '-45deg',
|
||||||
|
'0': '0',
|
||||||
|
'45': '45deg',
|
||||||
|
'90': '90deg',
|
||||||
|
'180': '180deg',
|
||||||
|
},
|
||||||
|
translate: (theme, { negative }) => ({
|
||||||
|
...theme('spacing'),
|
||||||
|
...negative(theme('spacing')),
|
||||||
|
'-full': '-100%',
|
||||||
|
'-1/2': '-50%',
|
||||||
|
'1/2': '50%',
|
||||||
|
full: '100%',
|
||||||
|
}),
|
||||||
|
skew: {
|
||||||
|
'-12': '-12deg',
|
||||||
|
'-6': '-6deg',
|
||||||
|
'-3': '-3deg',
|
||||||
|
'0': '0',
|
||||||
|
'3': '3deg',
|
||||||
|
'6': '6deg',
|
||||||
|
'12': '12deg',
|
||||||
|
},
|
||||||
|
transitionProperty: {
|
||||||
|
none: 'none',
|
||||||
|
all: 'all',
|
||||||
|
default: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',
|
||||||
|
colors: 'background-color, border-color, color, fill, stroke',
|
||||||
|
opacity: 'opacity',
|
||||||
|
shadow: 'box-shadow',
|
||||||
|
transform: 'transform',
|
||||||
|
},
|
||||||
|
transitionTimingFunction: {
|
||||||
|
linear: 'linear',
|
||||||
|
in: 'cubic-bezier(0.4, 0, 1, 1)',
|
||||||
|
out: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||||
|
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
},
|
||||||
|
transitionDuration: {
|
||||||
|
'75': '75ms',
|
||||||
|
'100': '100ms',
|
||||||
|
'150': '150ms',
|
||||||
|
'200': '200ms',
|
||||||
|
'300': '300ms',
|
||||||
|
'500': '500ms',
|
||||||
|
'700': '700ms',
|
||||||
|
'1000': '1000ms',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
accessibility: ['responsive', 'focus'],
|
||||||
|
alignContent: ['responsive'],
|
||||||
|
alignItems: ['responsive'],
|
||||||
|
alignSelf: ['responsive'],
|
||||||
|
appearance: ['responsive'],
|
||||||
|
backgroundAttachment: ['responsive'],
|
||||||
|
backgroundColor: ['responsive', 'hover', 'focus'],
|
||||||
|
backgroundPosition: ['responsive'],
|
||||||
|
backgroundRepeat: ['responsive'],
|
||||||
|
backgroundSize: ['responsive'],
|
||||||
|
borderCollapse: ['responsive'],
|
||||||
|
borderColor: ['responsive', 'hover', 'focus'],
|
||||||
|
borderRadius: ['responsive'],
|
||||||
|
borderStyle: ['responsive'],
|
||||||
|
borderWidth: ['responsive'],
|
||||||
|
boxShadow: ['responsive', 'hover', 'focus'],
|
||||||
|
boxSizing: ['responsive'],
|
||||||
|
cursor: ['responsive'],
|
||||||
|
display: ['responsive'],
|
||||||
|
fill: ['responsive'],
|
||||||
|
flex: ['responsive'],
|
||||||
|
flexDirection: ['responsive'],
|
||||||
|
flexGrow: ['responsive'],
|
||||||
|
flexShrink: ['responsive'],
|
||||||
|
flexWrap: ['responsive'],
|
||||||
|
float: ['responsive'],
|
||||||
|
clear: ['responsive'],
|
||||||
|
fontFamily: ['responsive'],
|
||||||
|
fontSize: ['responsive'],
|
||||||
|
fontSmoothing: ['responsive'],
|
||||||
|
fontStyle: ['responsive'],
|
||||||
|
fontWeight: ['responsive', 'hover', 'focus'],
|
||||||
|
height: ['responsive'],
|
||||||
|
inset: ['responsive'],
|
||||||
|
justifyContent: ['responsive'],
|
||||||
|
letterSpacing: ['responsive'],
|
||||||
|
lineHeight: ['responsive'],
|
||||||
|
listStylePosition: ['responsive'],
|
||||||
|
listStyleType: ['responsive'],
|
||||||
|
margin: ['responsive'],
|
||||||
|
maxHeight: ['responsive'],
|
||||||
|
maxWidth: ['responsive'],
|
||||||
|
minHeight: ['responsive'],
|
||||||
|
minWidth: ['responsive'],
|
||||||
|
objectFit: ['responsive'],
|
||||||
|
objectPosition: ['responsive'],
|
||||||
|
opacity: ['responsive', 'hover', 'focus'],
|
||||||
|
order: ['responsive'],
|
||||||
|
outline: ['responsive', 'focus'],
|
||||||
|
overflow: ['responsive'],
|
||||||
|
padding: ['responsive'],
|
||||||
|
placeholderColor: ['responsive', 'focus'],
|
||||||
|
pointerEvents: ['responsive'],
|
||||||
|
position: ['responsive'],
|
||||||
|
resize: ['responsive'],
|
||||||
|
stroke: ['responsive'],
|
||||||
|
strokeWidth: ['responsive'],
|
||||||
|
tableLayout: ['responsive'],
|
||||||
|
textAlign: ['responsive'],
|
||||||
|
textColor: ['responsive', 'hover', 'focus'],
|
||||||
|
textDecoration: ['responsive', 'hover', 'focus'],
|
||||||
|
textTransform: ['responsive'],
|
||||||
|
userSelect: ['responsive'],
|
||||||
|
verticalAlign: ['responsive'],
|
||||||
|
visibility: ['responsive'],
|
||||||
|
whitespace: ['responsive'],
|
||||||
|
width: ['responsive'],
|
||||||
|
wordBreak: ['responsive'],
|
||||||
|
zIndex: ['responsive'],
|
||||||
|
gap: ['responsive'],
|
||||||
|
gridAutoFlow: ['responsive'],
|
||||||
|
gridTemplateColumns: ['responsive'],
|
||||||
|
gridColumn: ['responsive'],
|
||||||
|
gridColumnStart: ['responsive'],
|
||||||
|
gridColumnEnd: ['responsive'],
|
||||||
|
gridTemplateRows: ['responsive'],
|
||||||
|
gridRow: ['responsive'],
|
||||||
|
gridRowStart: ['responsive'],
|
||||||
|
gridRowEnd: ['responsive'],
|
||||||
|
transform: ['responsive'],
|
||||||
|
transformOrigin: ['responsive'],
|
||||||
|
scale: ['responsive', 'hover', 'focus'],
|
||||||
|
rotate: ['responsive', 'hover', 'focus'],
|
||||||
|
translate: ['responsive', 'hover', 'focus'],
|
||||||
|
skew: ['responsive', 'hover', 'focus'],
|
||||||
|
transitionProperty: ['responsive'],
|
||||||
|
transitionTimingFunction: ['responsive'],
|
||||||
|
transitionDuration: ['responsive'],
|
||||||
|
},
|
||||||
|
corePlugins: {},
|
||||||
|
plugins: [],
|
||||||
|
}
|
48
packages/playground/test.res
Normal file
48
packages/playground/test.res
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
@react.component
|
||||||
|
let make = (
|
||||||
|
~disabled: bool=?,
|
||||||
|
~ghost: bool=?,
|
||||||
|
~href: string=?,
|
||||||
|
~htmlType: @string [#button | #submit | #submit]=?,
|
||||||
|
~icon: 'a=?,
|
||||||
|
~shape: @string [#circle | #round]=?,
|
||||||
|
~size: @string [#small | #large]=?,
|
||||||
|
~target: string=?,
|
||||||
|
~loading: bool=?,
|
||||||
|
~_type: @string
|
||||||
|
[
|
||||||
|
| #primary
|
||||||
|
| #default
|
||||||
|
| #dashed
|
||||||
|
| #danger
|
||||||
|
| #link
|
||||||
|
| #ghost
|
||||||
|
]=?,
|
||||||
|
~onClick: ReactEvent.Mouse.t => unit=?,
|
||||||
|
~block: bool=?,
|
||||||
|
~children: React.element=?,
|
||||||
|
~className: string=?,
|
||||||
|
~id: string=?,
|
||||||
|
~testId: string=?,
|
||||||
|
) =>
|
||||||
|
ReasonReact.cloneElement(
|
||||||
|
<AntButton
|
||||||
|
_type
|
||||||
|
disabled
|
||||||
|
ghost
|
||||||
|
href
|
||||||
|
htmlType
|
||||||
|
icon={Antd_Utils.tts(Antd_Icon.iconToJsSafe(~icon, ()))}
|
||||||
|
shape
|
||||||
|
size
|
||||||
|
target
|
||||||
|
onClick
|
||||||
|
block
|
||||||
|
loading
|
||||||
|
className
|
||||||
|
id>
|
||||||
|
children
|
||||||
|
</AntButton>,
|
||||||
|
~props={"data-testid": testId},
|
||||||
|
[],
|
||||||
|
)
|
10647
packages/playground/yarn.lock
Normal file
10647
packages/playground/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
11654
packages/playground/yarn.nix
Normal file
11654
packages/playground/yarn.nix
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -4,10 +4,10 @@ open Expect
|
||||||
describe("Bandwidth", () => {
|
describe("Bandwidth", () => {
|
||||||
test("nrd0()", () => {
|
test("nrd0()", () => {
|
||||||
let data = [1., 4., 3., 2.]
|
let data = [1., 4., 3., 2.]
|
||||||
expect(Bandwidth.nrd0(data)) |> toEqual(0.7625801874014622)
|
expect(Bandwidth.nrd0(data)) -> toEqual(0.7625801874014622)
|
||||||
})
|
})
|
||||||
test("nrd()", () => {
|
test("nrd()", () => {
|
||||||
let data = [1., 4., 3., 2.]
|
let data = [1., 4., 3., 2.]
|
||||||
expect(Bandwidth.nrd(data)) |> toEqual(0.8981499984950554)
|
expect(Bandwidth.nrd(data)) -> toEqual(0.8981499984950554)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,8 +3,8 @@ open Expect
|
||||||
|
|
||||||
let makeTest = (~only=false, str, item1, item2) =>
|
let makeTest = (~only=false, str, item1, item2) =>
|
||||||
only
|
only
|
||||||
? Only.test(str, () => expect(item1) |> toEqual(item2))
|
? Only.test(str, () => expect(item1) -> toEqual(item2))
|
||||||
: test(str, () => expect(item1) |> toEqual(item2))
|
: test(str, () => expect(item1) -> toEqual(item2))
|
||||||
|
|
||||||
describe("DistTypes", () =>
|
describe("DistTypes", () =>
|
||||||
describe("Domain", () => {
|
describe("Domain", () => {
|
||||||
|
|
57
packages/squiggle-lang/__tests__/Hardcoded__Test.res
Normal file
57
packages/squiggle-lang/__tests__/Hardcoded__Test.res
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"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}); */
|
20
packages/squiggle-lang/__tests__/Lodash__test.res
Normal file
20
packages/squiggle-lang/__tests__/Lodash__test.res
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
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))
|
||||||
|
|
||||||
|
describe("Lodash", () =>
|
||||||
|
describe("Lodash", () => {
|
||||||
|
makeTest("min", Lodash.min([1, 3, 4]), 1)
|
||||||
|
makeTest("max", Lodash.max([1, 3, 4]), 4)
|
||||||
|
makeTest("uniq", Lodash.uniq([1, 3, 4, 4]), [1, 3, 4])
|
||||||
|
makeTest(
|
||||||
|
"countBy",
|
||||||
|
Lodash.countBy([1, 3, 4, 4], r => r),
|
||||||
|
Js.Dict.fromArray([("1", 1), ("3", 1), ("4", 2)]),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
|
@ -3,8 +3,8 @@ open Expect
|
||||||
|
|
||||||
let makeTest = (~only=false, str, item1, item2) =>
|
let makeTest = (~only=false, str, item1, item2) =>
|
||||||
only
|
only
|
||||||
? Only.test(str, () => expect(item1) |> toEqual(item2))
|
? Only.test(str, () => expect(item1) -> toEqual(item2))
|
||||||
: test(str, () => expect(item1) |> toEqual(item2))
|
: test(str, () => expect(item1) -> toEqual(item2))
|
||||||
|
|
||||||
describe("Lodash", () =>
|
describe("Lodash", () =>
|
||||||
describe("Lodash", () => {
|
describe("Lodash", () => {
|
||||||
|
|
|
@ -3,8 +3,8 @@ open Expect
|
||||||
|
|
||||||
let makeTest = (~only=false, str, item1, item2) =>
|
let makeTest = (~only=false, str, item1, item2) =>
|
||||||
only
|
only
|
||||||
? Only.test(str, () => expect(item1) |> toEqual(item2))
|
? Only.test(str, () => expect(item1) -> toEqual(item2))
|
||||||
: test(str, () => expect(item1) |> toEqual(item2))
|
: test(str, () => expect(item1) -> toEqual(item2))
|
||||||
|
|
||||||
let shape1: DistTypes.xyShape = {xs: [1., 4., 8.], ys: [0.2, 0.4, 0.8]}
|
let shape1: DistTypes.xyShape = {xs: [1., 4., 8.], ys: [0.2, 0.4, 0.8]}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "probExample",
|
"name": "SquiggleExperimental",
|
||||||
"reason": {},
|
"reason": {},
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
"suffix": ".bs.js",
|
"suffix": ".bs.js",
|
||||||
"namespace": true,
|
"namespace": true,
|
||||||
"bs-dependencies": [
|
"bs-dependencies": [
|
||||||
"@glennsl/bs-jest",
|
"@glennsl/rescript-jest",
|
||||||
"@glennsl/bs-json",
|
"@glennsl/bs-json",
|
||||||
"rationale"
|
"rationale"
|
||||||
],
|
],
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user