Merge remote-tracking branch 'origin/master' into feature/1083
This commit is contained in:
commit
eaea7cc69a
|
@ -1,25 +1,365 @@
|
|||
open Jest;
|
||||
open Expect;
|
||||
|
||||
let shape: DistributionTypes.xyShape = {
|
||||
xs: [|1., 4., 8.|],
|
||||
ys: [|8., 9., 2.|],
|
||||
};
|
||||
let shape: DistTypes.xyShape = {xs: [|1., 4., 8.|], ys: [|8., 9., 2.|]};
|
||||
|
||||
let step: DistributionTypes.xyShape = {
|
||||
xs: [|1., 4., 8.|],
|
||||
ys: [|8., 17., 19.|],
|
||||
};
|
||||
let makeTest = (~only=false, str, item1, item2) =>
|
||||
only
|
||||
? Only.test(str, () =>
|
||||
expect(item1) |> toEqual(item2)
|
||||
)
|
||||
: test(str, () =>
|
||||
expect(item1) |> toEqual(item2)
|
||||
);
|
||||
|
||||
open Shape;
|
||||
|
||||
describe("Shape", () =>
|
||||
describe("XYShape", () => {
|
||||
test("#ySum", () =>
|
||||
expect(XYShape.ySum(shape)) |> toEqual(19.0)
|
||||
describe("Shape", () => {
|
||||
describe("Continuous", () => {
|
||||
open Distributions.Continuous;
|
||||
let continuous = make(shape, `Linear);
|
||||
makeTest("minX", T.minX(continuous), Some(1.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", () =>
|
||||
expect(Discrete.integrate(shape)) |> toEqual(step)
|
||||
describe("xToY", () => {
|
||||
describe("when Linear", () => {
|
||||
makeTest(
|
||||
"at 4.0",
|
||||
T.xToY(4., continuous),
|
||||
{continuous: 9.0, discrete: 0.0},
|
||||
);
|
||||
// Note: This below is weird to me, I'm not sure if it's what we want really.
|
||||
makeTest(
|
||||
"at 0.0",
|
||||
T.xToY(0., continuous),
|
||||
{continuous: 8.0, discrete: 0.0},
|
||||
);
|
||||
makeTest(
|
||||
"at 5.0",
|
||||
T.xToY(5., continuous),
|
||||
{continuous: 7.25, discrete: 0.0},
|
||||
);
|
||||
makeTest(
|
||||
"at 10.0",
|
||||
T.xToY(10., continuous),
|
||||
{continuous: 2.0, discrete: 0.0},
|
||||
);
|
||||
});
|
||||
describe("when Stepwise", () => {
|
||||
let continuous = make(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);
|
||||
HS.set(entriesByPath, f.id, FolderEntry(f));
|
||||
f.children
|
||||
|> E.L.iter(e =>
|
||||
switch (e) {
|
||||
|> E.L.iter(
|
||||
fun
|
||||
| CompEntry(c) => processEntry(c, f.id)
|
||||
| FolderEntry(f) => processFolder(f, f.id)
|
||||
}
|
||||
| FolderEntry(f) => processFolder(f, f.id),
|
||||
);
|
||||
}
|
||||
and processEntry = (c: compEntry, curPath) => {
|
||||
|
@ -38,11 +37,10 @@ let buildIds = entries => {
|
|||
HS.set(entriesByPath, c.id, CompEntry(c));
|
||||
};
|
||||
entries
|
||||
|> E.L.iter(e =>
|
||||
switch (e) {
|
||||
|> E.L.iter(
|
||||
fun
|
||||
| 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(normal(28,4)), normal(32,2), uniform(20,24), [.5,.2,.1])",
|
||||
|
||||
let timeDist =
|
||||
GenericDistribution.make(
|
||||
~generationSource=
|
||||
GuesstimatorString("mm(floor(normal(30,3)), normal(39,1), [.5,.5])"),
|
||||
~probabilityType=Pdf,
|
||||
DistPlusIngredients.make(
|
||||
~guesstimatorString="mm(floor(10 to 15), 10 to 11, [.9,.1])",
|
||||
~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 = () =>
|
||||
<div>
|
||||
<div>
|
||||
<h2> {"Basic Mixed Distribution" |> ReasonReact.string} </h2>
|
||||
{timeDist
|
||||
|> E.O.bind(_, GenericDistribution.normalize)
|
||||
|> E.O.React.fmapOrNull(dist => <GenericDistributionChart dist />)}
|
||||
<h2> {"Simple Continuous" |> ReasonReact.string} </h2>
|
||||
<h2> {"Single-Discrete" |> ReasonReact.string} </h2>
|
||||
{setup(
|
||||
DistPlusIngredients.make(
|
||||
~guesstimatorString="8 to 12, [.5,.5])",
|
||||
~domain=Complete,
|
||||
(),
|
||||
),
|
||||
)
|
||||
|> E.O.React.fmapOrNull(distPlus => <DistPlusPlot distPlus />)}
|
||||
</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 = {
|
||||
open Css;
|
||||
let textOverlay = style([position(`absolute)]);
|
||||
|
@ -40,17 +102,17 @@ let make =
|
|||
~timeScale=?,
|
||||
) => {
|
||||
<div className={Styles.graph(color)}>
|
||||
<CdfChart__Base
|
||||
<RawPlot
|
||||
?maxX
|
||||
?minX
|
||||
?scale
|
||||
?timeScale
|
||||
discrete={discrete |> E.O.fmap(Shape.Discrete.toJs)}
|
||||
discrete={discrete |> E.O.fmap(XYShape.toJs)}
|
||||
height
|
||||
marginBottom=50
|
||||
marginTop=0
|
||||
onHover
|
||||
continuous={continuous |> E.O.fmap(Shape.XYShape.toJs)}
|
||||
continuous={continuous |> E.O.fmap(XYShape.toJs)}
|
||||
showDistributionLines
|
||||
showDistributionYAxis
|
||||
showVerticalLine
|
|
@ -129,6 +129,8 @@ export class CdfChartD3 {
|
|||
|
||||
data(data) {
|
||||
this.attrs.data = data;
|
||||
this.attrs.data.continuous = data.continuous || {xs: [], ys: []};
|
||||
this.attrs.data.discrete = data.discrete || {xs: [], ys: []};
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -308,21 +310,13 @@ export class CdfChartD3 {
|
|||
// Add drawing rectangle.
|
||||
{
|
||||
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() {
|
||||
const mouse = d3.mouse(this);
|
||||
hoverLine.attr('opacity', 1).attr('x1', mouse[0]).attr('x2', mouse[0]);
|
||||
const xValue = mouse[0] > range[0] && mouse[0] < range[1]
|
||||
? xScale.invert(mouse[0]).toFixed(2)
|
||||
: 0;
|
||||
const xValue = xScale.invert(mouse[0]);
|
||||
// This used to be here, but doesn't seem important
|
||||
// const xValue = (mouse[0] > range[0] && mouse[0] < range[1]) ? : 0;
|
||||
context.attrs.onHover(xValue);
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useSize } from 'react-use';
|
||||
|
||||
import { CdfChartD3 } from './cdfChartD3';
|
||||
import { CdfChartD3 } from './distPlotD3';
|
||||
|
||||
/**
|
||||
* @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),
|
||||
};
|
||||
|
||||
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) =>
|
||||
switch (assumptions) {
|
||||
| {
|
||||
|
@ -17,7 +40,7 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
|||
} =>
|
||||
// TODO: Fix this, it's wrong :(
|
||||
Some(
|
||||
Shape.Mixed.make(
|
||||
Distributions.Mixed.make(
|
||||
~continuous,
|
||||
~discrete,
|
||||
~discreteProbabilityMassFraction=r,
|
||||
|
@ -30,7 +53,7 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
|||
discreteProbabilityMass: Some(r),
|
||||
} =>
|
||||
Some(
|
||||
Shape.Mixed.make(
|
||||
Distributions.Mixed.make(
|
||||
~continuous,
|
||||
~discrete,
|
||||
~discreteProbabilityMassFraction=r,
|
||||
|
@ -56,10 +79,12 @@ let build = (~continuous, ~discrete, ~assumptions) =>
|
|||
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||
discreteProbabilityMass: None,
|
||||
} =>
|
||||
let discreteProbabilityMassFraction = Shape.Discrete.ySum(discrete);
|
||||
let discrete = Shape.Discrete.scaleYToTotal(1.0, discrete);
|
||||
let discreteProbabilityMassFraction =
|
||||
Distributions.Discrete.T.Integral.sum(~cache=None, discrete);
|
||||
let discrete =
|
||||
Distributions.Discrete.T.scaleToIntegralSum(~intendedSum=1.0, discrete);
|
||||
Some(
|
||||
Shape.Mixed.make(
|
||||
Distributions.Mixed.make(
|
||||
~continuous,
|
||||
~discrete,
|
||||
~discreteProbabilityMassFraction,
|
|
@ -50,11 +50,11 @@ module TimePoint = {
|
|||
MomentRe.diff(timeVector.zero, moment, timeVector.unit);
|
||||
};
|
||||
|
||||
module RelativeTimePoint = {
|
||||
type timeInVector =
|
||||
| Time(MomentRe.Moment.t)
|
||||
| XValue(float);
|
||||
type timeInVector =
|
||||
| Time(MomentRe.Moment.t)
|
||||
| XValue(float);
|
||||
|
||||
module RelativeTimePoint = {
|
||||
let toTime = (timeVector: timeVector, timeInVector: timeInVector) =>
|
||||
switch (timeInVector) {
|
||||
| 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) {
|
||||
| SelectSingle(r) => r |> ReasonReact.string
|
||||
| ConditionalArray(r) => "Array" |> ReasonReact.string
|
||||
| GenericDistribution(r) =>
|
||||
| DistPlusIngredients(r) =>
|
||||
let newDistribution =
|
||||
GenericDistribution.renderIfNeeded(~sampleCount=2000, r);
|
||||
DistPlusIngredients.toDistPlus(~sampleCount=1000, r);
|
||||
switch (newDistribution) {
|
||||
| Some(distribution) =>
|
||||
<div>
|
||||
{GenericDistribution.normalize(distribution)
|
||||
|> E.O.React.fmapOrNull(dist => <GenericDistributionChart dist />)}
|
||||
</div>
|
||||
<div> <DistPlusPlot distPlus=distribution /> </div>
|
||||
| None => "Something went wrong" |> ReasonReact.string
|
||||
| _ => <div />
|
||||
};
|
||||
| FloatCdf(_) => <div />
|
||||
| Probability(r) =>
|
||||
|
|
|
@ -9,7 +9,7 @@ module Value = {
|
|||
| DateTime(MomentRe.Moment.t)
|
||||
| FloatPoint(float)
|
||||
| Probability(float)
|
||||
| GenericDistribution(DistributionTypes.genericDistribution)
|
||||
| DistPlusIngredients(DistTypes.distPlusIngredients)
|
||||
| ConditionalArray(array(conditional))
|
||||
| FloatCdf(string);
|
||||
|
||||
|
@ -85,7 +85,7 @@ module ValueCluster = {
|
|||
[ | `combination(range(MomentRe.Moment.t)) | `item(string)],
|
||||
)
|
||||
| Probability([ | `item(string)])
|
||||
| GenericDistribution([ | `item(DistributionTypes.genericDistribution)])
|
||||
| DistPlusIngredients([ | `item(DistTypes.distPlusIngredients)])
|
||||
| ConditionalArray([ | `item(array(conditional))])
|
||||
| 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
|
||||
let getGlobalCatastropheChance = dateTime => {
|
||||
let model = GlobalCatastrophe.Model.make(dateTime);
|
||||
switch (model) {
|
||||
| Prop.Value.GenericDistribution(genericDistribution) =>
|
||||
GenericDistribution.renderIfNeeded(
|
||||
~sampleCount=1000,
|
||||
genericDistribution,
|
||||
)
|
||||
|> E.O.bind(_, GenericDistribution.normalize)
|
||||
|> E.O.bind(_, GenericDistribution.yIntegral(_, 18.0))
|
||||
| _ => None
|
||||
};
|
||||
GlobalCatastrophe.makeI(MomentRe.momentNow())
|
||||
|> DistPlusIngredients.toDistPlus(~sampleCount=1000)
|
||||
|> E.O.bind(
|
||||
_,
|
||||
t => {
|
||||
let foo =
|
||||
Distributions.DistPlusTime.Integral.xToY(
|
||||
~cache=None,
|
||||
Time(dateTime),
|
||||
t,
|
||||
);
|
||||
Some(0.5);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
let make =
|
||||
|
@ -148,7 +151,8 @@ module Model = {
|
|||
| Some({truthValue: false}) => difference
|
||||
| None =>
|
||||
let foo =
|
||||
getGlobalCatastropheChance(dateTime)
|
||||
// getGlobalCatastropheChance(dateTime)
|
||||
Some(0.5)
|
||||
|> E.O.fmap(E.Float.with2DigitsPrecision)
|
||||
|> E.O.fmap((r: string) =>
|
||||
"uniform(0,1) > " ++ r ++ " ? " ++ difference ++ ": 0"
|
||||
|
@ -156,29 +160,24 @@ module Model = {
|
|||
foo |> E.O.default("");
|
||||
};
|
||||
|
||||
let genericDistribution =
|
||||
GenericDistribution.make(
|
||||
~generationSource=GuesstimatorString(str),
|
||||
~probabilityType=Cdf,
|
||||
let distPlusIngredients =
|
||||
DistPlusIngredients.make(
|
||||
~guesstimatorString=str,
|
||||
~domain=Complete,
|
||||
~unit=UnspecifiedDistribution,
|
||||
(),
|
||||
);
|
||||
Prop.Value.GenericDistribution(genericDistribution);
|
||||
Prop.Value.DistPlusIngredients(distPlusIngredients);
|
||||
|
||||
| CHANCE_OF_EXISTENCE =>
|
||||
Prop.Value.GenericDistribution(
|
||||
GenericDistribution.make(
|
||||
~generationSource=
|
||||
GuesstimatorString(
|
||||
GuesstimatorDist.min(
|
||||
GlobalCatastrophe.guesstimatorString,
|
||||
GuesstimatorDist.logNormal(40., 4.),
|
||||
),
|
||||
Prop.Value.DistPlusIngredients(
|
||||
DistPlusIngredients.make(
|
||||
~guesstimatorString=
|
||||
GuesstimatorDist.min(
|
||||
GlobalCatastrophe.guesstimatorString,
|
||||
GuesstimatorDist.logNormal(40., 4.),
|
||||
),
|
||||
~probabilityType=Cdf,
|
||||
~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 = {
|
||||
let make = (currentDateTime: MomentRe.Moment.t) => {
|
||||
let genericDistribution =
|
||||
GenericDistribution.make(
|
||||
~generationSource=GuesstimatorString(guesstimatorString),
|
||||
~probabilityType=Cdf,
|
||||
let distPlusIngredients =
|
||||
DistPlusIngredients.make(
|
||||
~guesstimatorString,
|
||||
~unit=TimeDistribution({zero: currentDateTime, unit: `years}),
|
||||
(),
|
||||
);
|
||||
Prop.Value.GenericDistribution(genericDistribution);
|
||||
Prop.Value.DistPlusIngredients(distPlusIngredients);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -97,6 +97,18 @@ const {
|
|||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param y
|
||||
|
@ -153,6 +165,7 @@ const {
|
|||
pdfToCdf,
|
||||
findY,
|
||||
findX,
|
||||
convertToNewLength,
|
||||
mean,
|
||||
scoreNonMarketCdfCdf,
|
||||
differentialEntropy,
|
||||
|
|
|
@ -5,16 +5,14 @@ module JS = {
|
|||
ys: array(float),
|
||||
};
|
||||
|
||||
let distToJs = (d: DistributionTypes.continuousShape) =>
|
||||
distJs(~xs=d.xs, ~ys=d.ys);
|
||||
let distToJs = (d: DistTypes.xyShape) => distJs(~xs=d.xs, ~ys=d.ys);
|
||||
|
||||
let jsToDist = (d: distJs): DistributionTypes.continuousShape => {
|
||||
let jsToDist = (d: distJs): DistTypes.xyShape => {
|
||||
xs: xsGet(d),
|
||||
ys: ysGet(d),
|
||||
};
|
||||
|
||||
let doAsDist = (f, d: DistributionTypes.continuousShape) =>
|
||||
d |> distToJs |> f |> jsToDist;
|
||||
let doAsDist = (f, d: DistTypes.xyShape) => d |> distToJs |> f |> jsToDist;
|
||||
|
||||
[@bs.module "./CdfLibrary.js"]
|
||||
external cdfToPdf: distJs => distJs = "cdfToPdf";
|
||||
|
@ -34,9 +32,18 @@ module JS = {
|
|||
[@bs.module "./CdfLibrary.js"]
|
||||
external differentialEntropy: (int, distJs) => distJs =
|
||||
"differentialEntropy";
|
||||
|
||||
[@bs.module "./CdfLibrary.js"]
|
||||
external convertToNewLength: (int, distJs) => distJs = "convertToNewLength";
|
||||
};
|
||||
|
||||
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 toCdf = dist => dist |> JS.doAsDist(JS.pdfToCdf);
|
||||
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) =>
|
||||
try (Some(fn(s))) {
|
||||
try(Some(fn(s))) {
|
||||
| _ => None
|
||||
};
|
||||
|
||||
|
@ -216,6 +216,8 @@ module A = {
|
|||
let unsafe_get = Array.unsafe_get;
|
||||
let get = Belt.Array.get;
|
||||
let getBy = Belt.Array.getBy;
|
||||
let last = a => get(a, length(a) - 1);
|
||||
let first = get(_, 0);
|
||||
let hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome;
|
||||
let fold_left = Array.fold_left;
|
||||
let fold_right = Array.fold_right;
|
||||
|
@ -295,27 +297,3 @@ module JsArray = {
|
|||
);
|
||||
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),
|
||||
};
|
||||
|
||||
let jsToDistDiscrete = (d: discrete): DistributionTypes.discreteShape => {
|
||||
let jsToDistDiscrete = (d: discrete): DistTypes.discreteShape => {
|
||||
xs: xsGet(d),
|
||||
ys: ysGet(d),
|
||||
};
|
||||
|
@ -16,28 +16,34 @@ module Internals = {
|
|||
discrete,
|
||||
};
|
||||
|
||||
let toContinous = (r: combined): DistributionTypes.continuousShape =>
|
||||
continuousGet(r) |> CdfLibrary.JS.jsToDist;
|
||||
// todo: Force to be fewer samples
|
||||
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;
|
||||
|
||||
[@bs.module "./GuesstimatorLibrary.js"]
|
||||
external toCombinedFormat: (string, int) => combined = "run";
|
||||
external toCombinedFormat: (string, int, int) => combined = "run";
|
||||
|
||||
let toMixedShape = (r: combined): option(DistributionTypes.mixedShape) => {
|
||||
let assumptions: MixedShapeBuilder.assumptions = {
|
||||
continuous: ADDS_TO_1,
|
||||
discrete: ADDS_TO_CORRECT_PROBABILITY,
|
||||
discreteProbabilityMass: None,
|
||||
};
|
||||
MixedShapeBuilder.build(
|
||||
~continuous=toContinous(r),
|
||||
~discrete=toDiscrete(r),
|
||||
~assumptions,
|
||||
);
|
||||
// todo: Format to correct mass, also normalize the pdf.
|
||||
let toMixedShape = (r: combined): option(DistTypes.shape) => {
|
||||
let continuous =
|
||||
toContinous(r) |> Distributions.Continuous.convertToNewLength(100);
|
||||
let discrete = toDiscrete(r);
|
||||
// let continuousProb =
|
||||
// cont |> Distributions.Continuous.T.Integral.sum(~cache=None);
|
||||
// let discreteProb =
|
||||
// d |> Distributions.Discrete.T.Integral.sum(~cache=None);
|
||||
|
||||
let foo = MixedShapeBuilder.buildSimple(~continuous, ~discrete);
|
||||
foo;
|
||||
};
|
||||
};
|
||||
|
||||
let stringToMixedShape = (~string, ~sampleCount=1000, ()) =>
|
||||
Internals.toCombinedFormat(string, sampleCount) |> Internals.toMixedShape;
|
||||
let stringToMixedShape =
|
||||
(~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 totalLength = _.size(values);
|
||||
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 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;
|
||||
}
|
||||
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 });
|
||||
const { parsedInput } = item;
|
||||
const { guesstimateType } = parsedInput;
|
||||
|
@ -78,7 +78,7 @@ let run = (text, sampleCount, inputs=[], min=false, max=false) => {
|
|||
} else if (values.length === 1) {
|
||||
update = blankResponse;
|
||||
} else {
|
||||
update = toPdf(values, sampleCount, min, max);
|
||||
update = toPdf(values, outputResolutionCount, min, max);
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user