Merge remote-tracking branch 'origin/master' into feature/1083
This commit is contained in:
commit
eaea7cc69a
|
@ -1,25 +1,365 @@
|
||||||
open Jest;
|
open Jest;
|
||||||
open Expect;
|
open Expect;
|
||||||
|
|
||||||
let shape: DistributionTypes.xyShape = {
|
let shape: DistTypes.xyShape = {xs: [|1., 4., 8.|], ys: [|8., 9., 2.|]};
|
||||||
xs: [|1., 4., 8.|],
|
|
||||||
ys: [|8., 9., 2.|],
|
|
||||||
};
|
|
||||||
|
|
||||||
let step: DistributionTypes.xyShape = {
|
let makeTest = (~only=false, str, item1, item2) =>
|
||||||
xs: [|1., 4., 8.|],
|
only
|
||||||
ys: [|8., 17., 19.|],
|
? Only.test(str, () =>
|
||||||
};
|
expect(item1) |> toEqual(item2)
|
||||||
|
)
|
||||||
|
: test(str, () =>
|
||||||
|
expect(item1) |> toEqual(item2)
|
||||||
|
);
|
||||||
|
|
||||||
open Shape;
|
describe("Shape", () => {
|
||||||
|
describe("Continuous", () => {
|
||||||
describe("Shape", () =>
|
open Distributions.Continuous;
|
||||||
describe("XYShape", () => {
|
let continuous = make(shape, `Linear);
|
||||||
test("#ySum", () =>
|
makeTest("minX", T.minX(continuous), Some(1.0));
|
||||||
expect(XYShape.ySum(shape)) |> toEqual(19.0)
|
makeTest("maxX", T.maxX(continuous), Some(8.0));
|
||||||
|
makeTest(
|
||||||
|
"pointwiseFmap",
|
||||||
|
T.pointwiseFmap(r => r *. 2.0, continuous) |> getShape |> (r => r.ys),
|
||||||
|
[|16., 18.0, 4.0|],
|
||||||
);
|
);
|
||||||
test("#yFOo", () =>
|
describe("xToY", () => {
|
||||||
expect(Discrete.integrate(shape)) |> toEqual(step)
|
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(shape, `Stepwise);
|
||||||
|
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({xs: [|1., 4., 8.|], ys: [|0.1, 5., 1.0|]}, `Stepwise);
|
||||||
|
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({xs: [|0.0|], ys: [|0.3|]}, `Stepwise);
|
||||||
|
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.scaleToIntegralSum(~intendedSum=1.0)
|
||||||
|
|> T.Integral.sum(~cache=None),
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Discrete", () => {
|
||||||
|
open Distributions.Discrete;
|
||||||
|
let shape: DistTypes.xyShape = {
|
||||||
|
xs: [|1., 4., 8.|],
|
||||||
|
ys: [|0.3, 0.5, 0.2|],
|
||||||
|
};
|
||||||
|
let discrete = shape;
|
||||||
|
makeTest("minX", T.minX(discrete), Some(1.0));
|
||||||
|
makeTest("maxX", T.maxX(discrete), Some(8.0));
|
||||||
|
makeTest(
|
||||||
|
"pointwiseFmap",
|
||||||
|
T.pointwiseFmap(r => r *. 2.0, discrete) |> (r => 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",
|
||||||
|
T.scaleBy(~scale=4.0, discrete),
|
||||||
|
{xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]},
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"scaleToIntegralSum",
|
||||||
|
T.scaleToIntegralSum(~intendedSum=4.0, discrete),
|
||||||
|
{xs: [|1., 4., 8.|], ys: [|1.2, 2.0, 0.8|]},
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"scaleToIntegralSum: back and forth",
|
||||||
|
discrete
|
||||||
|
|> T.scaleToIntegralSum(~intendedSum=4.0)
|
||||||
|
|> T.scaleToIntegralSum(~intendedSum=1.0),
|
||||||
|
discrete,
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"integral",
|
||||||
|
T.Integral.get(~cache=None, discrete),
|
||||||
|
Distributions.Continuous.make(
|
||||||
|
{xs: [|1., 4., 8.|], ys: [|0.3, 0.8, 1.0|]},
|
||||||
|
`Stepwise,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"integral with 1 element",
|
||||||
|
T.Integral.get(~cache=None, {xs: [|0.0|], ys: [|1.0|]}),
|
||||||
|
Distributions.Continuous.make({xs: [|0.0|], ys: [|1.0|]}, `Stepwise),
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"integralXToY",
|
||||||
|
T.Integral.xToY(~cache=None, 6.0, discrete),
|
||||||
|
0.9,
|
||||||
|
);
|
||||||
|
makeTest("integralEndY", T.Integral.sum(~cache=None, discrete), 1.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Mixed", () => {
|
||||||
|
open Distributions.Mixed;
|
||||||
|
let discrete: DistTypes.xyShape = {
|
||||||
|
xs: [|1., 4., 8.|],
|
||||||
|
ys: [|0.3, 0.5, 0.2|],
|
||||||
|
};
|
||||||
|
let continuous =
|
||||||
|
Distributions.Continuous.make(
|
||||||
|
{xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]},
|
||||||
|
`Linear,
|
||||||
|
)
|
||||||
|
|> Distributions.Continuous.T.scaleToIntegralSum(~intendedSum=1.0);
|
||||||
|
let mixed =
|
||||||
|
MixedShapeBuilder.build(
|
||||||
|
~continuous,
|
||||||
|
~discrete,
|
||||||
|
~assumptions={
|
||||||
|
continuous: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discreteProbabilityMass: Some(0.5),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|> E.O.toExn("");
|
||||||
|
makeTest("minX", T.minX(mixed), Some(1.0));
|
||||||
|
makeTest("maxX", T.maxX(mixed), Some(14.0));
|
||||||
|
makeTest(
|
||||||
|
"pointwiseFmap",
|
||||||
|
T.pointwiseFmap(r => r *. 2.0, mixed),
|
||||||
|
Distributions.Mixed.make(
|
||||||
|
~continuous=
|
||||||
|
Distributions.Continuous.make(
|
||||||
|
{
|
||||||
|
xs: [|3., 7., 14.|],
|
||||||
|
ys: [|
|
||||||
|
0.11588411588411589,
|
||||||
|
0.16383616383616384,
|
||||||
|
0.24775224775224775,
|
||||||
|
|],
|
||||||
|
},
|
||||||
|
`Linear,
|
||||||
|
),
|
||||||
|
~discrete={xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]},
|
||||||
|
~discreteProbabilityMassFraction=0.5,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
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",
|
||||||
|
T.scaleBy(~scale=2.0, mixed),
|
||||||
|
Distributions.Mixed.make(
|
||||||
|
~continuous=
|
||||||
|
Distributions.Continuous.make(
|
||||||
|
{
|
||||||
|
xs: [|3., 7., 14.|],
|
||||||
|
ys: [|
|
||||||
|
0.11588411588411589,
|
||||||
|
0.16383616383616384,
|
||||||
|
0.24775224775224775,
|
||||||
|
|],
|
||||||
|
},
|
||||||
|
`Linear,
|
||||||
|
),
|
||||||
|
~discrete={xs: [|1., 4., 8.|], ys: [|0.6, 1.0, 0.4|]},
|
||||||
|
~discreteProbabilityMassFraction=0.5,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
makeTest(
|
||||||
|
"integral",
|
||||||
|
T.Integral.get(~cache=None, mixed),
|
||||||
|
Distributions.Continuous.make(
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
|],
|
||||||
|
},
|
||||||
|
`Linear,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Mixed", () => {
|
||||||
|
open Distributions.DistPlus;
|
||||||
|
let discrete: DistTypes.xyShape = {
|
||||||
|
xs: [|1., 4., 8.|],
|
||||||
|
ys: [|0.3, 0.5, 0.2|],
|
||||||
|
};
|
||||||
|
let continuous =
|
||||||
|
Distributions.Continuous.make(
|
||||||
|
{xs: [|3., 7., 14.|], ys: [|0.058, 0.082, 0.124|]},
|
||||||
|
`Linear,
|
||||||
|
)
|
||||||
|
|> Distributions.Continuous.T.scaleToIntegralSum(~intendedSum=1.0);
|
||||||
|
let mixed =
|
||||||
|
MixedShapeBuilder.build(
|
||||||
|
~continuous,
|
||||||
|
~discrete,
|
||||||
|
~assumptions={
|
||||||
|
continuous: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
|
discreteProbabilityMass: Some(0.5),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|> E.O.toExn("");
|
||||||
|
let distPlus =
|
||||||
|
Distributions.DistPlus.make(
|
||||||
|
~shape=Mixed(mixed),
|
||||||
|
~guesstimatorString=None,
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
makeTest("minX", T.minX(distPlus), Some(1.0));
|
||||||
|
makeTest("maxX", T.maxX(distPlus), Some(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(
|
||||||
|
Distributions.Continuous.make(
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
|],
|
||||||
|
},
|
||||||
|
`Linear,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -26,11 +26,10 @@ let buildIds = entries => {
|
||||||
f.id = curPath ++ "/" ++ genId(f.title, curPath);
|
f.id = curPath ++ "/" ++ genId(f.title, curPath);
|
||||||
HS.set(entriesByPath, f.id, FolderEntry(f));
|
HS.set(entriesByPath, f.id, FolderEntry(f));
|
||||||
f.children
|
f.children
|
||||||
|> E.L.iter(e =>
|
|> E.L.iter(
|
||||||
switch (e) {
|
fun
|
||||||
| CompEntry(c) => processEntry(c, f.id)
|
| CompEntry(c) => processEntry(c, f.id)
|
||||||
| FolderEntry(f) => processFolder(f, f.id)
|
| FolderEntry(f) => processFolder(f, f.id),
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
and processEntry = (c: compEntry, curPath) => {
|
and processEntry = (c: compEntry, curPath) => {
|
||||||
|
@ -38,11 +37,10 @@ let buildIds = entries => {
|
||||||
HS.set(entriesByPath, c.id, CompEntry(c));
|
HS.set(entriesByPath, c.id, CompEntry(c));
|
||||||
};
|
};
|
||||||
entries
|
entries
|
||||||
|> E.L.iter(e =>
|
|> E.L.iter(
|
||||||
switch (e) {
|
fun
|
||||||
| CompEntry(c) => processEntry(c, "")
|
| CompEntry(c) => processEntry(c, "")
|
||||||
| FolderEntry(f) => processFolder(f, "")
|
| FolderEntry(f) => processFolder(f, ""),
|
||||||
}
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,31 @@
|
||||||
// "mm(floor(uniform(30,35)), normal(50,20), [.25,.5])",
|
// "mm(floor(uniform(30,35)), normal(50,20), [.25,.5])",
|
||||||
|
// "mm(floor(normal(28,4)), normal(32,2), uniform(20,24), [.5,.2,.1])",
|
||||||
|
|
||||||
let timeDist =
|
let timeDist =
|
||||||
GenericDistribution.make(
|
DistPlusIngredients.make(
|
||||||
~generationSource=
|
~guesstimatorString="mm(floor(10 to 15), 10 to 11, [.9,.1])",
|
||||||
GuesstimatorString("mm(floor(normal(30,3)), normal(39,1), [.5,.5])"),
|
|
||||||
~probabilityType=Pdf,
|
|
||||||
~domain=Complete,
|
~domain=Complete,
|
||||||
~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `days}),
|
~unit=
|
||||||
|
DistTypes.TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
|
||||||
(),
|
(),
|
||||||
)
|
);
|
||||||
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
|
|
||||||
|
let setup = dist =>
|
||||||
|
dist
|
||||||
|
|> DistPlusIngredients.toDistPlus(~sampleCount=5000, ~outputXYPoints=1000);
|
||||||
|
|
||||||
let distributions = () =>
|
let distributions = () =>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h2> {"Basic Mixed Distribution" |> ReasonReact.string} </h2>
|
<h2> {"Single-Discrete" |> ReasonReact.string} </h2>
|
||||||
{timeDist
|
{setup(
|
||||||
|> E.O.bind(_, GenericDistribution.normalize)
|
DistPlusIngredients.make(
|
||||||
|> E.O.React.fmapOrNull(dist => <GenericDistributionChart dist />)}
|
~guesstimatorString="8 to 12, [.5,.5])",
|
||||||
<h2> {"Simple Continuous" |> ReasonReact.string} </h2>
|
~domain=Complete,
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|> E.O.React.fmapOrNull(distPlus => <DistPlusPlot distPlus />)}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
[@bs.module "./cdfChartReact.js"]
|
|
||||||
external cdfChart: ReasonReact.reactClass = "default";
|
|
||||||
|
|
||||||
type primaryDistribution =
|
|
||||||
option({
|
|
||||||
.
|
|
||||||
"xs": array(float),
|
|
||||||
"ys": array(float),
|
|
||||||
});
|
|
||||||
|
|
||||||
type discrete =
|
|
||||||
option({
|
|
||||||
.
|
|
||||||
"xs": array(float),
|
|
||||||
"ys": array(float),
|
|
||||||
});
|
|
||||||
|
|
||||||
[@react.component]
|
|
||||||
let make =
|
|
||||||
(
|
|
||||||
~height=?,
|
|
||||||
~marginBottom=?,
|
|
||||||
~marginTop=?,
|
|
||||||
~maxX=?,
|
|
||||||
~minX=?,
|
|
||||||
~onHover=(f: float) => (),
|
|
||||||
~continuous=?,
|
|
||||||
~discrete=?,
|
|
||||||
~scale=?,
|
|
||||||
~showDistributionLines=?,
|
|
||||||
~showDistributionYAxis=?,
|
|
||||||
~showVerticalLine=?,
|
|
||||||
~timeScale=?,
|
|
||||||
~verticalLine=?,
|
|
||||||
~children=[||],
|
|
||||||
) =>
|
|
||||||
ReasonReact.wrapJsForReason(
|
|
||||||
~reactClass=cdfChart,
|
|
||||||
~props=
|
|
||||||
makeProps(
|
|
||||||
~height?,
|
|
||||||
~marginBottom?,
|
|
||||||
~marginTop?,
|
|
||||||
~maxX?,
|
|
||||||
~minX?,
|
|
||||||
~onHover,
|
|
||||||
~continuous?,
|
|
||||||
~discrete?,
|
|
||||||
~scale?,
|
|
||||||
~showDistributionLines?,
|
|
||||||
~showDistributionYAxis?,
|
|
||||||
~showVerticalLine?,
|
|
||||||
~timeScale?,
|
|
||||||
~verticalLine?,
|
|
||||||
(),
|
|
||||||
),
|
|
||||||
children,
|
|
||||||
)
|
|
||||||
|> ReasonReact.element;
|
|
|
@ -1,24 +0,0 @@
|
||||||
module Styles = {
|
|
||||||
open Css;
|
|
||||||
let graph = chartColor =>
|
|
||||||
style([
|
|
||||||
selector(".axis", [fontSize(`px(9))]),
|
|
||||||
selector(".domain", [display(`none)]),
|
|
||||||
selector(".tick line", [display(`none)]),
|
|
||||||
selector(".tick text", [color(`hex("bfcad4"))]),
|
|
||||||
selector(".chart .area-path", [SVG.fill(chartColor)]),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
[@react.component]
|
|
||||||
let make = (~minX=None, ~maxX=None, ~height=50, ~color=`hex("7e9db7")) =>
|
|
||||||
<div className={Styles.graph(color)}>
|
|
||||||
<CdfChart__Base
|
|
||||||
height
|
|
||||||
?minX
|
|
||||||
?maxX
|
|
||||||
marginBottom=20
|
|
||||||
showVerticalLine=false
|
|
||||||
showDistributionLines=false
|
|
||||||
/>
|
|
||||||
</div>;
|
|
120
src/components/charts/DistPlusPlot.re
Normal file
120
src/components/charts/DistPlusPlot.re
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
module DistPlusChart = {
|
||||||
|
[@react.component]
|
||||||
|
let make = (~distPlus: DistTypes.distPlus, ~onHover) => {
|
||||||
|
open Distributions.DistPlus;
|
||||||
|
// todo: Change to scaledContinuous and scaledDiscrete
|
||||||
|
let discrete = distPlus |> T.toDiscrete;
|
||||||
|
let continuous =
|
||||||
|
distPlus
|
||||||
|
|> T.toContinuous
|
||||||
|
|> E.O.fmap(Distributions.Continuous.getShape);
|
||||||
|
let minX = T.minX(distPlus);
|
||||||
|
let maxX = T.maxX(distPlus);
|
||||||
|
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson;
|
||||||
|
<DistributionPlot
|
||||||
|
minX
|
||||||
|
maxX
|
||||||
|
?discrete
|
||||||
|
?continuous
|
||||||
|
color={`hex("333")}
|
||||||
|
onHover
|
||||||
|
timeScale
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module IntegralChart = {
|
||||||
|
[@react.component]
|
||||||
|
let make = (~distPlus: DistTypes.distPlus, ~onHover) => {
|
||||||
|
open Distributions.DistPlus;
|
||||||
|
let integral =
|
||||||
|
Distributions.DistPlus.T.toShape(distPlus)
|
||||||
|
|> Distributions.Shape.T.Integral.get(~cache=None);
|
||||||
|
let continuous =
|
||||||
|
integral
|
||||||
|
|> Distributions.Continuous.toLinear
|
||||||
|
|> E.O.fmap(Distributions.Continuous.getShape);
|
||||||
|
let minX = integral |> Distributions.Continuous.T.minX;
|
||||||
|
let maxX = integral |> Distributions.Continuous.T.maxX;
|
||||||
|
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson;
|
||||||
|
Js.log3("HIHI", continuous, distPlus);
|
||||||
|
<DistributionPlot
|
||||||
|
minX
|
||||||
|
maxX
|
||||||
|
?continuous
|
||||||
|
color={`hex("333")}
|
||||||
|
timeScale
|
||||||
|
onHover
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make = (~distPlus: DistTypes.distPlus) => {
|
||||||
|
let (x, setX) = React.useState(() => 0.);
|
||||||
|
let chart =
|
||||||
|
React.useMemo1(
|
||||||
|
() => {<DistPlusChart distPlus onHover={r => {setX(_ => r)}} />},
|
||||||
|
[|distPlus|],
|
||||||
|
);
|
||||||
|
let chart2 =
|
||||||
|
React.useMemo1(
|
||||||
|
() => {<IntegralChart distPlus onHover={r => {setX(_ => r)}} />},
|
||||||
|
[|distPlus|],
|
||||||
|
);
|
||||||
|
<div>
|
||||||
|
chart
|
||||||
|
chart2
|
||||||
|
<table className="table-auto">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2"> {"X Point" |> ReasonReact.string} </th>
|
||||||
|
<th className="px-4 py-2">
|
||||||
|
{"Discrete Value" |> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2">
|
||||||
|
{"Continuous Value" |> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2">
|
||||||
|
{"Y Integral to Point" |> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{x |> E.Float.toString |> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> Distributions.DistPlus.T.xToY(x)
|
||||||
|
|> DistTypes.MixedPoint.toDiscreteValue
|
||||||
|
|> Js.Float.toPrecisionWithPrecision(_, ~digits=7)
|
||||||
|
|> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> Distributions.DistPlus.T.xToY(x)
|
||||||
|
|> DistTypes.MixedPoint.toContinuousValue
|
||||||
|
|> Js.Float.toPrecisionWithPrecision(_, ~digits=7)
|
||||||
|
|> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> Distributions.DistPlus.T.Integral.xToY(~cache=None, x)
|
||||||
|
|> E.Float.with2DigitsPrecision
|
||||||
|
|> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 border ">
|
||||||
|
{distPlus
|
||||||
|
|> Distributions.DistPlus.T.Integral.sum(~cache=None)
|
||||||
|
|> E.Float.with2DigitsPrecision
|
||||||
|
|> ReasonReact.string}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div />
|
||||||
|
</div>;
|
||||||
|
// chart
|
||||||
|
};
|
|
@ -1,3 +1,65 @@
|
||||||
|
module RawPlot = {
|
||||||
|
[@bs.module "./distPlotReact.js"]
|
||||||
|
external plot: ReasonReact.reactClass = "default";
|
||||||
|
|
||||||
|
type primaryDistribution =
|
||||||
|
option({
|
||||||
|
.
|
||||||
|
"xs": array(float),
|
||||||
|
"ys": array(float),
|
||||||
|
});
|
||||||
|
|
||||||
|
type discrete =
|
||||||
|
option({
|
||||||
|
.
|
||||||
|
"xs": array(float),
|
||||||
|
"ys": array(float),
|
||||||
|
});
|
||||||
|
|
||||||
|
[@react.component]
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~height=?,
|
||||||
|
~marginBottom=?,
|
||||||
|
~marginTop=?,
|
||||||
|
~maxX=?,
|
||||||
|
~minX=?,
|
||||||
|
~onHover=(f: float) => (),
|
||||||
|
~continuous=?,
|
||||||
|
~discrete=?,
|
||||||
|
~scale=?,
|
||||||
|
~showDistributionLines=?,
|
||||||
|
~showDistributionYAxis=?,
|
||||||
|
~showVerticalLine=?,
|
||||||
|
~timeScale=?,
|
||||||
|
~verticalLine=?,
|
||||||
|
~children=[||],
|
||||||
|
) =>
|
||||||
|
ReasonReact.wrapJsForReason(
|
||||||
|
~reactClass=plot,
|
||||||
|
~props=
|
||||||
|
makeProps(
|
||||||
|
~height?,
|
||||||
|
~marginBottom?,
|
||||||
|
~marginTop?,
|
||||||
|
~maxX?,
|
||||||
|
~minX?,
|
||||||
|
~onHover,
|
||||||
|
~continuous?,
|
||||||
|
~discrete?,
|
||||||
|
~scale?,
|
||||||
|
~showDistributionLines?,
|
||||||
|
~showDistributionYAxis?,
|
||||||
|
~showVerticalLine?,
|
||||||
|
~timeScale?,
|
||||||
|
~verticalLine?,
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
|> ReasonReact.element;
|
||||||
|
};
|
||||||
|
|
||||||
module Styles = {
|
module Styles = {
|
||||||
open Css;
|
open Css;
|
||||||
let textOverlay = style([position(`absolute)]);
|
let textOverlay = style([position(`absolute)]);
|
||||||
|
@ -40,17 +102,17 @@ let make =
|
||||||
~timeScale=?,
|
~timeScale=?,
|
||||||
) => {
|
) => {
|
||||||
<div className={Styles.graph(color)}>
|
<div className={Styles.graph(color)}>
|
||||||
<CdfChart__Base
|
<RawPlot
|
||||||
?maxX
|
?maxX
|
||||||
?minX
|
?minX
|
||||||
?scale
|
?scale
|
||||||
?timeScale
|
?timeScale
|
||||||
discrete={discrete |> E.O.fmap(Shape.Discrete.toJs)}
|
discrete={discrete |> E.O.fmap(XYShape.toJs)}
|
||||||
height
|
height
|
||||||
marginBottom=50
|
marginBottom=50
|
||||||
marginTop=0
|
marginTop=0
|
||||||
onHover
|
onHover
|
||||||
continuous={continuous |> E.O.fmap(Shape.XYShape.toJs)}
|
continuous={continuous |> E.O.fmap(XYShape.toJs)}
|
||||||
showDistributionLines
|
showDistributionLines
|
||||||
showDistributionYAxis
|
showDistributionYAxis
|
||||||
showVerticalLine
|
showVerticalLine
|
|
@ -129,6 +129,8 @@ export class CdfChartD3 {
|
||||||
|
|
||||||
data(data) {
|
data(data) {
|
||||||
this.attrs.data = data;
|
this.attrs.data = data;
|
||||||
|
this.attrs.data.continuous = data.continuous || {xs: [], ys: []};
|
||||||
|
this.attrs.data.discrete = data.discrete || {xs: [], ys: []};
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,21 +310,13 @@ export class CdfChartD3 {
|
||||||
// Add drawing rectangle.
|
// Add drawing rectangle.
|
||||||
{
|
{
|
||||||
const context = this;
|
const context = this;
|
||||||
const range = [
|
|
||||||
xScale(dataPoints[dataPoints.length - 1][0].x),
|
|
||||||
xScale(
|
|
||||||
dataPoints
|
|
||||||
[dataPoints.length - 1]
|
|
||||||
[dataPoints[dataPoints.length - 1].length - 1].x,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
function mouseover() {
|
function mouseover() {
|
||||||
const mouse = d3.mouse(this);
|
const mouse = d3.mouse(this);
|
||||||
hoverLine.attr('opacity', 1).attr('x1', mouse[0]).attr('x2', mouse[0]);
|
hoverLine.attr('opacity', 1).attr('x1', mouse[0]).attr('x2', mouse[0]);
|
||||||
const xValue = mouse[0] > range[0] && mouse[0] < range[1]
|
const xValue = xScale.invert(mouse[0]);
|
||||||
? xScale.invert(mouse[0]).toFixed(2)
|
// This used to be here, but doesn't seem important
|
||||||
: 0;
|
// const xValue = (mouse[0] > range[0] && mouse[0] < range[1]) ? : 0;
|
||||||
context.attrs.onHover(xValue);
|
context.attrs.onHover(xValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useSize } from 'react-use';
|
import { useSize } from 'react-use';
|
||||||
|
import { CdfChartD3 } from './distPlotD3';
|
||||||
import { CdfChartD3 } from './cdfChartD3';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param min
|
* @param min
|
|
@ -1,75 +0,0 @@
|
||||||
module Shapee = {
|
|
||||||
[@react.component]
|
|
||||||
let make = (~shape: DistributionTypes.pointsType, ~timeScale, ~onHover) => {
|
|
||||||
let discrete = Shape.PointsType.scaledDiscreteComponent(shape);
|
|
||||||
let continuous = Shape.PointsType.scaledContinuousComponent(shape);
|
|
||||||
<div>
|
|
||||||
<CdfChart__Plain
|
|
||||||
minX={Shape.PointsType.minX(shape)}
|
|
||||||
maxX={Shape.PointsType.maxX(shape)}
|
|
||||||
?discrete
|
|
||||||
?continuous
|
|
||||||
color={`hex("333")}
|
|
||||||
onHover
|
|
||||||
timeScale
|
|
||||||
/>
|
|
||||||
{discrete |> E.O.React.fmapOrNull(Shape.Discrete.render)}
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module GenericDist = {
|
|
||||||
[@react.component]
|
|
||||||
let make = (~genericDistribution: DistributionTypes.genericDistribution) => {
|
|
||||||
let (x, setX) = React.useState(() => 0.);
|
|
||||||
let timeScale =
|
|
||||||
genericDistribution.unit |> DistributionTypes.DistributionUnit.toJson;
|
|
||||||
let chart =
|
|
||||||
React.useMemo1(
|
|
||||||
() => {
|
|
||||||
genericDistribution
|
|
||||||
|> DistributionTypes.shape
|
|
||||||
|> E.O.React.fmapOrNull(shape => {
|
|
||||||
<Shapee shape timeScale onHover={r => setX(_ => r)} />
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[|genericDistribution|],
|
|
||||||
);
|
|
||||||
<div>
|
|
||||||
chart
|
|
||||||
<table className="table-auto">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-2"> {"X Point" |> ReasonReact.string} </th>
|
|
||||||
<th className="px-4 py-2">
|
|
||||||
{"Y Integral to Point" |> ReasonReact.string}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-2 border ">
|
|
||||||
{x |> E.Float.toString |> ReasonReact.string}
|
|
||||||
</th>
|
|
||||||
<th className="px-4 py-2 border ">
|
|
||||||
{genericDistribution->GenericDistribution.yIntegral(x)
|
|
||||||
|> E.O.fmap(E.Float.with2DigitsPrecision)
|
|
||||||
|> E.O.default("")
|
|
||||||
|> ReasonReact.string}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div />
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
[@react.component]
|
|
||||||
let make = (~dist) => {
|
|
||||||
switch ((dist: DistributionTypes.genericDistribution)) {
|
|
||||||
| {generationSource: Shape(_)} =>
|
|
||||||
<div> <GenericDist genericDistribution=dist /> </div>
|
|
||||||
| _ => <div />
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,66 +0,0 @@
|
||||||
type domainLimit = {
|
|
||||||
xPoint: float,
|
|
||||||
excludingProbabilityMass: float,
|
|
||||||
};
|
|
||||||
|
|
||||||
type domain =
|
|
||||||
| Complete
|
|
||||||
| LeftLimited(domainLimit)
|
|
||||||
| RightLimited(domainLimit)
|
|
||||||
| LeftAndRightLimited(domainLimit, domainLimit);
|
|
||||||
|
|
||||||
type xyShape = {
|
|
||||||
xs: array(float),
|
|
||||||
ys: array(float),
|
|
||||||
};
|
|
||||||
|
|
||||||
type continuousShape = xyShape;
|
|
||||||
type discreteShape = xyShape;
|
|
||||||
|
|
||||||
type mixedShape = {
|
|
||||||
continuous: continuousShape,
|
|
||||||
discrete: discreteShape,
|
|
||||||
discreteProbabilityMassFraction: float,
|
|
||||||
};
|
|
||||||
|
|
||||||
type pointsType =
|
|
||||||
| Mixed(mixedShape)
|
|
||||||
| Discrete(discreteShape)
|
|
||||||
| Continuous(continuousShape);
|
|
||||||
|
|
||||||
type generationSource =
|
|
||||||
| GuesstimatorString(string)
|
|
||||||
| Shape(pointsType);
|
|
||||||
|
|
||||||
type distributionUnit =
|
|
||||||
| UnspecifiedDistribution
|
|
||||||
| TimeDistribution(TimeTypes.timeVector);
|
|
||||||
|
|
||||||
type probabilityType =
|
|
||||||
| Cdf
|
|
||||||
| Pdf
|
|
||||||
| Arbitrary;
|
|
||||||
|
|
||||||
type genericDistribution = {
|
|
||||||
generationSource,
|
|
||||||
probabilityType,
|
|
||||||
domain,
|
|
||||||
unit: distributionUnit,
|
|
||||||
};
|
|
||||||
|
|
||||||
let shape = ({generationSource}: genericDistribution) =>
|
|
||||||
switch (generationSource) {
|
|
||||||
| GuesstimatorString(_) => None
|
|
||||||
| Shape(pointsType) => Some(pointsType)
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,113 +0,0 @@
|
||||||
open DistributionTypes;
|
|
||||||
|
|
||||||
module Domain = {
|
|
||||||
let excludedProbabilityMass = (t: DistributionTypes.domain) => {
|
|
||||||
switch (t) {
|
|
||||||
| Complete => 1.0
|
|
||||||
| LeftLimited({excludingProbabilityMass}) => excludingProbabilityMass
|
|
||||||
| RightLimited({excludingProbabilityMass}) => excludingProbabilityMass
|
|
||||||
| LeftAndRightLimited(
|
|
||||||
{excludingProbabilityMass: l},
|
|
||||||
{excludingProbabilityMass: r},
|
|
||||||
) =>
|
|
||||||
l +. r
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let initialProbabilityMass = (t: DistributionTypes.domain) => {
|
|
||||||
switch (t) {
|
|
||||||
| Complete
|
|
||||||
| RightLimited(_) => 0.0
|
|
||||||
| LeftLimited({excludingProbabilityMass}) => excludingProbabilityMass
|
|
||||||
| LeftAndRightLimited({excludingProbabilityMass}, _) => excludingProbabilityMass
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let normalizeProbabilityMass = (t: DistributionTypes.domain) => {
|
|
||||||
1. /. excludedProbabilityMass(t);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let make =
|
|
||||||
(
|
|
||||||
~generationSource,
|
|
||||||
~probabilityType=Pdf,
|
|
||||||
~domain=Complete,
|
|
||||||
~unit=UnspecifiedDistribution,
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
: genericDistribution => {
|
|
||||||
generationSource,
|
|
||||||
probabilityType,
|
|
||||||
domain,
|
|
||||||
unit,
|
|
||||||
};
|
|
||||||
|
|
||||||
let renderIfNeeded =
|
|
||||||
(~sampleCount=1000, t: genericDistribution): option(genericDistribution) => {
|
|
||||||
switch (t.generationSource) {
|
|
||||||
| GuesstimatorString(s) =>
|
|
||||||
let shape = Guesstimator.stringToMixedShape(~string=s, ~sampleCount, ());
|
|
||||||
let newShape =
|
|
||||||
switch (shape) {
|
|
||||||
| Some({
|
|
||||||
continuous: {xs: [||], ys: [||]},
|
|
||||||
discrete: {xs: [||], ys: [||]},
|
|
||||||
}) =>
|
|
||||||
None
|
|
||||||
| Some({continuous, discrete: {xs: [||], ys: [||]}}) =>
|
|
||||||
Some(Continuous(continuous))
|
|
||||||
| Some({continuous: {xs: [||], ys: [||]}, discrete}) =>
|
|
||||||
Some(Discrete(discrete))
|
|
||||||
| Some(shape) => Some(Mixed(shape))
|
|
||||||
| _ => None
|
|
||||||
};
|
|
||||||
|
|
||||||
newShape
|
|
||||||
|> E.O.fmap((shape: DistributionTypes.pointsType) =>
|
|
||||||
make(
|
|
||||||
~generationSource=Shape(shape),
|
|
||||||
~probabilityType=Cdf,
|
|
||||||
~domain=t.domain,
|
|
||||||
~unit=t.unit,
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
| Shape(_) => Some(t)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let normalize = (t: genericDistribution): option(genericDistribution) => {
|
|
||||||
switch (t.generationSource) {
|
|
||||||
| Shape(shape) =>
|
|
||||||
Shape.PointsType.normalizePdf(shape)
|
|
||||||
|> E.O.fmap(shape => {...t, generationSource: Shape(shape)})
|
|
||||||
| GuesstimatorString(_) => Some(t)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let yIntegral = (t: DistributionTypes.genericDistribution, x) => {
|
|
||||||
let addInitialMass = n => n +. Domain.initialProbabilityMass(t.domain);
|
|
||||||
let normalize = n => n *. Domain.normalizeProbabilityMass(t.domain);
|
|
||||||
switch (t) {
|
|
||||||
| {generationSource: Shape(shape)} =>
|
|
||||||
Shape.PointsType.yIntegral(shape, x)
|
|
||||||
|> E.O.fmap(addInitialMass)
|
|
||||||
|> E.O.fmap(normalize)
|
|
||||||
| _ => None
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: This obviously needs to be fleshed out a lot.
|
|
||||||
let integrate = (t: DistributionTypes.genericDistribution) => {
|
|
||||||
switch (t) {
|
|
||||||
| {probabilityType: Pdf, generationSource: Shape(shape), domain, unit} =>
|
|
||||||
Some({
|
|
||||||
generationSource: Shape(shape),
|
|
||||||
probabilityType: Pdf,
|
|
||||||
domain,
|
|
||||||
unit,
|
|
||||||
})
|
|
||||||
| _ => None
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,355 +0,0 @@
|
||||||
open DistributionTypes;
|
|
||||||
|
|
||||||
let _lastElement = (a: array('a)) =>
|
|
||||||
switch (Belt.Array.size(a)) {
|
|
||||||
| 0 => None
|
|
||||||
| n => Belt.Array.get(a, n - 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
type pointInRange =
|
|
||||||
| Unbounded
|
|
||||||
| X(float);
|
|
||||||
|
|
||||||
module XYShape = {
|
|
||||||
type t = xyShape;
|
|
||||||
|
|
||||||
let toJs = (t: t) => {
|
|
||||||
{"xs": t.xs, "ys": t.ys};
|
|
||||||
};
|
|
||||||
|
|
||||||
let minX = (t: t) => t.xs |> E.A.get(_, 0);
|
|
||||||
// TODO: Check if this actually gets the last element, I'm not sure it does.
|
|
||||||
let maxX = (t: t) => t.xs |> (r => E.A.get(r, E.A.length(r) - 1));
|
|
||||||
|
|
||||||
let zip = t => Belt.Array.zip(t.xs, t.ys);
|
|
||||||
|
|
||||||
let fmap = (t: t, y): t => {xs: t.xs, ys: t.ys |> E.A.fmap(y)};
|
|
||||||
|
|
||||||
let scaleCdfTo = (~scaleTo=1., t: t) =>
|
|
||||||
switch (_lastElement(t.ys)) {
|
|
||||||
| Some(n) =>
|
|
||||||
let scaleBy = scaleTo /. n;
|
|
||||||
fmap(t, r => r *. scaleBy);
|
|
||||||
| None => t
|
|
||||||
};
|
|
||||||
|
|
||||||
let yFold = (fn, t: t) => {
|
|
||||||
E.A.fold_left(fn, 0., t.ys);
|
|
||||||
};
|
|
||||||
|
|
||||||
let ySum = yFold((a, b) => a +. b);
|
|
||||||
|
|
||||||
let fromArray = ((xs, ys)): t => {xs, ys};
|
|
||||||
let fromArrays = (xs, ys): t => {xs, ys};
|
|
||||||
|
|
||||||
let _transverse = fn =>
|
|
||||||
Belt.Array.reduce(_, [||], (items, (x, y)) =>
|
|
||||||
switch (_lastElement(items)) {
|
|
||||||
| Some((xLast, yLast)) =>
|
|
||||||
Belt.Array.concat(items, [|(x, fn(y, yLast))|])
|
|
||||||
| None => [|(x, y)|]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let _transverseShape = (fn, p: t) => {
|
|
||||||
Belt.Array.zip(p.xs, p.ys)
|
|
||||||
|> _transverse(fn)
|
|
||||||
|> Belt.Array.unzip
|
|
||||||
|> fromArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
let accumulateYs = _transverseShape((aCurrent, aLast) => aCurrent +. aLast);
|
|
||||||
let subtractYs = _transverseShape((aCurrent, aLast) => aCurrent -. aLast);
|
|
||||||
|
|
||||||
module Range = {
|
|
||||||
// ((lastX, lastY), (nextX, nextY))
|
|
||||||
type zippedRange = ((float, float), (float, float));
|
|
||||||
|
|
||||||
let floatSum = Belt.Array.reduce(_, 0., (a, b) => a +. b);
|
|
||||||
let toT = r => r |> Belt.Array.unzip |> fromArray;
|
|
||||||
let nextX = ((_, (nextX, _)): zippedRange) => nextX;
|
|
||||||
|
|
||||||
let rangeAreaAssumingSteps =
|
|
||||||
(((lastX, lastY), (nextX, _)): zippedRange) =>
|
|
||||||
(nextX -. lastX) *. lastY;
|
|
||||||
|
|
||||||
let rangeAreaAssumingTriangles =
|
|
||||||
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
|
|
||||||
(nextX -. lastX) *. (lastY +. nextY) /. 2.;
|
|
||||||
|
|
||||||
let delta_y_over_delta_x =
|
|
||||||
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
|
|
||||||
(nextY -. lastY) /. (nextX -. lastX);
|
|
||||||
|
|
||||||
let inRanges = (mapper, reducer, t: t) => {
|
|
||||||
Belt.Array.zip(t.xs, t.ys)
|
|
||||||
|> E.A.toRanges
|
|
||||||
|> E.R.toOption
|
|
||||||
|> E.O.fmap(r => r |> Belt.Array.map(_, mapper) |> reducer);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mapYsBasedOnRanges = fn => inRanges(r => (nextX(r), fn(r)), toT);
|
|
||||||
|
|
||||||
let integrateWithSteps = z =>
|
|
||||||
mapYsBasedOnRanges(rangeAreaAssumingSteps, z) |> E.O.fmap(accumulateYs);
|
|
||||||
|
|
||||||
let integrateWithTriangles = z =>
|
|
||||||
mapYsBasedOnRanges(rangeAreaAssumingTriangles, z)
|
|
||||||
|> E.O.fmap(accumulateYs);
|
|
||||||
|
|
||||||
let derivative = mapYsBasedOnRanges(delta_y_over_delta_x);
|
|
||||||
};
|
|
||||||
|
|
||||||
let findY = CdfLibrary.Distribution.findY;
|
|
||||||
let findX = CdfLibrary.Distribution.findX;
|
|
||||||
};
|
|
||||||
|
|
||||||
module Continuous = {
|
|
||||||
let minX = XYShape.minX;
|
|
||||||
let maxX = XYShape.maxX;
|
|
||||||
let fromArrays = XYShape.fromArrays;
|
|
||||||
let toJs = XYShape.toJs;
|
|
||||||
let toPdf = XYShape.Range.derivative;
|
|
||||||
let toCdf = XYShape.Range.integrateWithTriangles;
|
|
||||||
let findX = CdfLibrary.Distribution.findX;
|
|
||||||
let findY = CdfLibrary.Distribution.findY;
|
|
||||||
let findIntegralY = (f, r) => {
|
|
||||||
r |> toCdf |> E.O.fmap(findY(f));
|
|
||||||
};
|
|
||||||
|
|
||||||
let normalizeCdf = (continuousShape: continuousShape) =>
|
|
||||||
continuousShape |> XYShape.scaleCdfTo(~scaleTo=1.0);
|
|
||||||
|
|
||||||
let scalePdf = (~scaleTo=1.0, continuousShape: continuousShape) =>
|
|
||||||
continuousShape
|
|
||||||
|> toCdf
|
|
||||||
|> E.O.fmap(XYShape.scaleCdfTo(~scaleTo))
|
|
||||||
|> E.O.bind(_, toPdf);
|
|
||||||
};
|
|
||||||
|
|
||||||
module Discrete = {
|
|
||||||
let minX = XYShape.minX;
|
|
||||||
let maxX = XYShape.maxX;
|
|
||||||
type t = discreteShape;
|
|
||||||
let fromArrays = XYShape.fromArrays;
|
|
||||||
let toJs = XYShape.toJs;
|
|
||||||
let ySum = XYShape.ySum;
|
|
||||||
let zip = t => Belt.Array.zip(t.xs, t.ys);
|
|
||||||
|
|
||||||
let scaleYToTotal = (totalDesired, t: t): t => {
|
|
||||||
let difference = totalDesired /. ySum(t);
|
|
||||||
XYShape.fmap(t, y => y *. difference);
|
|
||||||
};
|
|
||||||
|
|
||||||
let render = (t: t) =>
|
|
||||||
Belt.Array.zip(t.xs, t.ys)
|
|
||||||
|> E.A.fmap(((x, y)) =>
|
|
||||||
<div>
|
|
||||||
{E.Float.toFixed(x)
|
|
||||||
++ "---"
|
|
||||||
++ E.Float.with3DigitsPrecision(y *. 100.)
|
|
||||||
|> ReasonReact.string}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|> ReasonReact.array;
|
|
||||||
|
|
||||||
let integrate = XYShape.accumulateYs;
|
|
||||||
let derivative = XYShape.subtractYs;
|
|
||||||
|
|
||||||
// TODO: This has a clear bug where it returns the Y value of the first item,
|
|
||||||
// even if X is less than the X of the first item.
|
|
||||||
// It has a second bug that it assumes things are triangular, instead of interpolating via steps.
|
|
||||||
let findIntegralY = (f, t: t) => {
|
|
||||||
t |> XYShape.accumulateYs |> CdfLibrary.Distribution.findY(f);
|
|
||||||
};
|
|
||||||
|
|
||||||
let findY = (f, t: t) => {
|
|
||||||
Belt.Array.zip(t.xs, t.ys)
|
|
||||||
|> E.A.getBy(_, ((x, _)) => x == f)
|
|
||||||
|> E.O.fmap(((_, y)) => y)
|
|
||||||
|> E.O.default(0.);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let min = (f1: option(float), f2: option(float)) =>
|
|
||||||
switch (f1, f2) {
|
|
||||||
| (Some(f1), Some(f2)) => Some(f1 < f2 ? f1 : f2)
|
|
||||||
| (Some(f1), None) => Some(f1)
|
|
||||||
| (None, Some(f2)) => Some(f2)
|
|
||||||
| (None, None) => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let max = (f1: option(float), f2: option(float)) =>
|
|
||||||
switch (f1, f2) {
|
|
||||||
| (Some(f1), Some(f2)) => Some(f1 > f2 ? f1 : f2)
|
|
||||||
| (Some(f1), None) => Some(f1)
|
|
||||||
| (None, Some(f2)) => Some(f2)
|
|
||||||
| (None, None) => None
|
|
||||||
};
|
|
||||||
|
|
||||||
module Mixed = {
|
|
||||||
let make = (~continuous, ~discrete, ~discreteProbabilityMassFraction) => {
|
|
||||||
continuous,
|
|
||||||
discrete,
|
|
||||||
discreteProbabilityMassFraction,
|
|
||||||
};
|
|
||||||
|
|
||||||
let minX = (t: DistributionTypes.mixedShape) =>
|
|
||||||
min(t.continuous |> Continuous.minX, t.discrete |> Discrete.minX);
|
|
||||||
|
|
||||||
let maxX = (t: DistributionTypes.mixedShape) => {
|
|
||||||
max(t.continuous |> Continuous.maxX, t.discrete |> Discrete.maxX);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mixedMultiply =
|
|
||||||
(
|
|
||||||
t: DistributionTypes.mixedShape,
|
|
||||||
continuousComponent,
|
|
||||||
discreteComponent,
|
|
||||||
) => {
|
|
||||||
let diffFn = t.discreteProbabilityMassFraction;
|
|
||||||
continuousComponent *. (1.0 -. diffFn) +. discreteComponent *. diffFn;
|
|
||||||
};
|
|
||||||
|
|
||||||
type yPdfPoint = {
|
|
||||||
continuous: option(float),
|
|
||||||
discrete: option(float),
|
|
||||||
discreteProbabilityMassFraction: float,
|
|
||||||
};
|
|
||||||
|
|
||||||
let findY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => {
|
|
||||||
continuous: Continuous.findY(x, t.continuous) |> E.O.some,
|
|
||||||
discrete: Discrete.findY(x, t.discrete) |> E.O.some,
|
|
||||||
discreteProbabilityMassFraction: t.discreteProbabilityMassFraction,
|
|
||||||
};
|
|
||||||
|
|
||||||
let findYIntegral =
|
|
||||||
(x: float, t: DistributionTypes.mixedShape): option(float) => {
|
|
||||||
let c = t.continuous |> Continuous.findIntegralY(x);
|
|
||||||
let d = Discrete.findIntegralY(x, t.discrete);
|
|
||||||
switch (c, d) {
|
|
||||||
| (Some(c), d) => Some(mixedMultiply(t, c, d))
|
|
||||||
| _ => None
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module PointsType = {
|
|
||||||
type t = DistributionTypes.pointsType;
|
|
||||||
|
|
||||||
let y = (t: t, x: float) =>
|
|
||||||
switch (t) {
|
|
||||||
| Mixed(m) => `mixed(Mixed.findY(m, x))
|
|
||||||
| Discrete(discreteShape) => `discrete(Discrete.findY(x, discreteShape))
|
|
||||||
| Continuous(continuousShape) =>
|
|
||||||
`continuous(Continuous.findY(x, continuousShape))
|
|
||||||
};
|
|
||||||
|
|
||||||
let yIntegral = (t: t, x: float) =>
|
|
||||||
switch (t) {
|
|
||||||
| Mixed(m) => Mixed.findYIntegral(x, m)
|
|
||||||
| Discrete(discreteShape) =>
|
|
||||||
Discrete.findIntegralY(x, discreteShape) |> E.O.some
|
|
||||||
| Continuous(continuousShape) =>
|
|
||||||
Continuous.findIntegralY(x, continuousShape)
|
|
||||||
};
|
|
||||||
|
|
||||||
let minX = (t: t) =>
|
|
||||||
switch (t) {
|
|
||||||
| Mixed(m) => Mixed.minX(m)
|
|
||||||
| Discrete(discreteShape) => Discrete.minX(discreteShape)
|
|
||||||
| Continuous(continuousShape) => Continuous.minX(continuousShape)
|
|
||||||
};
|
|
||||||
|
|
||||||
let maxX = (t: t) =>
|
|
||||||
switch (t) {
|
|
||||||
| Mixed(m) => Mixed.maxX(m)
|
|
||||||
| Discrete(discreteShape) => Discrete.maxX(discreteShape)
|
|
||||||
| Continuous(continuousShape) => Continuous.maxX(continuousShape)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: This is wrong. The discrete component should be made continuous when integrating.
|
|
||||||
let pdfToCdf = (t: t) =>
|
|
||||||
switch (t) {
|
|
||||||
| Mixed({continuous, discrete, discreteProbabilityMassFraction}) =>
|
|
||||||
Some(
|
|
||||||
Mixed({
|
|
||||||
continuous: Continuous.toCdf(continuous) |> E.O.toExt(""),
|
|
||||||
discrete: discrete |> Discrete.integrate,
|
|
||||||
discreteProbabilityMassFraction,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
| Discrete(discrete) => Some(Continuous(discrete |> Discrete.integrate))
|
|
||||||
| Continuous(continuous) =>
|
|
||||||
Continuous.toCdf(continuous) |> E.O.fmap(e => Continuous(e))
|
|
||||||
};
|
|
||||||
|
|
||||||
let discreteComponent = (t: t) =>
|
|
||||||
switch (t) {
|
|
||||||
| Mixed({discrete}) => Some(discrete)
|
|
||||||
| Discrete(d) => Some(d)
|
|
||||||
| Continuous(_) => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let continuousComponent = (t: t) =>
|
|
||||||
switch (t) {
|
|
||||||
| Mixed({continuous}) => Some(continuous)
|
|
||||||
| Continuous(c) => Some(c)
|
|
||||||
| Discrete(_) => None
|
|
||||||
};
|
|
||||||
|
|
||||||
let scaledContinuousComponent = (t: t): option(continuousShape) => {
|
|
||||||
switch (t) {
|
|
||||||
| Mixed({continuous, discreteProbabilityMassFraction}) =>
|
|
||||||
Continuous.scalePdf(
|
|
||||||
~scaleTo=1.0 -. discreteProbabilityMassFraction,
|
|
||||||
continuous,
|
|
||||||
)
|
|
||||||
| Discrete(_) => None
|
|
||||||
| Continuous(c) => Some(c)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let scaledDiscreteComponent = (t: t): option(discreteShape) => {
|
|
||||||
switch (t) {
|
|
||||||
| Mixed({discrete, discreteProbabilityMassFraction}) =>
|
|
||||||
Some(Discrete.scaleYToTotal(discreteProbabilityMassFraction, discrete))
|
|
||||||
| Discrete(d) => Some(d)
|
|
||||||
| Continuous(_) => None
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let normalizeCdf = (t: DistributionTypes.pointsType) => {
|
|
||||||
switch (t) {
|
|
||||||
| Mixed({continuous, discrete, discreteProbabilityMassFraction}) =>
|
|
||||||
Mixed({
|
|
||||||
continuous: continuous |> Continuous.normalizeCdf,
|
|
||||||
discrete: discrete |> Discrete.scaleYToTotal(1.0),
|
|
||||||
discreteProbabilityMassFraction,
|
|
||||||
})
|
|
||||||
| Discrete(d) => Discrete(d |> Discrete.scaleYToTotal(1.0))
|
|
||||||
| Continuous(continuousShape) =>
|
|
||||||
Continuous(Continuous.normalizeCdf(continuousShape))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let normalizePdf = (t: DistributionTypes.pointsType) => {
|
|
||||||
switch (t) {
|
|
||||||
| Mixed({continuous, discrete, discreteProbabilityMassFraction}) =>
|
|
||||||
continuous
|
|
||||||
|> Continuous.scalePdf(~scaleTo=1.0)
|
|
||||||
|> E.O.fmap(r =>
|
|
||||||
Mixed({
|
|
||||||
continuous: r,
|
|
||||||
discrete: discrete |> Discrete.scaleYToTotal(1.0),
|
|
||||||
discreteProbabilityMassFraction,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
| Discrete(d) => Some(Discrete(d |> Discrete.scaleYToTotal(1.0)))
|
|
||||||
| Continuous(continuousShape) =>
|
|
||||||
continuousShape
|
|
||||||
|> Continuous.scalePdf(~scaleTo=1.0)
|
|
||||||
|> E.O.fmap(r => Continuous(r))
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
36
src/distributions/DistPlusIngredients.re
Normal file
36
src/distributions/DistPlusIngredients.re
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
open DistTypes;
|
||||||
|
|
||||||
|
let make =
|
||||||
|
(~guesstimatorString, ~domain=Complete, ~unit=UnspecifiedDistribution, ())
|
||||||
|
: distPlusIngredients => {
|
||||||
|
guesstimatorString,
|
||||||
|
domain,
|
||||||
|
unit,
|
||||||
|
};
|
||||||
|
|
||||||
|
let toDistPlus =
|
||||||
|
(~sampleCount=1000, ~outputXYPoints=1000, t: distPlusIngredients)
|
||||||
|
: option(distPlus) => {
|
||||||
|
let shape =
|
||||||
|
Guesstimator.stringToMixedShape(
|
||||||
|
~string=t.guesstimatorString,
|
||||||
|
~sampleCount,
|
||||||
|
~outputXYPoints,
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
let distPlus =
|
||||||
|
shape
|
||||||
|
|> E.O.fmap(
|
||||||
|
Distributions.DistPlus.make(
|
||||||
|
~shape=_,
|
||||||
|
~domain=t.domain,
|
||||||
|
~unit=t.unit,
|
||||||
|
~guesstimatorString=None,
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|> E.O.fmap(
|
||||||
|
Distributions.DistPlus.T.scaleToIntegralSum(~intendedSum=1.0),
|
||||||
|
);
|
||||||
|
distPlus;
|
||||||
|
};
|
134
src/distributions/DistTypes.re
Normal file
134
src/distributions/DistTypes.re
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
type domainLimit = {
|
||||||
|
xPoint: float,
|
||||||
|
excludingProbabilityMass: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
type domain =
|
||||||
|
| Complete
|
||||||
|
| LeftLimited(domainLimit)
|
||||||
|
| RightLimited(domainLimit)
|
||||||
|
| LeftAndRightLimited(domainLimit, domainLimit);
|
||||||
|
|
||||||
|
type xyShape = {
|
||||||
|
xs: array(float),
|
||||||
|
ys: array(float),
|
||||||
|
};
|
||||||
|
|
||||||
|
type continuousShape = {
|
||||||
|
xyShape,
|
||||||
|
interpolation: [ | `Stepwise | `Linear],
|
||||||
|
};
|
||||||
|
|
||||||
|
type discreteShape = xyShape;
|
||||||
|
|
||||||
|
type mixedShape = {
|
||||||
|
continuous: continuousShape,
|
||||||
|
discrete: discreteShape,
|
||||||
|
discreteProbabilityMassFraction: float,
|
||||||
|
};
|
||||||
|
|
||||||
|
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 =
|
||||||
|
| GuesstimatorString(string)
|
||||||
|
| Shape(shape);
|
||||||
|
|
||||||
|
type distributionUnit =
|
||||||
|
| UnspecifiedDistribution
|
||||||
|
| TimeDistribution(TimeTypes.timeVector);
|
||||||
|
|
||||||
|
type distPlus = {
|
||||||
|
shape,
|
||||||
|
domain,
|
||||||
|
integralCache: continuousShape,
|
||||||
|
unit: distributionUnit,
|
||||||
|
guesstimatorString: option(string),
|
||||||
|
};
|
||||||
|
|
||||||
|
type distPlusIngredients = {
|
||||||
|
guesstimatorString: string,
|
||||||
|
domain,
|
||||||
|
unit: distributionUnit,
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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, 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);
|
||||||
|
};
|
554
src/distributions/Distributions.re
Normal file
554
src/distributions/Distributions.re
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
let min = (f1: option(float), f2: option(float)) =>
|
||||||
|
switch (f1, f2) {
|
||||||
|
| (Some(f1), Some(f2)) => Some(f1 < f2 ? f1 : f2)
|
||||||
|
| (Some(f1), None) => Some(f1)
|
||||||
|
| (None, Some(f2)) => Some(f2)
|
||||||
|
| (None, None) => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let max = (f1: option(float), f2: option(float)) =>
|
||||||
|
switch (f1, f2) {
|
||||||
|
| (Some(f1), Some(f2)) => Some(f1 > f2 ? f1 : f2)
|
||||||
|
| (Some(f1), None) => Some(f1)
|
||||||
|
| (None, Some(f2)) => Some(f2)
|
||||||
|
| (None, None) => None
|
||||||
|
};
|
||||||
|
|
||||||
|
module type dist = {
|
||||||
|
type t;
|
||||||
|
let minX: t => option(float);
|
||||||
|
let maxX: t => option(float);
|
||||||
|
let pointwiseFmap: (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 toScaledContinuous: t => option(DistTypes.continuousShape);
|
||||||
|
let toScaledDiscrete: t => option(DistTypes.discreteShape);
|
||||||
|
|
||||||
|
type integral;
|
||||||
|
let integral: (~cache: option(integral), t) => integral;
|
||||||
|
let integralEndY: (~cache: option(integral), t) => float;
|
||||||
|
let integralXtoY: (~cache: option(integral), float, t) => float;
|
||||||
|
};
|
||||||
|
|
||||||
|
module Dist = (T: dist) => {
|
||||||
|
type t = T.t;
|
||||||
|
type integral = T.integral;
|
||||||
|
let minX = T.minX;
|
||||||
|
let maxX = T.maxX;
|
||||||
|
let xTotalRange = (t: t) =>
|
||||||
|
switch (minX(t), maxX(t)) {
|
||||||
|
| (Some(min), Some(max)) => Some(max -. min)
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
let pointwiseFmap = T.pointwiseFmap;
|
||||||
|
let xToY = T.xToY;
|
||||||
|
let toShape = T.toShape;
|
||||||
|
let toContinuous = T.toContinuous;
|
||||||
|
let toDiscrete = T.toDiscrete;
|
||||||
|
let toScaledContinuous = T.toScaledContinuous;
|
||||||
|
let toScaledDiscrete = T.toScaledDiscrete;
|
||||||
|
let scaleBy = (~scale=1.0, t: t) =>
|
||||||
|
t |> pointwiseFmap((r: float) => r *. scale);
|
||||||
|
|
||||||
|
module Integral = {
|
||||||
|
type t = T.integral;
|
||||||
|
let get = T.integral;
|
||||||
|
let xToY = T.integralXtoY;
|
||||||
|
let sum = T.integralEndY;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is suboptimal because it could get the cache but doesn't here.
|
||||||
|
let scaleToIntegralSum = (~intendedSum=1.0, t: t) => {
|
||||||
|
let scale = intendedSum /. Integral.sum(~cache=None, t);
|
||||||
|
scaleBy(~scale, t);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module Continuous = {
|
||||||
|
type t = DistTypes.continuousShape;
|
||||||
|
let xyShape = (t: t) => t.xyShape;
|
||||||
|
let getShape = (t: t) => t.xyShape;
|
||||||
|
let interpolation = (t: t) => t.interpolation;
|
||||||
|
let make = (xyShape, interpolation): t => {xyShape, interpolation};
|
||||||
|
let fromShape = xyShape => make(xyShape, `Linear);
|
||||||
|
let shapeMap = (fn, {xyShape, interpolation}: t): t => {
|
||||||
|
xyShape: fn(xyShape),
|
||||||
|
interpolation,
|
||||||
|
};
|
||||||
|
let lastY = (t: t) =>
|
||||||
|
t |> xyShape |> XYShape.unsafeLast |> (((_, y)) => y);
|
||||||
|
let oShapeMap =
|
||||||
|
(fn, {xyShape, interpolation}: t): option(DistTypes.continuousShape) =>
|
||||||
|
fn(xyShape) |> E.O.fmap(make(_, interpolation));
|
||||||
|
|
||||||
|
let toLinear = (t: t): option(t) => {
|
||||||
|
switch (t) {
|
||||||
|
| {interpolation: `Stepwise, xyShape} =>
|
||||||
|
xyShape
|
||||||
|
|> XYShape.Range.stepsToContinuous
|
||||||
|
|> E.O.fmap(xyShape => make(xyShape, `Linear))
|
||||||
|
| {interpolation: `Linear, _} => Some(t)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let shapeFn = (fn, t: t) => t |> xyShape |> fn;
|
||||||
|
|
||||||
|
let convertToNewLength = i =>
|
||||||
|
shapeMap(CdfLibrary.Distribution.convertToNewLength(i));
|
||||||
|
|
||||||
|
module T =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.continuousShape;
|
||||||
|
type integral = DistTypes.continuousShape;
|
||||||
|
let minX = shapeFn(XYShape.minX);
|
||||||
|
let maxX = shapeFn(XYShape.maxX);
|
||||||
|
let pointwiseFmap = (fn, t: t) =>
|
||||||
|
t |> xyShape |> XYShape.pointwiseMap(fn) |> fromShape;
|
||||||
|
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)
|
||||||
|
|> DistTypes.MixedPoint.makeContinuous
|
||||||
|
| `Linear =>
|
||||||
|
xyShape
|
||||||
|
|> XYShape.XtoY.linear(f)
|
||||||
|
|> DistTypes.MixedPoint.makeContinuous
|
||||||
|
};
|
||||||
|
|
||||||
|
// let combineWithFn = (t1: t, t2: t, fn: (float, float) => float) => {
|
||||||
|
// switch(t1, t2){
|
||||||
|
// | ({interpolation: `Stepwise}, {interpolation: `Stepwise}) => 3.0
|
||||||
|
// | ({interpolation: `Linear}, {interpolation: `Linear}) => 3.0
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
let integral = (~cache, t) =>
|
||||||
|
cache
|
||||||
|
|> E.O.default(
|
||||||
|
t
|
||||||
|
|> xyShape
|
||||||
|
|> XYShape.Range.integrateWithTriangles
|
||||||
|
|> E.O.toExt("This should not have happened")
|
||||||
|
|> fromShape,
|
||||||
|
);
|
||||||
|
let integralEndY = (~cache, t) => t |> integral(~cache) |> lastY;
|
||||||
|
let integralXtoY = (~cache, f, t) =>
|
||||||
|
t |> integral(~cache) |> shapeFn(CdfLibrary.Distribution.findY(f));
|
||||||
|
let toContinuous = t => Some(t);
|
||||||
|
let toDiscrete = _ => None;
|
||||||
|
let toScaledContinuous = t => Some(t);
|
||||||
|
let toScaledDiscrete = _ => None;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module Discrete = {
|
||||||
|
module T =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.discreteShape;
|
||||||
|
type integral = DistTypes.continuousShape;
|
||||||
|
let integral = (~cache, t) =>
|
||||||
|
cache
|
||||||
|
|> E.O.default(Continuous.make(XYShape.accumulateYs(t), `Stepwise));
|
||||||
|
let integralEndY = (~cache, t) =>
|
||||||
|
t |> integral(~cache) |> Continuous.lastY;
|
||||||
|
let minX = XYShape.minX;
|
||||||
|
let maxX = XYShape.maxX;
|
||||||
|
let pointwiseFmap = XYShape.pointwiseMap;
|
||||||
|
let toShape = (t: t): DistTypes.shape => Discrete(t);
|
||||||
|
let toContinuous = _ => None;
|
||||||
|
let toDiscrete = t => Some(t);
|
||||||
|
let toScaledContinuous = _ => None;
|
||||||
|
let toScaledDiscrete = t => Some(t);
|
||||||
|
|
||||||
|
let xToY = (f, t) => {
|
||||||
|
XYShape.XtoY.stepwiseIfAtX(f, t)
|
||||||
|
|> E.O.default(0.0)
|
||||||
|
|> DistTypes.MixedPoint.makeDiscrete;
|
||||||
|
};
|
||||||
|
// todo: This should use cache and/or same code as above. FindingY is more complex, should use interpolationType.
|
||||||
|
let integralXtoY = (~cache, f, t) =>
|
||||||
|
t
|
||||||
|
|> integral(~cache)
|
||||||
|
|> Continuous.getShape
|
||||||
|
|> CdfLibrary.Distribution.findY(f);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module Mixed = {
|
||||||
|
type t = DistTypes.mixedShape;
|
||||||
|
let make =
|
||||||
|
(~continuous, ~discrete, ~discreteProbabilityMassFraction)
|
||||||
|
: DistTypes.mixedShape => {
|
||||||
|
continuous,
|
||||||
|
discrete,
|
||||||
|
discreteProbabilityMassFraction,
|
||||||
|
};
|
||||||
|
|
||||||
|
let clean = (t: DistTypes.mixedShape): option(DistTypes.shape) => {
|
||||||
|
switch (t) {
|
||||||
|
| {
|
||||||
|
continuous: {xyShape: {xs: [||], ys: [||]}},
|
||||||
|
discrete: {xs: [||], ys: [||]},
|
||||||
|
} =>
|
||||||
|
None
|
||||||
|
| {continuous, discrete: {xs: [||], ys: [||]}} =>
|
||||||
|
Some(Continuous(continuous))
|
||||||
|
| {continuous: {xyShape: {xs: [||], ys: [||]}}, discrete} =>
|
||||||
|
Some(Discrete(discrete))
|
||||||
|
| shape => Some(Mixed(shape))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo: Put into scaling module
|
||||||
|
let scaleDiscreteFn =
|
||||||
|
({discreteProbabilityMassFraction}: DistTypes.mixedShape, f) =>
|
||||||
|
f *. discreteProbabilityMassFraction;
|
||||||
|
|
||||||
|
let scaleContinuousFn =
|
||||||
|
({discreteProbabilityMassFraction}: DistTypes.mixedShape, f) =>
|
||||||
|
f *. (1.0 -. discreteProbabilityMassFraction);
|
||||||
|
|
||||||
|
let scaleContinuous = ({discreteProbabilityMassFraction}: t, continuous) =>
|
||||||
|
continuous
|
||||||
|
|> Continuous.T.scaleBy(~scale=1.0 -. discreteProbabilityMassFraction);
|
||||||
|
|
||||||
|
let scaleDiscrete = ({discreteProbabilityMassFraction}: t, disrete) =>
|
||||||
|
disrete |> Discrete.T.scaleBy(~scale=discreteProbabilityMassFraction);
|
||||||
|
|
||||||
|
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 toContinuous = ({continuous}: t) => Some(continuous);
|
||||||
|
let toDiscrete = ({discrete}: t) => Some(discrete);
|
||||||
|
let xToY = (f, {discrete, continuous} as t: t) => {
|
||||||
|
let c =
|
||||||
|
continuous
|
||||||
|
|> Continuous.T.xToY(f)
|
||||||
|
|> DistTypes.MixedPoint.fmap(scaleContinuousFn(t));
|
||||||
|
let d =
|
||||||
|
discrete
|
||||||
|
|> Discrete.T.xToY(f)
|
||||||
|
|> DistTypes.MixedPoint.fmap(scaleDiscreteFn(t));
|
||||||
|
DistTypes.MixedPoint.add(c, d);
|
||||||
|
};
|
||||||
|
|
||||||
|
let toScaledContinuous = ({continuous} as t: t) =>
|
||||||
|
Some(scaleContinuous(t, continuous));
|
||||||
|
|
||||||
|
let toScaledDiscrete = ({discrete} as t: t) =>
|
||||||
|
Some(scaleDiscrete(t, discrete));
|
||||||
|
|
||||||
|
let integral =
|
||||||
|
(
|
||||||
|
~cache,
|
||||||
|
{continuous, discrete, discreteProbabilityMassFraction} as t: t,
|
||||||
|
) => {
|
||||||
|
cache
|
||||||
|
|> E.O.default(
|
||||||
|
{
|
||||||
|
let cont =
|
||||||
|
continuous
|
||||||
|
|> Continuous.T.Integral.get(~cache=None)
|
||||||
|
|> scaleContinuous(t);
|
||||||
|
let dist =
|
||||||
|
discrete
|
||||||
|
|> Discrete.T.Integral.get(~cache=None)
|
||||||
|
|> Continuous.toLinear
|
||||||
|
|> E.O.toExn("")
|
||||||
|
|> Continuous.T.scaleBy(
|
||||||
|
~scale=discreteProbabilityMassFraction,
|
||||||
|
);
|
||||||
|
Continuous.make(
|
||||||
|
XYShape.Combine.combineLinear(
|
||||||
|
Continuous.getShape(cont),
|
||||||
|
Continuous.getShape(dist),
|
||||||
|
(a, b) =>
|
||||||
|
a +. b
|
||||||
|
),
|
||||||
|
`Linear,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let integralEndY = (~cache, t: t) => {
|
||||||
|
integral(~cache, t) |> Continuous.lastY;
|
||||||
|
};
|
||||||
|
|
||||||
|
let integralXtoY = (~cache, f, {discrete, continuous} as t: t) => {
|
||||||
|
let cont = Continuous.T.Integral.xToY(~cache, f, continuous);
|
||||||
|
let discrete = Discrete.T.Integral.xToY(~cache, f, discrete);
|
||||||
|
scaleDiscreteFn(t, discrete) +. scaleContinuousFn(t, cont);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: This functionality is kinda weird, because it seems to assume the cdf adds to 1.0 elsewhere, which wouldn't happen here.
|
||||||
|
let pointwiseFmap =
|
||||||
|
(fn, {discrete, continuous, discreteProbabilityMassFraction}: t): t => {
|
||||||
|
{
|
||||||
|
discrete: Discrete.T.pointwiseFmap(fn, discrete),
|
||||||
|
continuous: Continuous.T.pointwiseFmap(fn, continuous),
|
||||||
|
discreteProbabilityMassFraction,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module Shape = {
|
||||||
|
module T =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.shape;
|
||||||
|
type integral = DistTypes.continuousShape;
|
||||||
|
|
||||||
|
// todo: change order of arguments so t goes last.
|
||||||
|
// todo: Think of other name here?
|
||||||
|
let mapToAll = (t: t, (fn1, fn2, fn3)) =>
|
||||||
|
switch (t) {
|
||||||
|
| Mixed(m) => fn1(m)
|
||||||
|
| Discrete(m) => fn2(m)
|
||||||
|
| Continuous(m) => fn3(m)
|
||||||
|
};
|
||||||
|
|
||||||
|
let fmap = (t: t, (fn1, fn2, fn3)): t =>
|
||||||
|
switch (t) {
|
||||||
|
| Mixed(m) => Mixed(fn1(m))
|
||||||
|
| Discrete(m) => Discrete(fn2(m))
|
||||||
|
| Continuous(m) => Continuous(fn3(m))
|
||||||
|
};
|
||||||
|
|
||||||
|
let xToY = (f, t) =>
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(Mixed.T.xToY(f), Discrete.T.xToY(f), Continuous.T.xToY(f)),
|
||||||
|
);
|
||||||
|
let toShape = (t: t) => t;
|
||||||
|
let toContinuous = (t: t) =>
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.toContinuous,
|
||||||
|
Discrete.T.toContinuous,
|
||||||
|
Continuous.T.toContinuous,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let toDiscrete = (t: t) =>
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.toDiscrete,
|
||||||
|
Discrete.T.toDiscrete,
|
||||||
|
Continuous.T.toDiscrete,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let toScaledDiscrete = (t: t) =>
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.toScaledDiscrete,
|
||||||
|
Discrete.T.toScaledDiscrete,
|
||||||
|
Continuous.T.toScaledDiscrete,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let toScaledContinuous = (t: t) =>
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.toScaledContinuous,
|
||||||
|
Discrete.T.toScaledContinuous,
|
||||||
|
Continuous.T.toScaledContinuous,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let minX = (t: t) =>
|
||||||
|
mapToAll(t, (Mixed.T.minX, Discrete.T.minX, Continuous.T.minX));
|
||||||
|
let integral = (~cache, t: t) => {
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.Integral.get(~cache),
|
||||||
|
Discrete.T.Integral.get(~cache),
|
||||||
|
Continuous.T.Integral.get(~cache),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let integralEndY = (~cache, t: t) =>
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.Integral.sum(~cache),
|
||||||
|
Discrete.T.Integral.sum(~cache),
|
||||||
|
Continuous.T.Integral.sum(~cache),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let integralXtoY = (~cache, f, t) => {
|
||||||
|
mapToAll(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.Integral.xToY(~cache, f),
|
||||||
|
Discrete.T.Integral.xToY(~cache, f),
|
||||||
|
Continuous.T.Integral.xToY(~cache, f),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let maxX = (t: t) =>
|
||||||
|
mapToAll(t, (Mixed.T.maxX, Discrete.T.maxX, Continuous.T.maxX));
|
||||||
|
let pointwiseFmap = (fn, t: t) =>
|
||||||
|
fmap(
|
||||||
|
t,
|
||||||
|
(
|
||||||
|
Mixed.T.pointwiseFmap(fn),
|
||||||
|
Discrete.T.pointwiseFmap(fn),
|
||||||
|
Continuous.T.pointwiseFmap(fn),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module DistPlus = {
|
||||||
|
open DistTypes;
|
||||||
|
|
||||||
|
type t = DistTypes.distPlus;
|
||||||
|
|
||||||
|
let shapeIntegral = shape => Shape.T.Integral.get(~cache=None, shape);
|
||||||
|
let make =
|
||||||
|
(
|
||||||
|
~shape,
|
||||||
|
~guesstimatorString,
|
||||||
|
~domain=Complete,
|
||||||
|
~unit=UnspecifiedDistribution,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
: t => {
|
||||||
|
let integral = shapeIntegral(shape);
|
||||||
|
{shape, domain, integralCache: integral, unit, guesstimatorString};
|
||||||
|
};
|
||||||
|
|
||||||
|
let update =
|
||||||
|
(
|
||||||
|
~shape=?,
|
||||||
|
~integralCache=?,
|
||||||
|
~domain=?,
|
||||||
|
~unit=?,
|
||||||
|
~guesstimatorString=?,
|
||||||
|
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),
|
||||||
|
guesstimatorString: E.O.default(t.guesstimatorString, guesstimatorString),
|
||||||
|
};
|
||||||
|
|
||||||
|
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 =
|
||||||
|
Dist({
|
||||||
|
type t = DistTypes.distPlus;
|
||||||
|
type integral = DistTypes.distPlus;
|
||||||
|
let toShape = toShape;
|
||||||
|
let toContinuous = shapeFn(Shape.T.toContinuous);
|
||||||
|
let toDiscrete = shapeFn(Shape.T.toDiscrete);
|
||||||
|
// todo: Adjust for total mass.
|
||||||
|
|
||||||
|
let toScaledContinuous = (t: t) => {
|
||||||
|
t
|
||||||
|
|> toShape
|
||||||
|
|> Shape.T.toScaledContinuous
|
||||||
|
|> E.O.fmap(
|
||||||
|
Continuous.T.pointwiseFmap(
|
||||||
|
domainIncludedProbabilityMassAdjustment(t),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let toScaledDiscrete = (t: t) => {
|
||||||
|
t
|
||||||
|
|> toShape
|
||||||
|
|> Shape.T.toScaledDiscrete
|
||||||
|
|> E.O.fmap(
|
||||||
|
Discrete.T.pointwiseFmap(
|
||||||
|
domainIncludedProbabilityMassAdjustment(t),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 fromShape = (t, shape): t => update(~shape, t);
|
||||||
|
|
||||||
|
// This bit is kind of akward, could probably use rethinking.
|
||||||
|
let integral = (~cache, t: t) =>
|
||||||
|
updateShape(Continuous(t.integralCache), t);
|
||||||
|
|
||||||
|
// todo: adjust for limit, maybe?
|
||||||
|
let pointwiseFmap = (fn, {shape, _} as t: t): t =>
|
||||||
|
Shape.T.pointwiseFmap(fn, shape) |> updateShape(_, t);
|
||||||
|
|
||||||
|
let integralEndY = (~cache as _, t: t) =>
|
||||||
|
Shape.T.Integral.sum(~cache=Some(t.integralCache), toShape(t));
|
||||||
|
|
||||||
|
// TODO: Fix this below, obviously. Adjust for limits
|
||||||
|
let integralXtoY = (~cache as _, f, t: t) => {
|
||||||
|
Shape.T.Integral.xToY(~cache=Some(t.integralCache), f, toShape(t));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module DistPlusTime = {
|
||||||
|
open DistTypes;
|
||||||
|
open DistPlus;
|
||||||
|
|
||||||
|
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 = (~cache as _, f: TimeTypes.timeInVector, t: t) => {
|
||||||
|
timeInVectorToX(f, t)
|
||||||
|
|> E.O.fmap(x => DistPlus.T.Integral.xToY(~cache=None, x, t));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -8,6 +8,29 @@ type assumptions = {
|
||||||
discreteProbabilityMass: option(float),
|
discreteProbabilityMass: option(float),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let buildSimple = (~continuous, ~discrete): option(DistTypes.shape) => {
|
||||||
|
let cLength =
|
||||||
|
continuous |> Distributions.Continuous.getShape |> XYShape.xs |> E.A.length;
|
||||||
|
let dLength = discrete |> XYShape.xs |> E.A.length;
|
||||||
|
switch (cLength, dLength) {
|
||||||
|
| (0 | 1, 0) => None
|
||||||
|
| (0 | 1, _) => Some(Discrete(discrete))
|
||||||
|
| (_, 0) => Some(Continuous(continuous))
|
||||||
|
| (_, _) =>
|
||||||
|
let discreteProbabilityMassFraction =
|
||||||
|
Distributions.Discrete.T.Integral.sum(~cache=None, discrete);
|
||||||
|
let discrete =
|
||||||
|
Distributions.Discrete.T.scaleToIntegralSum(~intendedSum=1.0, discrete);
|
||||||
|
let foobar =
|
||||||
|
Distributions.Mixed.make(
|
||||||
|
~continuous,
|
||||||
|
~discrete,
|
||||||
|
~discreteProbabilityMassFraction,
|
||||||
|
)
|
||||||
|
|> Distributions.Mixed.clean;
|
||||||
|
foobar;
|
||||||
|
};
|
||||||
|
};
|
||||||
let build = (~continuous, ~discrete, ~assumptions) =>
|
let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
switch (assumptions) {
|
switch (assumptions) {
|
||||||
| {
|
| {
|
||||||
|
@ -17,7 +40,7 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
} =>
|
} =>
|
||||||
// TODO: Fix this, it's wrong :(
|
// TODO: Fix this, it's wrong :(
|
||||||
Some(
|
Some(
|
||||||
Shape.Mixed.make(
|
Distributions.Mixed.make(
|
||||||
~continuous,
|
~continuous,
|
||||||
~discrete,
|
~discrete,
|
||||||
~discreteProbabilityMassFraction=r,
|
~discreteProbabilityMassFraction=r,
|
||||||
|
@ -30,7 +53,7 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
discreteProbabilityMass: Some(r),
|
discreteProbabilityMass: Some(r),
|
||||||
} =>
|
} =>
|
||||||
Some(
|
Some(
|
||||||
Shape.Mixed.make(
|
Distributions.Mixed.make(
|
||||||
~continuous,
|
~continuous,
|
||||||
~discrete,
|
~discrete,
|
||||||
~discreteProbabilityMassFraction=r,
|
~discreteProbabilityMassFraction=r,
|
||||||
|
@ -56,10 +79,12 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
||||||
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||||
discreteProbabilityMass: None,
|
discreteProbabilityMass: None,
|
||||||
} =>
|
} =>
|
||||||
let discreteProbabilityMassFraction = Shape.Discrete.ySum(discrete);
|
let discreteProbabilityMassFraction =
|
||||||
let discrete = Shape.Discrete.scaleYToTotal(1.0, discrete);
|
Distributions.Discrete.T.Integral.sum(~cache=None, discrete);
|
||||||
|
let discrete =
|
||||||
|
Distributions.Discrete.T.scaleToIntegralSum(~intendedSum=1.0, discrete);
|
||||||
Some(
|
Some(
|
||||||
Shape.Mixed.make(
|
Distributions.Mixed.make(
|
||||||
~continuous,
|
~continuous,
|
||||||
~discrete,
|
~discrete,
|
||||||
~discreteProbabilityMassFraction,
|
~discreteProbabilityMassFraction,
|
|
@ -50,11 +50,11 @@ module TimePoint = {
|
||||||
MomentRe.diff(timeVector.zero, moment, timeVector.unit);
|
MomentRe.diff(timeVector.zero, moment, timeVector.unit);
|
||||||
};
|
};
|
||||||
|
|
||||||
module RelativeTimePoint = {
|
type timeInVector =
|
||||||
type timeInVector =
|
| Time(MomentRe.Moment.t)
|
||||||
| Time(MomentRe.Moment.t)
|
| XValue(float);
|
||||||
| XValue(float);
|
|
||||||
|
|
||||||
|
module RelativeTimePoint = {
|
||||||
let toTime = (timeVector: timeVector, timeInVector: timeInVector) =>
|
let toTime = (timeVector: timeVector, timeInVector: timeInVector) =>
|
||||||
switch (timeInVector) {
|
switch (timeInVector) {
|
||||||
| Time(r) => r
|
| Time(r) => r
|
236
src/distributions/XYShape.re
Normal file
236
src/distributions/XYShape.re
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
open DistTypes;
|
||||||
|
|
||||||
|
type t = xyShape;
|
||||||
|
|
||||||
|
let toJs = (t: t) => {
|
||||||
|
{"xs": t.xs, "ys": t.ys};
|
||||||
|
};
|
||||||
|
let xs = (t: t) => t.xs;
|
||||||
|
let minX = (t: t) => t |> xs |> E.A.first;
|
||||||
|
let maxX = (t: t) => t |> xs |> E.A.last;
|
||||||
|
let xTotalRange = (t: t) =>
|
||||||
|
switch (minX(t), maxX(t)) {
|
||||||
|
| (Some(min), Some(max)) => Some(max -. min)
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
let first = ({xs, ys}: t) =>
|
||||||
|
switch (xs |> E.A.first, ys |> E.A.first) {
|
||||||
|
| (Some(x), Some(y)) => Some((x, y))
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
let last = ({xs, ys}: t) =>
|
||||||
|
switch (xs |> E.A.last, ys |> E.A.last) {
|
||||||
|
| (Some(x), Some(y)) => Some((x, y))
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let unsafeFirst = (t: t) => first(t) |> E.O.toExn("Unsafe operation");
|
||||||
|
let unsafeLast = (t: t) => last(t) |> E.O.toExn("Unsafe operation");
|
||||||
|
|
||||||
|
let zip = ({xs, ys}: t) => Belt.Array.zip(xs, ys);
|
||||||
|
let getBy = (t: t, fn) => t |> zip |> Belt.Array.getBy(_, fn);
|
||||||
|
|
||||||
|
let firstPairAtOrBeforeValue = (xValue, t: t) => {
|
||||||
|
let zipped = zip(t);
|
||||||
|
let firstIndex =
|
||||||
|
zipped |> Belt.Array.getIndexBy(_, ((x, y)) => 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 XtoY = {
|
||||||
|
let stepwiseIncremental = (f, t: t) =>
|
||||||
|
firstPairAtOrBeforeValue(f, t) |> E.O.fmap(((_, y)) => y);
|
||||||
|
|
||||||
|
let stepwiseIfAtX = (f: float, t: t) => {
|
||||||
|
getBy(t, ((x: float, _)) => {x == f}) |> E.O.fmap(((_, y)) => y);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: When Roman's PR comes in, fix this bit. This depends on interpolation, obviously.
|
||||||
|
let linear = (f, t: t) => t |> CdfLibrary.Distribution.findY(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
let pointwiseMap = (fn, t: t): t => {xs: t.xs, ys: t.ys |> E.A.fmap(fn)};
|
||||||
|
let xMap = (fn, t: t): t => {xs: E.A.fmap(fn, t.xs), ys: t.ys};
|
||||||
|
let fromArray = ((xs, ys)): t => {xs, ys};
|
||||||
|
let fromArrays = (xs, ys): t => {xs, ys};
|
||||||
|
|
||||||
|
module Combine = {
|
||||||
|
let combineLinear = (t1: t, t2: t, fn: (float, float) => float) => {
|
||||||
|
let allXs = Belt.Array.concat(xs(t1), xs(t2));
|
||||||
|
allXs |> Array.sort(compare);
|
||||||
|
let allYs =
|
||||||
|
allXs
|
||||||
|
|> E.A.fmap(x => {
|
||||||
|
let y1 = XtoY.linear(x, t1);
|
||||||
|
let y2 = XtoY.linear(x, t2);
|
||||||
|
fn(y1, y2);
|
||||||
|
});
|
||||||
|
fromArrays(allXs, allYs);
|
||||||
|
};
|
||||||
|
|
||||||
|
let combineStepwise =
|
||||||
|
(t1: t, t2: t, fn: (option(float), option(float)) => float) => {
|
||||||
|
let allXs = Belt.Array.concat(xs(t1), xs(t2));
|
||||||
|
allXs |> Array.sort(compare);
|
||||||
|
let allYs =
|
||||||
|
allXs
|
||||||
|
|> E.A.fmap(x => {
|
||||||
|
let y1 = XtoY.stepwiseIncremental(x, t1);
|
||||||
|
let y2 = XtoY.stepwiseIncremental(x, t2);
|
||||||
|
fn(y1, y2);
|
||||||
|
});
|
||||||
|
fromArrays(allXs, allYs);
|
||||||
|
};
|
||||||
|
|
||||||
|
let combineIfAtX =
|
||||||
|
(t1: t, t2: t, fn: (option(float), option(float)) => float) => {
|
||||||
|
let allXs = Belt.Array.concat(xs(t1), xs(t2));
|
||||||
|
allXs |> Array.sort(compare);
|
||||||
|
let allYs =
|
||||||
|
allXs
|
||||||
|
|> E.A.fmap(x => {
|
||||||
|
let y1 = XtoY.stepwiseIfAtX(x, t1);
|
||||||
|
let y2 = XtoY.stepwiseIfAtX(x, t2);
|
||||||
|
fn(y1, y2);
|
||||||
|
});
|
||||||
|
fromArrays(allXs, allYs);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo: maybe not needed?
|
||||||
|
// let comparePoint = (a: float, b: float) => a > b ? 1 : (-1);
|
||||||
|
|
||||||
|
let comparePoints = ((x1: float, y1: float), (x2: float, y2: float)) =>
|
||||||
|
switch (x1 == x2, y1 == y2) {
|
||||||
|
| (false, _) => compare(x1, x2)
|
||||||
|
| (true, false) => compare(y1, y2)
|
||||||
|
| (true, true) => (-1)
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo: This is broken :(
|
||||||
|
let combine = (t1: t, t2: t) => {
|
||||||
|
let totalLength = E.A.length(t1.xs) + E.A.length(t2.xs);
|
||||||
|
let array = Belt.Array.concat(zip(t1), zip(t2));
|
||||||
|
Array.sort(comparePoints, array);
|
||||||
|
array |> Belt.Array.unzip |> fromArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
let intersperce = (t1: t, t2: t) => {
|
||||||
|
let items: ref(array((float, float))) = ref([||]);
|
||||||
|
let t1 = zip(t1);
|
||||||
|
let t2 = zip(t2);
|
||||||
|
|
||||||
|
Belt.Array.forEachWithIndex(t1, (i, item) => {
|
||||||
|
switch (Belt.Array.get(t2, i)) {
|
||||||
|
| Some(r) => items := E.A.append(items^, [|item, r|])
|
||||||
|
| None => items := E.A.append(items^, [|item|])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
items^ |> Belt.Array.unzip |> fromArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
let yFold = (fn, t: t) => {
|
||||||
|
E.A.fold_left(fn, 0., t.ys);
|
||||||
|
};
|
||||||
|
|
||||||
|
let ySum = yFold((a, b) => a +. b);
|
||||||
|
|
||||||
|
let _transverse = fn =>
|
||||||
|
Belt.Array.reduce(_, [||], (items, (x, y)) =>
|
||||||
|
switch (E.A.last(items)) {
|
||||||
|
| Some((_, yLast)) => Belt.Array.concat(items, [|(x, fn(y, yLast))|])
|
||||||
|
| None => [|(x, y)|]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let _transverseShape = (fn, p: t) => {
|
||||||
|
Belt.Array.zip(p.xs, p.ys)
|
||||||
|
|> _transverse(fn)
|
||||||
|
|> Belt.Array.unzip
|
||||||
|
|> fromArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
let accumulateYs = _transverseShape((aCurrent, aLast) => aCurrent +. aLast);
|
||||||
|
let subtractYs = _transverseShape((aCurrent, aLast) => aCurrent -. aLast);
|
||||||
|
|
||||||
|
let findY = CdfLibrary.Distribution.findY;
|
||||||
|
let findX = CdfLibrary.Distribution.findX;
|
||||||
|
|
||||||
|
// 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 floatSum = Belt.Array.reduce(_, 0., (a, b) => a +. b);
|
||||||
|
let toT = r => r |> Belt.Array.unzip |> fromArray;
|
||||||
|
let nextX = ((_, (nextX, _)): zippedRange) => nextX;
|
||||||
|
|
||||||
|
let rangePointAssumingSteps =
|
||||||
|
(((lastX, lastY), (nextX, nextY)): zippedRange) => (
|
||||||
|
nextX,
|
||||||
|
lastY,
|
||||||
|
);
|
||||||
|
|
||||||
|
let rangeAreaAssumingTriangles =
|
||||||
|
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
|
||||||
|
(nextX -. lastX) *. (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))));
|
||||||
|
|
||||||
|
let integrateWithTriangles = z => {
|
||||||
|
let rangeItems = mapYsBasedOnRanges(rangeAreaAssumingTriangles, z);
|
||||||
|
(
|
||||||
|
switch (rangeItems, z |> first) {
|
||||||
|
| (Some(r), Some((firstX, _))) =>
|
||||||
|
Some(Belt.Array.concat([|(firstX, 0.0)|], r))
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> E.O.fmap(toT)
|
||||||
|
|> E.O.fmap(accumulateYs);
|
||||||
|
};
|
||||||
|
|
||||||
|
let derivative = mapYsBasedOnRanges(delta_y_over_delta_x);
|
||||||
|
|
||||||
|
// 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 stepsToContinuous = t => {
|
||||||
|
let diff = xTotalRange(t) |> E.O.fmap(r => r *. 0.00001);
|
||||||
|
let items =
|
||||||
|
switch (diff, E.A.toRanges(Belt.Array.zip(t.xs, t.ys))) {
|
||||||
|
| (Some(diff), Ok(items)) =>
|
||||||
|
Some(
|
||||||
|
items
|
||||||
|
|> Belt.Array.map(_, rangePointAssumingSteps)
|
||||||
|
|> Belt.Array.unzip
|
||||||
|
|> fromArray
|
||||||
|
|> intersperce(t |> xMap(e => e +. diff)),
|
||||||
|
)
|
||||||
|
| _ => Some(t)
|
||||||
|
};
|
||||||
|
let bar = items |> E.O.fmap(zip) |> E.O.bind(_, E.A.get(_, 0));
|
||||||
|
let items =
|
||||||
|
switch (items, bar) {
|
||||||
|
| (Some(items), Some((0.0, _))) => Some(items)
|
||||||
|
| (Some(items), Some((firstX, _))) =>
|
||||||
|
let all = E.A.append([|(firstX, 0.0)|], items |> zip);
|
||||||
|
let foo = all |> Belt.Array.unzip |> fromArray;
|
||||||
|
Some(foo);
|
||||||
|
| _ => None
|
||||||
|
};
|
||||||
|
items;
|
||||||
|
};
|
||||||
|
};
|
|
@ -17,17 +17,13 @@ let propValue = (t: Prop.Value.t) => {
|
||||||
switch (t) {
|
switch (t) {
|
||||||
| SelectSingle(r) => r |> ReasonReact.string
|
| SelectSingle(r) => r |> ReasonReact.string
|
||||||
| ConditionalArray(r) => "Array" |> ReasonReact.string
|
| ConditionalArray(r) => "Array" |> ReasonReact.string
|
||||||
| GenericDistribution(r) =>
|
| DistPlusIngredients(r) =>
|
||||||
let newDistribution =
|
let newDistribution =
|
||||||
GenericDistribution.renderIfNeeded(~sampleCount=2000, r);
|
DistPlusIngredients.toDistPlus(~sampleCount=1000, r);
|
||||||
switch (newDistribution) {
|
switch (newDistribution) {
|
||||||
| Some(distribution) =>
|
| Some(distribution) =>
|
||||||
<div>
|
<div> <DistPlusPlot distPlus=distribution /> </div>
|
||||||
{GenericDistribution.normalize(distribution)
|
|
||||||
|> E.O.React.fmapOrNull(dist => <GenericDistributionChart dist />)}
|
|
||||||
</div>
|
|
||||||
| None => "Something went wrong" |> ReasonReact.string
|
| None => "Something went wrong" |> ReasonReact.string
|
||||||
| _ => <div />
|
|
||||||
};
|
};
|
||||||
| FloatCdf(_) => <div />
|
| FloatCdf(_) => <div />
|
||||||
| Probability(r) =>
|
| Probability(r) =>
|
||||||
|
|
|
@ -9,7 +9,7 @@ module Value = {
|
||||||
| DateTime(MomentRe.Moment.t)
|
| DateTime(MomentRe.Moment.t)
|
||||||
| FloatPoint(float)
|
| FloatPoint(float)
|
||||||
| Probability(float)
|
| Probability(float)
|
||||||
| GenericDistribution(DistributionTypes.genericDistribution)
|
| DistPlusIngredients(DistTypes.distPlusIngredients)
|
||||||
| ConditionalArray(array(conditional))
|
| ConditionalArray(array(conditional))
|
||||||
| FloatCdf(string);
|
| FloatCdf(string);
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ module ValueCluster = {
|
||||||
[ | `combination(range(MomentRe.Moment.t)) | `item(string)],
|
[ | `combination(range(MomentRe.Moment.t)) | `item(string)],
|
||||||
)
|
)
|
||||||
| Probability([ | `item(string)])
|
| Probability([ | `item(string)])
|
||||||
| GenericDistribution([ | `item(DistributionTypes.genericDistribution)])
|
| DistPlusIngredients([ | `item(DistTypes.distPlusIngredients)])
|
||||||
| ConditionalArray([ | `item(array(conditional))])
|
| ConditionalArray([ | `item(array(conditional))])
|
||||||
| FloatCdf([ | `item(string)]);
|
| FloatCdf([ | `item(string)]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
module Styles = {
|
|
||||||
open Css;
|
|
||||||
let graph = chartColor =>
|
|
||||||
style([
|
|
||||||
selector(".x-axis", [fontSize(`px(9))]),
|
|
||||||
selector(".x-axis .domain", [display(`none)]),
|
|
||||||
selector(".x-axis .tick line", [display(`none)]),
|
|
||||||
selector(".x-axis .tick text", [color(`hex("bfcad4"))]),
|
|
||||||
selector(".chart .area-path", [SVG.fill(chartColor)]),
|
|
||||||
selector(".lollipops-line", [SVG.stroke(`hex("bfcad4"))]),
|
|
||||||
selector(
|
|
||||||
".lollipops-circle",
|
|
||||||
[SVG.stroke(`hex("bfcad4")), SVG.fill(`hex("bfcad4"))],
|
|
||||||
),
|
|
||||||
selector(".lollipops-x-axis .domain", [display(`none)]),
|
|
||||||
selector(".lollipops-x-axis .tick line", [display(`none)]),
|
|
||||||
selector(".lollipops-x-axis .tick text", [display(`none)]),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
[@react.component]
|
|
||||||
let make =
|
|
||||||
(
|
|
||||||
~data,
|
|
||||||
~minX=None,
|
|
||||||
~maxX=None,
|
|
||||||
~width=300,
|
|
||||||
~height=50,
|
|
||||||
~color=`hex("7e9db7"),
|
|
||||||
) =>
|
|
||||||
<div className={Styles.graph(color)}>
|
|
||||||
<ForetoldComponents.CdfChart__Base
|
|
||||||
width=0
|
|
||||||
height
|
|
||||||
?minX
|
|
||||||
?maxX
|
|
||||||
marginBottom=20
|
|
||||||
showVerticalLine=false
|
|
||||||
showDistributionLines=false
|
|
||||||
primaryDistribution=data
|
|
||||||
/>
|
|
||||||
</div>;
|
|
|
@ -109,17 +109,20 @@ module Model = {
|
||||||
|
|
||||||
// TODO: Fixe number that integral is calculated for
|
// TODO: Fixe number that integral is calculated for
|
||||||
let getGlobalCatastropheChance = dateTime => {
|
let getGlobalCatastropheChance = dateTime => {
|
||||||
let model = GlobalCatastrophe.Model.make(dateTime);
|
GlobalCatastrophe.makeI(MomentRe.momentNow())
|
||||||
switch (model) {
|
|> DistPlusIngredients.toDistPlus(~sampleCount=1000)
|
||||||
| Prop.Value.GenericDistribution(genericDistribution) =>
|
|> E.O.bind(
|
||||||
GenericDistribution.renderIfNeeded(
|
_,
|
||||||
~sampleCount=1000,
|
t => {
|
||||||
genericDistribution,
|
let foo =
|
||||||
)
|
Distributions.DistPlusTime.Integral.xToY(
|
||||||
|> E.O.bind(_, GenericDistribution.normalize)
|
~cache=None,
|
||||||
|> E.O.bind(_, GenericDistribution.yIntegral(_, 18.0))
|
Time(dateTime),
|
||||||
| _ => None
|
t,
|
||||||
};
|
);
|
||||||
|
Some(0.5);
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let make =
|
let make =
|
||||||
|
@ -148,7 +151,8 @@ module Model = {
|
||||||
| Some({truthValue: false}) => difference
|
| Some({truthValue: false}) => difference
|
||||||
| None =>
|
| None =>
|
||||||
let foo =
|
let foo =
|
||||||
getGlobalCatastropheChance(dateTime)
|
// getGlobalCatastropheChance(dateTime)
|
||||||
|
Some(0.5)
|
||||||
|> E.O.fmap(E.Float.with2DigitsPrecision)
|
|> E.O.fmap(E.Float.with2DigitsPrecision)
|
||||||
|> E.O.fmap((r: string) =>
|
|> E.O.fmap((r: string) =>
|
||||||
"uniform(0,1) > " ++ r ++ " ? " ++ difference ++ ": 0"
|
"uniform(0,1) > " ++ r ++ " ? " ++ difference ++ ": 0"
|
||||||
|
@ -156,29 +160,24 @@ module Model = {
|
||||||
foo |> E.O.default("");
|
foo |> E.O.default("");
|
||||||
};
|
};
|
||||||
|
|
||||||
let genericDistribution =
|
let distPlusIngredients =
|
||||||
GenericDistribution.make(
|
DistPlusIngredients.make(
|
||||||
~generationSource=GuesstimatorString(str),
|
~guesstimatorString=str,
|
||||||
~probabilityType=Cdf,
|
|
||||||
~domain=Complete,
|
~domain=Complete,
|
||||||
~unit=UnspecifiedDistribution,
|
~unit=UnspecifiedDistribution,
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
Prop.Value.GenericDistribution(genericDistribution);
|
Prop.Value.DistPlusIngredients(distPlusIngredients);
|
||||||
|
|
||||||
| CHANCE_OF_EXISTENCE =>
|
| CHANCE_OF_EXISTENCE =>
|
||||||
Prop.Value.GenericDistribution(
|
Prop.Value.DistPlusIngredients(
|
||||||
GenericDistribution.make(
|
DistPlusIngredients.make(
|
||||||
~generationSource=
|
~guesstimatorString=
|
||||||
GuesstimatorString(
|
GuesstimatorDist.min(
|
||||||
GuesstimatorDist.min(
|
GlobalCatastrophe.guesstimatorString,
|
||||||
GlobalCatastrophe.guesstimatorString,
|
GuesstimatorDist.logNormal(40., 4.),
|
||||||
GuesstimatorDist.logNormal(40., 4.),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
~probabilityType=Cdf,
|
|
||||||
~domain=RightLimited({xPoint: 100., excludingProbabilityMass: 0.3}),
|
~domain=RightLimited({xPoint: 100., excludingProbabilityMass: 0.3}),
|
||||||
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
|
||||||
(),
|
(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
let guesstimatorString = "20 to 80";
|
let guesstimatorString = "floor(10 to 20)";
|
||||||
|
|
||||||
|
let makeI = (currentDateTime: MomentRe.Moment.t) => {
|
||||||
|
DistPlusIngredients.make(
|
||||||
|
~guesstimatorString,
|
||||||
|
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
};
|
||||||
module Model = {
|
module Model = {
|
||||||
let make = (currentDateTime: MomentRe.Moment.t) => {
|
let make = (currentDateTime: MomentRe.Moment.t) => {
|
||||||
let genericDistribution =
|
let distPlusIngredients =
|
||||||
GenericDistribution.make(
|
DistPlusIngredients.make(
|
||||||
~generationSource=GuesstimatorString(guesstimatorString),
|
~guesstimatorString,
|
||||||
~probabilityType=Cdf,
|
|
||||||
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
Prop.Value.GenericDistribution(genericDistribution);
|
Prop.Value.DistPlusIngredients(distPlusIngredients);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,18 @@ const {
|
||||||
let cdf = new Cdf(xs, ys);
|
let cdf = new Cdf(xs, ys);
|
||||||
return cdf.findY(x);
|
return cdf.findY(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param x
|
||||||
|
* @param xs
|
||||||
|
* @param ys
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
function convertToNewLength(n, { xs, ys }) {
|
||||||
|
let dist = new ContinuousDistribution(xs, ys);
|
||||||
|
return dist.convertToNewLength(n);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -153,6 +165,7 @@ const {
|
||||||
pdfToCdf,
|
pdfToCdf,
|
||||||
findY,
|
findY,
|
||||||
findX,
|
findX,
|
||||||
|
convertToNewLength,
|
||||||
mean,
|
mean,
|
||||||
scoreNonMarketCdfCdf,
|
scoreNonMarketCdfCdf,
|
||||||
differentialEntropy,
|
differentialEntropy,
|
||||||
|
|
|
@ -5,16 +5,14 @@ module JS = {
|
||||||
ys: array(float),
|
ys: array(float),
|
||||||
};
|
};
|
||||||
|
|
||||||
let distToJs = (d: DistributionTypes.continuousShape) =>
|
let distToJs = (d: DistTypes.xyShape) => distJs(~xs=d.xs, ~ys=d.ys);
|
||||||
distJs(~xs=d.xs, ~ys=d.ys);
|
|
||||||
|
|
||||||
let jsToDist = (d: distJs): DistributionTypes.continuousShape => {
|
let jsToDist = (d: distJs): DistTypes.xyShape => {
|
||||||
xs: xsGet(d),
|
xs: xsGet(d),
|
||||||
ys: ysGet(d),
|
ys: ysGet(d),
|
||||||
};
|
};
|
||||||
|
|
||||||
let doAsDist = (f, d: DistributionTypes.continuousShape) =>
|
let doAsDist = (f, d: DistTypes.xyShape) => d |> distToJs |> f |> jsToDist;
|
||||||
d |> distToJs |> f |> jsToDist;
|
|
||||||
|
|
||||||
[@bs.module "./CdfLibrary.js"]
|
[@bs.module "./CdfLibrary.js"]
|
||||||
external cdfToPdf: distJs => distJs = "cdfToPdf";
|
external cdfToPdf: distJs => distJs = "cdfToPdf";
|
||||||
|
@ -34,9 +32,18 @@ module JS = {
|
||||||
[@bs.module "./CdfLibrary.js"]
|
[@bs.module "./CdfLibrary.js"]
|
||||||
external differentialEntropy: (int, distJs) => distJs =
|
external differentialEntropy: (int, distJs) => distJs =
|
||||||
"differentialEntropy";
|
"differentialEntropy";
|
||||||
|
|
||||||
|
[@bs.module "./CdfLibrary.js"]
|
||||||
|
external convertToNewLength: (int, distJs) => distJs = "convertToNewLength";
|
||||||
};
|
};
|
||||||
|
|
||||||
module Distribution = {
|
module Distribution = {
|
||||||
|
let convertToNewLength = (int, {xs, _} as dist: DistTypes.xyShape) =>
|
||||||
|
switch (E.A.length(xs)) {
|
||||||
|
| 0
|
||||||
|
| 1 => dist
|
||||||
|
| _ => dist |> JS.doAsDist(JS.convertToNewLength(int))
|
||||||
|
};
|
||||||
let toPdf = dist => dist |> JS.doAsDist(JS.cdfToPdf);
|
let toPdf = dist => dist |> JS.doAsDist(JS.cdfToPdf);
|
||||||
let toCdf = dist => dist |> JS.doAsDist(JS.pdfToCdf);
|
let toCdf = dist => dist |> JS.doAsDist(JS.pdfToCdf);
|
||||||
let findX = (y, dist) => dist |> JS.distToJs |> JS.findX(y);
|
let findX = (y, dist) => dist |> JS.distToJs |> JS.findX(y);
|
||||||
|
|
|
@ -122,7 +122,7 @@ module R = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let safe_fn_of_string = (fn, s: string): option('a) =>
|
let safe_fn_of_string = (fn, s: string): option('a) =>
|
||||||
try (Some(fn(s))) {
|
try(Some(fn(s))) {
|
||||||
| _ => None
|
| _ => None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -216,6 +216,8 @@ module A = {
|
||||||
let unsafe_get = Array.unsafe_get;
|
let unsafe_get = Array.unsafe_get;
|
||||||
let get = Belt.Array.get;
|
let get = Belt.Array.get;
|
||||||
let getBy = Belt.Array.getBy;
|
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 hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome;
|
||||||
let fold_left = Array.fold_left;
|
let fold_left = Array.fold_left;
|
||||||
let fold_right = Array.fold_right;
|
let fold_right = Array.fold_right;
|
||||||
|
@ -294,28 +296,4 @@ module JsArray = {
|
||||||
Rationale.Option.toExn("Warning: This should not have happened"),
|
Rationale.Option.toExn("Warning: This should not have happened"),
|
||||||
);
|
);
|
||||||
let filter = Js.Array.filter;
|
let filter = Js.Array.filter;
|
||||||
};
|
|
||||||
|
|
||||||
module NonZeroInt = {
|
|
||||||
type t = int;
|
|
||||||
let make = (i: int) => i < 0 ? None : Some(i);
|
|
||||||
let fmap = (fn, a: t) => make(fn(a));
|
|
||||||
let increment = fmap(I.increment);
|
|
||||||
let decrement = fmap(I.decrement);
|
|
||||||
};
|
|
||||||
|
|
||||||
module BoundedInt = {
|
|
||||||
type t = int;
|
|
||||||
let make = (i: int, limit: int) => {
|
|
||||||
let lessThan0 = r => r < 0;
|
|
||||||
let greaterThanLimit = r => r > limit;
|
|
||||||
if (lessThan0(i) || greaterThanLimit(i)) {
|
|
||||||
None;
|
|
||||||
} else {
|
|
||||||
Some(i);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
let fmap = (fn, a: t, l) => make(fn(a), l);
|
|
||||||
let increment = fmap(I.increment);
|
|
||||||
let decrement = fmap(I.decrement);
|
|
||||||
};
|
};
|
|
@ -5,7 +5,7 @@ module Internals = {
|
||||||
ys: array(float),
|
ys: array(float),
|
||||||
};
|
};
|
||||||
|
|
||||||
let jsToDistDiscrete = (d: discrete): DistributionTypes.discreteShape => {
|
let jsToDistDiscrete = (d: discrete): DistTypes.discreteShape => {
|
||||||
xs: xsGet(d),
|
xs: xsGet(d),
|
||||||
ys: ysGet(d),
|
ys: ysGet(d),
|
||||||
};
|
};
|
||||||
|
@ -16,28 +16,34 @@ module Internals = {
|
||||||
discrete,
|
discrete,
|
||||||
};
|
};
|
||||||
|
|
||||||
let toContinous = (r: combined): DistributionTypes.continuousShape =>
|
// todo: Force to be fewer samples
|
||||||
continuousGet(r) |> CdfLibrary.JS.jsToDist;
|
let toContinous = (r: combined) =>
|
||||||
|
continuousGet(r)
|
||||||
|
|> CdfLibrary.JS.jsToDist
|
||||||
|
|> Distributions.Continuous.fromShape;
|
||||||
|
|
||||||
let toDiscrete = (r: combined): DistributionTypes.discreteShape =>
|
let toDiscrete = (r: combined): DistTypes.xyShape =>
|
||||||
discreteGet(r) |> jsToDistDiscrete;
|
discreteGet(r) |> jsToDistDiscrete;
|
||||||
|
|
||||||
[@bs.module "./GuesstimatorLibrary.js"]
|
[@bs.module "./GuesstimatorLibrary.js"]
|
||||||
external toCombinedFormat: (string, int) => combined = "run";
|
external toCombinedFormat: (string, int, int) => combined = "run";
|
||||||
|
|
||||||
let toMixedShape = (r: combined): option(DistributionTypes.mixedShape) => {
|
// todo: Format to correct mass, also normalize the pdf.
|
||||||
let assumptions: MixedShapeBuilder.assumptions = {
|
let toMixedShape = (r: combined): option(DistTypes.shape) => {
|
||||||
continuous: ADDS_TO_1,
|
let continuous =
|
||||||
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
toContinous(r) |> Distributions.Continuous.convertToNewLength(100);
|
||||||
discreteProbabilityMass: None,
|
let discrete = toDiscrete(r);
|
||||||
};
|
// let continuousProb =
|
||||||
MixedShapeBuilder.build(
|
// cont |> Distributions.Continuous.T.Integral.sum(~cache=None);
|
||||||
~continuous=toContinous(r),
|
// let discreteProb =
|
||||||
~discrete=toDiscrete(r),
|
// d |> Distributions.Discrete.T.Integral.sum(~cache=None);
|
||||||
~assumptions,
|
|
||||||
);
|
let foo = MixedShapeBuilder.buildSimple(~continuous, ~discrete);
|
||||||
|
foo;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
let stringToMixedShape = (~string, ~sampleCount=1000, ()) =>
|
let stringToMixedShape =
|
||||||
Internals.toCombinedFormat(string, sampleCount) |> Internals.toMixedShape;
|
(~string, ~sampleCount=1000, ~outputXYPoints=1000, ()) =>
|
||||||
|
Internals.toCombinedFormat(string, sampleCount, outputXYPoints)
|
||||||
|
|> Internals.toMixedShape;
|
|
@ -34,7 +34,7 @@ const ratioSize = samples => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const toPdf = (values, sampleCount, min, max) => {
|
const toPdf = (values, outputResolutionCount, min, max) => {
|
||||||
let duplicateSamples = _(values).groupBy().pickBy(x => x.length > 1).keys().value();
|
let duplicateSamples = _(values).groupBy().pickBy(x => x.length > 1).keys().value();
|
||||||
let totalLength = _.size(values);
|
let totalLength = _.size(values);
|
||||||
let frequencies = duplicateSamples.map(s => ({value: parseFloat(s), percentage: _(values).filter(x => x ==s).size()/totalLength}));
|
let frequencies = duplicateSamples.map(s => ({value: parseFloat(s), percentage: _(values).filter(x => x ==s).size()/totalLength}));
|
||||||
|
@ -48,13 +48,13 @@ const toPdf = (values, sampleCount, min, max) => {
|
||||||
const ratioSize$ = ratioSize(samples);
|
const ratioSize$ = ratioSize(samples);
|
||||||
const width = ratioSize$ === 'SMALL' ? 100 : 1;
|
const width = ratioSize$ === 'SMALL' ? 100 : 1;
|
||||||
|
|
||||||
const pdf = samples.toPdf({ size: sampleCount, width, min, max });
|
const pdf = samples.toPdf({ size: outputResolutionCount, width, min, max });
|
||||||
continuous = pdf;
|
continuous = pdf;
|
||||||
}
|
}
|
||||||
return {continuous, discrete};
|
return {continuous, discrete};
|
||||||
};
|
};
|
||||||
|
|
||||||
let run = (text, sampleCount, inputs=[], min=false, max=false) => {
|
let run = (text, sampleCount, outputResolutionCount, inputs=[], min=false, max=false) => {
|
||||||
let [_error, item] = Guesstimator.parse({ text: "=" + text });
|
let [_error, item] = Guesstimator.parse({ text: "=" + text });
|
||||||
const { parsedInput } = item;
|
const { parsedInput } = item;
|
||||||
const { guesstimateType } = parsedInput;
|
const { guesstimateType } = parsedInput;
|
||||||
|
@ -78,7 +78,7 @@ let run = (text, sampleCount, inputs=[], min=false, max=false) => {
|
||||||
} else if (values.length === 1) {
|
} else if (values.length === 1) {
|
||||||
update = blankResponse;
|
update = blankResponse;
|
||||||
} else {
|
} else {
|
||||||
update = toPdf(values, sampleCount, min, max);
|
update = toPdf(values, outputResolutionCount, min, max);
|
||||||
}
|
}
|
||||||
return update;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user