Merge remote-tracking branch 'origin/master' into feature/1083

This commit is contained in:
Roman Galochkin 2020-02-25 08:15:46 +03:00
commit eaea7cc69a
30 changed files with 1668 additions and 891 deletions

View File

@ -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,
),
),
);
});
});

View File

@ -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, ""),
}
); );
}; };

View File

@ -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>;

View File

@ -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;

View File

@ -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>;

View 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
};

View File

@ -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

View File

@ -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);
} }

View File

@ -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

View File

@ -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 />
};
};

View File

@ -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)
};
};

View File

@ -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
};
};

View File

@ -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))
};
};
};

View 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;
};

View 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);
};

View 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));
};
};
};

View File

@ -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,

View File

@ -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

View 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;
};
};

View File

@ -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) =>

View File

@ -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)]);
}; };

View File

@ -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>;

View File

@ -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}),
(), (),
), ),
) )

View File

@ -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);
}; };
}; };

View File

@ -97,6 +97,18 @@ const {
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);
}
/** /**
* *
* @param y * @param y
@ -153,6 +165,7 @@ const {
pdfToCdf, pdfToCdf,
findY, findY,
findX, findX,
convertToNewLength,
mean, mean,
scoreNonMarketCdfCdf, scoreNonMarketCdfCdf,
differentialEntropy, differentialEntropy,

View File

@ -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);

View File

@ -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;
@ -295,27 +297,3 @@ module JsArray = {
); );
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);
};

View File

@ -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;

View File

@ -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;
} }