First full-through with symbolic parsing
This commit is contained in:
parent
0e0f0221e9
commit
f662ccd6c6
11
__tests__/Jstat__test.re
Normal file
11
__tests__/Jstat__test.re
Normal 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
22
__tests__/Parser__test.re
Normal 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
5
__tests__/math__test.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const foo = require('./mathtest')
|
||||
|
||||
test('sdf', () => {
|
||||
expect(1).toBe(2)
|
||||
})
|
64
__tests__/mathtest.js
Normal file
64
__tests__/mathtest.js
Normal 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
|
|
@ -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"]
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {(*|*[])[]|*[]}
|
||||
|
|
|
@ -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
183
src/symbolic/Jstat.re
Normal 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)
|
||||
);
|
58
src/symbolic/MathJsParser.re
Normal file
58
src/symbolic/MathJsParser.re
Normal 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
2
src/symbolic/Mathjs.re
Normal file
|
@ -0,0 +1,2 @@
|
|||
[@bs.module "./MathjsWrapper.js"]
|
||||
external parseMath: string => Js.Json.t = "parseMath";
|
8
src/symbolic/MathjsWrapper.js
Normal file
8
src/symbolic/MathjsWrapper.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
const math = require("mathjs");
|
||||
|
||||
function parseMath(f){ return JSON.parse(JSON.stringify(math.parse(f))) };
|
||||
|
||||
module.exports = {
|
||||
parseMath,
|
||||
};
|
14
yarn.lock
14
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user