Merge remote-tracking branch 'origin/improvements/1080' into improvements/1080

This commit is contained in:
Roman Galochkin 2020-02-19 15:06:57 +03:00
commit 6e64453a2f
11 changed files with 2597 additions and 95 deletions

View File

@ -0,0 +1,26 @@
open Jest;
open Expect;
let shape: DistributionTypes.xyShape = {
xs: [|1., 4., 8.|],
ys: [|8., 9., 2.|],
};
open Shape;
describe("Shape", () =>
describe("XYShape", () =>
test("#ySum", ()
=>
expect(XYShape.ySum(shape)) |> toEqual(19.0)
)
// test("#both", () => {
// let expected: DistributionTypes.xyShape = {
// xs: [|1., 4., 8.|],
// ys: [|8., 1., 1.|],
// };
// expect(shape |> XYShape.derivative |> XYShape.integral)
// |> toEqual(shape);
// });
)
);

View File

@ -11,6 +11,11 @@
"dir": "showcase",
"type": "dev",
"subdirs": true
},
{
"dir": "__tests__",
"type": "dev",
"subdirs": true
}
],
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header"],
@ -21,6 +26,7 @@
"suffix": ".bs.js",
"namespace": true,
"bs-dependencies": [
"@glennsl/bs-jest",
"@foretold/components",
"bs-ant-design-alt",
"reason-react",

View File

@ -13,7 +13,10 @@
"server": "moduleserve ./ --port 8000",
"predeploy": "parcel build ./src/index.html --no-source-maps --no-autoinstall",
"deploy": "gh-pages -d dist",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest",
"test:ci": "yarn jest",
"watch:test": "jest --watchAll",
"watch:s": "yarn jest -- Converter_test --watch"
},
"keywords": [
"BuckleScript",
@ -28,12 +31,14 @@
"@foretold/guesstimator": "1.0.10",
"antd": "3.17.0",
"autoprefixer": "9.7.4",
"babel-jest": "25.1.0",
"bs-ant-design-alt": "2.0.0-alpha.31",
"bs-css": "11.0.0",
"bs-moment": "0.4.4",
"bs-reform": "9.7.1",
"d3": "5.15.0",
"lenses-ppx": "5.1.0",
"jest": "^25.1.0",
"less": "3.10.3",
"lodash": "4.17.15",
"moment": "2.24.0",
@ -55,4 +60,4 @@
"react": "./node_modules/react",
"react-dom": "./node_modules/react-dom"
}
}
}

View File

