First full-through with symbolic parsing

This commit is contained in:
Ozzie Gooen 2020-03-23 21:31:06 +00:00
parent 0e0f0221e9
commit f662ccd6c6
14 changed files with 376 additions and 27 deletions

11
__tests__/Jstat__test.re Normal file
View File

@ -0,0 +1,11 @@
open Jest;
open Expect;
describe("Shape", () => {
describe("Continuous", () => {
test("", () => {
Js.log(Jstat.Jstat.normal);
expect(Jstat.Jstat.normal##pdf(3.0, 3.0, 3.0)) |> toEqual(1.0);
})
})
});

22
__tests__/Parser__test.re Normal file
View File

@ -0,0 +1,22 @@
open Jest;
open Expect;
let json = Mathjs.parseMath("mm(normal(5,2), normal(10))");
describe("Shape", () => {
describe("Parser", () => {
test("", () => {
let parsed1 = MathJsParser.parseMathjs(json);
let parsed2 =
(
switch (parsed1 |> E.O.fmap(MathJsParser.toValue)) {
| Some(Ok(r)) => Some(r)
| _ => None
}
)
|> E.O.fmap(Jstat.toString);
Js.log2("YOYOYYO", parsed2);
expect(1.0) |> toEqual(1.0);
})
})
});

5
__tests__/math__test.js Normal file
View File

@ -0,0 +1,5 @@
const foo = require('./mathtest')
test('sdf', () => {
expect(1).toBe(2)
})

64
__tests__/mathtest.js Normal file
View File

@ -0,0 +1,64 @@
// This example demonstrates importing a custom data type,
// and extending an existing function (add) with support for this data type.
const { create, factory, all } = require('mathjs');
const math = create(all)
// factory function which defines a new data type FunctionalDistribution
const createFunctionalDistribution = factory('FunctionalDistribution', ['typed'], ({ typed }) => {
// create a new data type
function FunctionalDistribution (value) {
this.value = value
}
FunctionalDistribution.prototype.isFunctionalDistribution = true
FunctionalDistribution.prototype.toString = function () {
return 'FunctionalDistribution:' + this.value
}
// define a new data type with typed-function
typed.addType({
name: 'FunctionalDistribution',
test: function (x) {
// test whether x is of type FunctionalDistribution
return x && x.isFunctionalDistribution === true
}
})
return FunctionalDistribution
})
// function add which can add the FunctionalDistribution data type
// When imported in math.js, the existing function `add` with support for
// FunctionalDistribution, because both implementations are typed-functions and do not
// have conflicting signatures.
const createAddFunctionalDistribution = factory('add', ['typed', 'FunctionalDistribution'], ({ typed, FunctionalDistribution }) => {
return typed('add', {
'FunctionalDistribution, FunctionalDistribution': function (a, b) {
return new FunctionalDistribution(a.value + b.value)
}
})
})
const createSubtractFunctionalDistribution = factory('subtract', ['typed', 'FunctionalDistribution'], ({ typed, FunctionalDistribution }) => {
return typed('subtract', {
'FunctionalDistribution, FunctionalDistribution': function (a, b) {
return new FunctionalDistribution(a.value - b.value)
}
})
})
// import the new data type and function
math.import([
createFunctionalDistribution,
createAddFunctionalDistribution,
createSubtractFunctionalDistribution
])
// use the new type
const ans = math.chain(new math.FunctionalDistribution(2)).subtract(new math.FunctionalDistribution(3))
// ans = FunctionalDistribution(5)
console.log(ans.toString())
// outputs 'FunctionalDistribution:5'
module.exports = ans

View File

@ -3,7 +3,8 @@
"reason": {
"react-jsx": 3
},
"sources": [{
"sources": [
{
"dir": "src",
"subdirs": true
},
@ -19,14 +20,17 @@
}
],
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header"],
"package-specs": [{
"module": "commonjs",
"in-source": true
}],
"package-specs": [
{
"module": "commonjs",
"in-source": true
}
],
"suffix": ".bs.js",
"namespace": true,
"bs-dependencies": [
"@glennsl/bs-jest",
"@glennsl/bs-json",
"@foretold/components",
"bs-ant-design-alt",
"reason-react",
@ -37,7 +41,5 @@
"reschema"
],
"refmt": 3,
"ppx-flags": [
"lenses-ppx/ppx"
]
}
"ppx-flags": ["lenses-ppx/ppx"]
}