@ -5,13 +5,16 @@ let data: DistributionTypes.xyShape = {
let mixedDist =
GenericDistribution.make(
~generationSource=GuesstimatorString("mm(3, normal(5,1), [.5,.5])"),
~generationSource=
GuesstimatorString(
"mm(floor(uniform(20, 30)), normal(50,10), [.5,.5])",
),
~probabilityType=Pdf,
~domain=Complete,
~unit=UnspecifiedDistribution,
(),
)
|> GenericDistribution.renderIfNeeded(~sampleCount=3000);
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
let timeDist =
GenericDistribution.make(
@ -21,7 +24,7 @@ let timeDist =
~unit=TimeDistribution({zero: MomentRe.momentNow(), unit: `years}),
(),
)
|> GenericDistribution.renderIfNeeded(~sampleCount=3000);
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
let domainLimitedDist =
GenericDistribution.make(
@ -31,7 +34,7 @@ let domainLimitedDist =
~unit=UnspecifiedDistribution,
(),
)
|> GenericDistribution.renderIfNeeded(~sampleCount=3000);
|> GenericDistribution.renderIfNeeded(~sampleCount=1000);
let distributions = () =>
<div>
@ -39,14 +42,14 @@ let distributions = () =>
<h2> {"Basic Mixed Distribution" |> ReasonReact.string} </h2>
<GenericDistributionChart dist=mixedDist />
</div>
<div>
<h2> {"Time Distribution" |> ReasonReact.string} </h2>
<GenericDistributionChart dist=timeDist />
</div>
<div>
<h2> {"Domain Limited Distribution" |> ReasonReact.string} </h2>
<GenericDistributionChart dist=domainLimitedDist />
</div>
</div>;
// <div>
// <h2> {"Time Distribution" |> ReasonReact.string} </h2>
// <GenericDistributionChart dist=timeDist />
// </div>
// <div>
// <h2> {"Domain Limited Distribution" |> ReasonReact.string} </h2>
// <GenericDistributionChart dist=domainLimitedDist />
// </div>
let entry = EntryTypes.(entry(~title="Pdf", ~render=distributions));

View File

@ -1,6 +1,8 @@
module Continuous = {
[@react.component]
let make = (~data, ~unit) => {
let make = (~data, ~
) => {
let (x, setX) = React.useState(() => 0.);
let timeScale = unit |> DistributionTypes.DistributionUnit.toJson;
let chart =
@ -38,7 +40,11 @@ module Continuous = {
|> ReasonReact.string}
</th>
<th className="px-4 py-2 border ">
{Shape.Continuous.findY(x, Shape.XYShape.integral(data))
{Shape.Continuous.findY(
x,
Shape.XYShape.Range.integrateWithTriangles(data)
|> E.O.toExt(""),
)
|> E.Float.with2DigitsPrecision
|> ReasonReact.string}
</th>
@ -50,6 +56,51 @@ module Continuous = {
};
};
module Mixed = {
[@react.component]
let make = (~data: DistributionTypes.mixedShape) => {
let (x, setX) = React.useState(() => 0.);
let chart =
React.useMemo1(
() =>
<CdfChart__Plain
data={data.continuous}
color={`hex("333")}
onHover={r => setX(_ => r)}
/>,
[|data|],
);
<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 Pount" |> 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 ">
{Shape.Mixed.getYIntegral(x, data)
|> E.O.fmap(E.Float.with2DigitsPrecision)
|> E.O.default("")
|> ReasonReact.string}
</th>
</tr>
</tbody>
</table>
<div />
</div>;
};
};
[@react.component]
let make = (~dist) => {
switch ((dist: option(DistributionTypes.genericDistribution))) {
@ -66,6 +117,40 @@ let make = (~dist) => {
}) =>
<div>
<Continuous data={n |> Shape.Continuous.toPdf} unit />
<Continuous
unit
data={
n
|> Shape.XYShape.Range.integrateWithTriangles
|> E.O.toExt("")
|> Shape.XYShape.scaleCdfTo
}
/>
<Mixed
data={
continuous:
n
|> Shape.Continuous.toCdf
|> E.O.toExt("")
|> Shape.XYShape.scaleCdfTo
|> Shape.Continuous.toPdf
|> E.O.toExt(""),
discrete: d,
discreteProbabilityMassFraction: f,
}
/>
<Continuous
unit
data={
n
|> Shape.XYShape.Range.integrateWithTriangles
|> E.O.toExt("")
|> Shape.XYShape.Range.derivative
|> E.O.toExt("")
|> Shape.XYShape.Range.integrateWithTriangles
|> E.O.toExt("")
}
/>
{d |> Shape.Discrete.scaleYToTotal(f) |> Shape.Discrete.render}
</div>
| _ => <div />

View File

@ -32,4 +32,17 @@ let renderIfNeeded =
);
| Shape(_) => Some(t)
};
};
} /* }*/;
// let getTimeY =
// (t: genericDistribution, point: TimeTypes.RelativeTimePoint.timeInVector) => {
// switch (t) {
// | {
// generationSource: Shape(shape),
// probabilityType: Pdf,
// unit: Time(timeVector),
// } =>
// TimeTypes.RelativeTimePoint.toXValue(timeVector, point)
// |> E.O.fmap(x => Shape.Mixed.getY(t, x))
// | _ => 2.0
// };

View File

@ -6,6 +6,10 @@ let _lastElement = (a: array('a)) =>
| n => Belt.Array.get(a, n - 1)
};
type pointInRange =
| Unbounded
| X(float);
module XYShape = {
type t = xyShape;
@ -15,39 +19,103 @@ module XYShape = {
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, p: t) => {
let (xs, ys) =
Belt.Array.zip(p.xs, p.ys)
->Belt.Array.reduce([||], (items, (x, y)) =>
switch (_lastElement(items)) {
| Some((_, yLast)) =>
Belt.Array.concat(items, [|(x, fn(y, yLast))|])
| None => [|(x, y)|]
}
)
|> Belt.Array.unzip;
fromArrays(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 integral = transverse((aCurrent, aLast) => aCurrent +. aLast);
let derivative = transverse((aCurrent, aLast) => aCurrent -. aLast);
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 toStepFn = z => mapYsBasedOnRanges(rangeAreaAssumingSteps, z);
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;
};
// let massWithin = (t: t, left: pointInRange, right: pointInRange) => {
// switch (left, right) {
// | (Unbounded, Unbounded) => t |> ySum
// | (Unbounded, X(f)) => t |> integral |> getY(t, 3.0)
// | (X(f), Unbounded) => ySum(t) -. getY(integral(t), f)
// | (X(l), X(r)) => getY(integral(t), r) -. getY(integral(t), l)
// };
// };
module Continuous = {
let fromArrays = XYShape.fromArrays;
let toJs = XYShape.toJs;
let toPdf = CdfLibrary.Distribution.toPdf;
let toCdf = CdfLibrary.Distribution.toCdf;
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));
};
module Discrete = {
@ -79,6 +147,18 @@ module Discrete = {
| Some((_, y)) => y
| None => 0.
};
let integrate = XYShape.accumulateYs;
let derivative = XYShape.subtractYs;
let findIntegralY = (f, t: t) =>
t
|> XYShape.Range.toStepFn
|> E.O.fmap(XYShape.accumulateYs)
|> E.O.fmap(CdfLibrary.Distribution.findY(f));
let findX = (f, t: t) =>
t |> XYShape.Range.toStepFn |> E.O.fmap(CdfLibrary.Distribution.findX(f));
};
module Mixed = {
@ -88,15 +168,53 @@ module Mixed = {
discreteProbabilityMassFraction,
};
let mixedMultiply =
(
t: DistributionTypes.mixedShape,
continuousComponent,
discreteComponent,
) => {
let diffFn = t.discreteProbabilityMassFraction;
continuousComponent *. (1.0 -. diffFn) +. discreteComponent *. diffFn;
};
type yPdfPoint = {
continuous: float,
discrete: float,
continuous: option(float),
discrete: option(float),
discreteProbabilityMassFraction: float,
};
let getY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => {
continuous: Continuous.findY(x, t.continuous),
discrete: Discrete.findY(x, t.discrete),
continuous: Continuous.findY(x, t.continuous) |> E.O.some,
discrete: Discrete.findY(x, t.discrete) |> E.O.some,
discreteProbabilityMassFraction: t.discreteProbabilityMassFraction,
};
let getYIntegral =
(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), Some(d)) => Some(mixedMultiply(t, c, d))
| _ => None
};
};
//Do the math to add these distributions together
// let integral =
// (x: float, t: DistributionTypes.mixedShape): option(XYShape.t) => {
// };
};
module Any = {
type t = DistributionTypes.pointsType;
let x = (t: t, x: float) =>
switch (t) {
| Mixed(m) => `mixed(Mixed.getY(m, x))
| Discrete(discreteShape) => `discrete(Discrete.findY(x, discreteShape))
| Continuous(continuousShape) =>
`continuous(Continuous.findY(x, continuousShape))
};
};
module DomainMixed = {

View File

@ -114,6 +114,11 @@ module R = {
let id = e => e |> result(U.id, U.id);
let fmap = Rationale.Result.fmap;
let bind = Rationale.Result.bind;
let toOption = (e: Belt.Result.t('a, 'b)) =>
switch (e) {
| Ok(r) => Some(r)
| Error(_) => None
};
};
let safe_fn_of_string = (fn, s: string): option('a) =>
@ -217,6 +222,20 @@ module A = {
let concatMany = Belt.Array.concatMany;
let keepMap = Belt.Array.keepMap;
let stableSortBy = Belt.SortArray.stableSortBy;
let toRanges = (a: array('a)) =>
switch (a |> Belt.Array.length) {
| 0
| 1 => Belt.Result.Error("Must be at least 2 elements")
| n =>
Belt.Array.makeBy(n - 1, r => r)
|> Belt.Array.map(_, index =>
(
Belt.Array.getUnsafe(a, index),
Belt.Array.getUnsafe(a, index + 1),
)
)
|> Rationale.Result.return
};
let asList = (f: list('a) => list('a), r: array('a)) =>
r |> to_list |> f |> of_list;

View File

@ -126,12 +126,14 @@ module Model = {
currentDateTime,
yearlyMeanGrowthRateIfNotClosed(group),
);
let str =
switch (xRisk) {
| Some({truthValue: true}) => "0"
| Some({truthValue: false}) => difference
| None => "uniform(0,1) > .3 ? " ++ difference ++ ": 0"
};
let genericDistribution =
GenericDistribution.make(
~generationSource=GuesstimatorString(str),

View File

@ -48,8 +48,8 @@ const toPdf = (values, sampleCount, min, max) => {
const ratioSize$ = ratioSize(samples);
const width = ratioSize$ === 'SMALL' ? 20 : 1;
const cdf = samples.toCdf({ size: sampleCount, width, min, max });
continuous = cdf;
const pdf = samples.toPdf({ size: sampleCount, width, min, max });
continuous = pdf;
}
return {continuous, discrete};
};

2337
yarn.lock

File diff suppressed because it is too large Load Diff