View File

@ -29,6 +29,7 @@
"@foretold/components": "0.0.3",
"@foretold/guesstimator": "1.0.10",
"@glennsl/bs-jest": "^0.5.0",
"@glennsl/bs-json": "^5.0.2",
"antd": "3.17.0",
"autoprefixer": "9.7.4",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
@ -65,4 +66,4 @@
"react": "./node_modules/react",
"react-dom": "./node_modules/react-dom"
}
}
}

View File

@ -25,7 +25,7 @@ class BaseDistributionBinned {
this.max_bin_size = 0.005;
this.min_bin_size = 0;
this.increment = 0.0001;
this.desired_delta = 0.0001;
this.desired_delta = 0.001;
this.start_bin_size = 0.0001;
[this.params, this.pdf_func, this.sample] = this.get_params_and_pdf_func(
@ -44,6 +44,8 @@ class BaseDistributionBinned {
throw new Error("NotImplementedError");
}
//Adaptive binning. Specify a desired change in density to get adjusted bin sizes.
/**
* @returns {(number[]|[*])[]}
* @private

View File

@ -8,6 +8,7 @@ const math = _math.create(_math.all);
const NUM_MC_SAMPLES = 3000;
const OUTPUT_GRID_NUMEL = 3000;
/**
* The main algorithmic work is done by functions in this module.
* It also contains the main function, taking the user's string
@ -290,6 +291,7 @@ function pluck_from_array(array, idx) {
* If distr_string requires MC, try all possible
* choices for the deterministic distribution,
* and pick the one with the least variance.
* It's much better to sample from a normal than a lognormal.
*
* @param distr_string
* @returns {(*|*[])[]|*[]}

View File

@ -200,13 +200,6 @@ module T = {
| (true, true) => (-1)
};
// todo: This is broken :(
let combine = (t1: t, t2: t) => {
let array = Belt.Array.concat(zip(t1), zip(t2));
Array.fast_sort(comparePoints, array);
array |> Belt.Array.unzip |> fromArray;
};
// TODO: I'd bet this is pretty slow
let intersperce = (t1: t, t2: t) => {
let items: ref(array((float, float))) = ref([||]);

183
src/symbolic/Jstat.re Normal file
View File

@ -0,0 +1,183 @@
// Todo: Another way of doing this is with [@bs.scope "normal"], which may be more elegant
module Jstat = {
type normal = {
.
[@bs.meth] "pdf": (float, float, float) => float,
[@bs.meth] "cdf": (float, float, float) => float,
[@bs.meth] "inv": (float, float, float) => float,
[@bs.meth] "sample": (float, float) => float,
};
type lognormal = {
.
[@bs.meth] "pdf": (float, float, float) => float,
[@bs.meth] "cdf": (float, float, float) => float,
[@bs.meth] "inv": (float, float, float) => float,
[@bs.meth] "sample": (float, float) => float,
};
type uniform = {
.
[@bs.meth] "pdf": (float, float, float) => float,
[@bs.meth] "cdf": (float, float, float) => float,
[@bs.meth] "inv": (float, float, float) => float,
[@bs.meth] "sample": (float, float) => float,
};
[@bs.module "jStat"] external normal: normal = "normal";
[@bs.module "jStat"] external lognormal: lognormal = "lognormal";
[@bs.module "jStat"] external uniform: uniform = "uniform";
};
type normal = {
mean: float,
stdev: float,
};
type lognormal = {
mu: float,
sigma: float,
};
type uniform = {
low: float,
high: float,
};
module Normal = {
type t = normal;
let pdf = (x, t: t) => Jstat.normal##pdf(x, t.mean, t.stdev);
let inv = (p, t: t) => Jstat.normal##inv(p, t.mean, t.stdev);
let sample = (t: t) => Jstat.normal##sample(t.mean, t.stdev);
let toString = ({mean, stdev}: t) => {j|Normal($mean,$stdev)|j};
};
module Lognormal = {
type t = lognormal;
let pdf = (x, t: t) => Jstat.lognormal##pdf(x, t.mu, t.sigma);
let inv = (p, t: t) => Jstat.lognormal##inv(p, t.mu, t.sigma);
let sample = (t: t) => Jstat.lognormal##sample(t.mu, t.sigma);
let toString = ({mu, sigma}: t) => {j|Lognormal($mu,$sigma)|j};
};
module Uniform = {
type t = uniform;
let pdf = (x, t: t) => Jstat.uniform##pdf(x, t.low, t.high);
let inv = (p, t: t) => Jstat.uniform##inv(p, t.low, t.high);
let sample = (t: t) => Jstat.uniform##sample(t.low, t.high);
let toString = ({low, high}: t) => {j|Uniform($low,$high)|j};
};
type dist = [
| `Normal(normal)
| `Lognormal(lognormal)
| `Uniform(uniform)
];
module Mixed = {
let pdf = (x, dist) =>
switch (dist) {
| `Normal(n) => Normal.pdf(x, n)
| `Lognormal(n) => Lognormal.pdf(x, n)
| `Uniform(n) => Uniform.pdf(x, n)
};
let inv = (x, dist) =>
switch (dist) {
| `Normal(n) => Normal.inv(x, n)
| `Lognormal(n) => Lognormal.inv(x, n)
| `Uniform(n) => Uniform.inv(x, n)
};
let sample = dist =>
switch (dist) {
| `Normal(n) => Normal.sample(n)
| `Lognormal(n) => Lognormal.sample(n)
| `Uniform(n) => Uniform.sample(n)
};
let toString = dist =>
switch (dist) {
| `Normal(n) => Normal.toString(n)
| `Lognormal(n) => Lognormal.toString(n)
| `Uniform(n) => Uniform.toString(n)
};
let min = dist =>
switch (dist) {
| `Normal(n) => Normal.inv(0.01, n)
| `Lognormal(n) => Lognormal.inv(0.01, n)
| `Uniform({low}) => low
};
let max = dist =>
switch (dist) {
| `Normal(n) => Normal.inv(0.99, n)
| `Lognormal(n) => Lognormal.inv(0.99, n)
| `Uniform({high}) => high
};
// will space linear
let toShape =
(~xSelection: [ | `Linear | `ByWeight]=`Linear, dist: dist, sampleCount) => {
let xs =
switch (xSelection) {
| `Linear => Functions.range(min(dist), max(dist), sampleCount)
| `ByWeight =>
Functions.range(0.001, 0.999, sampleCount)
|> E.A.fmap(x => inv(x, dist))
};
let ys = xs |> E.A.fmap(r => pdf(r, dist));
XYShape.T.fromArrays(xs, ys);
};
};
// module PointwiseCombination = {
// type math = Multiply | Add | Exponent | Power;
// let fn = fun
// | Multiply => 3.0
// | Add => 4.0
// }
module PointwiseAddDistributionsWeighted = {
type t = array((dist, float));
let normalizeWeights = (dists: t) => {
let total = dists |> E.A.fmap(snd) |> Functions.sum;
dists |> E.A.fmap(((a, b)) => (a, b /. total));
};
let pdf = (dists: t, x: float) =>
dists |> E.A.fmap(((e, w)) => Mixed.pdf(x, e) *. w) |> Functions.sum;
let min = (dists: t) =>
dists |> E.A.fmap(d => d |> fst |> Mixed.min) |> Functions.min;
let max = (dists: t) =>
dists |> E.A.fmap(d => d |> fst |> Mixed.min) |> Functions.min;
let toShape = (dists: t, sampleCount: int) => {
let xs = Functions.range(min(dists), max(dists), sampleCount);
let ys = xs |> E.A.fmap(pdf(dists));
XYShape.T.fromArrays(xs, ys);
};
let toString = (dists: t) => {
let distString =
dists
|> E.A.fmap(d => Mixed.toString(fst(d)))
|> Js.Array.joinWith(",");
{j|pointwideAdded($distString)|j};
};
};
type bigDist = [
| `Dist(dist)
| `PointwiseCombination(PointwiseAddDistributionsWeighted.t)
];
let toString = (r: bigDist) =>
r
|> (
fun
| `Dist(d) => Mixed.toString(d)
| `PointwiseCombination(d) =>
PointwiseAddDistributionsWeighted.toString(d)
);

View File

@ -0,0 +1,58 @@
open Jstat;
type arg =
| Value(float)
| Fn(fn)
and fn = {
name: string,
args: array(arg),
};
let rec parseMathjs = (j: Js.Json.t) => {
Json.Decode.(
switch (field("mathjs", string, j)) {
| "FunctionNode" =>
let args = j |> field("args", array(parseMathjs));
Some(
Fn({
name: j |> field("fn", field("name", string)),
args: args |> E.A.O.concatSomes,
}),
);
| "ConstantNode" => Some(Value(field("value", float, j)))
| _ => None
}
);
};
let normal = (r): result(bigDist, string) =>
r
|> (
fun
| [|Value(mean), Value(stdev)|] => Ok(`Dist(`Normal({mean, stdev})))
| _ => Error("Wrong number of variables in normal distribution")
);
let rec toValue = (r): result(bigDist, string) =>
r
|> (
fun
| Value(_) => Error("Top level can't be value")
| Fn({name: "normal", args}) => normal(args)
| Fn({name: "mm", args}) => {
let dists: array(dist) =
args
|> E.A.fmap(toValue)
|> E.A.fmap(
fun
| Ok(`Dist(`Normal({mean, stdev}))) =>
Some(`Normal({mean, stdev}))
| _ => None,
)
|> E.A.O.concatSomes;
let inputs = dists |> E.A.fmap(r => (r, 1.0));
Ok(`PointwiseCombination(inputs));
}
| Fn({name}) => Error(name ++ ": name not found")
);

2
src/symbolic/Mathjs.re Normal file
View File

@ -0,0 +1,2 @@
[@bs.module "./MathjsWrapper.js"]
external parseMath: string => Js.Json.t = "parseMath";

View File

@ -0,0 +1,8 @@
const math = require("mathjs");
function parseMath(f){ return JSON.parse(JSON.stringify(math.parse(f))) };
module.exports = {
parseMath,
};

View File

@ -1017,15 +1017,6 @@
lodash "4.17.15"
pdfast "0.2.0"
"@foretold/cdf@1.0.15":
version "1.0.15"
resolved "https://registry.yarnpkg.com/@foretold/cdf/-/cdf-1.0.15.tgz#69ce4755158693e3d325e7be10d0aa9cdb465730"
integrity sha512-I7GhFQd4HaFd+tGD1IJ0W8xvFp2YiJdcFiXSCq9vYQZWWy+Npi4QOYsMoDJyoUTvOlVba4ARa/pDKPD2hn+uuQ==
dependencies:
lodash "4.17.15"
parcel "1.12.3"
pdfast "0.2.0"
"@foretold/components@0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@foretold/components/-/components-0.0.3.tgz#a195912647499735f64cb2b74f722eee4b2da13f"
@ -1081,6 +1072,11 @@
dependencies:
jest "^25.1.0"
"@glennsl/bs-json@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@glennsl/bs-json/-/bs-json-5.0.2.tgz#cfb85d94d370ec6dc17849e0ddb1a51eee08cfcc"
integrity sha512-vVlHJNrhmwvhyea14YiV4L5pDLjqw1edE3GzvMxlbPPQZVhzgO3sTWrUxCpQd2gV+CkMfk4FHBYunx9nWtBoDg==
"@iarna/toml@^2.2.0":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab"