diff --git a/src/components/DistBuilder.re b/src/components/DistBuilder.re
index c2a01b86..a34a71b3 100644
--- a/src/components/DistBuilder.re
+++ b/src/components/DistBuilder.re
@@ -45,18 +45,20 @@ module FieldString = {
module FieldNumber = {
[@react.component]
- let make = (~field, ~label) => {
+ let make = (~field, ~label, ~min=0) => {
E.ste}>
{
- e |> handleChange;
- ();
- }}
+ onChange=handleChange
+ min
onBlur={_ => validate()}
+ parser={str => {
+ let a = str |> Js.Float.fromString |> int_of_float;
+ a < min ? min : a;
+ }}
/>
}
@@ -66,19 +68,22 @@ module FieldNumber = {
module FieldFloat = {
[@react.component]
- let make = (~field, ~label, ~className=Css.style([])) => {
+ let make =
+ (~field, ~label, ~className=Css.style([]), ~min=0., ~precision=2) => {
E.ste}>
{
- e |> handleChange;
- ();
- }}
+ precision
+ onChange=handleChange
onBlur={_ => validate()}
className
+ parser={str => {
+ let a = str |> Js.Float.fromString;
+ Js.Float.isNaN(a) ? min : a;
+ }}
/>
}
@@ -440,16 +445,25 @@ let make = () => {
-
+
-
+
{
- let (ys, xs) = DistEditor.getPdfFromUserInput(guesstimatorString);
+ let (ys, xs, isEmpty) =
+ DistEditor.getPdfFromUserInput(guesstimatorString);
let continuous: DistTypes.xyShape = {xs, ys};
E.ste}>
-
+ {isEmpty
+ ? "Nothing to show. Try to change the distribution description."
+ |> E.ste
+ : }
;
};
};
diff --git a/src/components/charts/DistributionPlot/distPlotD3.js b/src/components/charts/DistributionPlot/distPlotD3.js
index 73f0caa1..5b1b3c31 100644
--- a/src/components/charts/DistributionPlot/distPlotD3.js
+++ b/src/components/charts/DistributionPlot/distPlotD3.js
@@ -68,14 +68,18 @@ export class CdfChartD3 {
* @returns {CdfChartD3}
*/
data(data) {
+ const continuousXs = _.get(data, 'continuous.xs', []);
+ const continuousYs = _.get(data, 'continuous.ys', []);
+ const discreteXs = _.get(data, 'discrete.xs', []);
+ const discreteYs = _.get(data, 'discrete.ys', []);
this.attrs.data = data;
- this.attrs.data.continuous = data.continuous || {
- xs: [],
- ys: [],
+ this.attrs.data.continuous = {
+ xs: continuousXs,
+ ys: continuousYs,
};
- this.attrs.data.discrete = data.discrete || {
- xs: [],
- ys: [],
+ this.attrs.data.discrete = {
+ xs: discreteXs,
+ ys: discreteYs,
};
return this;
}
diff --git a/src/components/editor/DistEditor.re b/src/components/editor/DistEditor.re
index ea315d11..cf6b8f52 100644
--- a/src/components/editor/DistEditor.re
+++ b/src/components/editor/DistEditor.re
@@ -1,3 +1,3 @@
[@bs.module "./main.js"]
-external getPdfFromUserInput: string => (array(float), array(float)) =
+external getPdfFromUserInput: string => (array(float), array(float), bool) =
"get_pdf_from_user_input";
diff --git a/src/components/editor/distribution.js b/src/components/editor/distribution.js
index c16c1fd5..8c4be312 100644
--- a/src/components/editor/distribution.js
+++ b/src/components/editor/distribution.js
@@ -1,11 +1,14 @@
-// This module defines an abstract BinnedDistribution class, which
-// should be implemented for each distribution. You need to decide
-// how to bin the distribution (use _adabin unless there's a nicer
-// way for your distr) and how to choose the distribution's support.
const _math = require("mathjs");
const math = _math.create(_math.all);
const jStat = require("jstat");
+/**
+ * This module defines an abstract BinnedDistribution class, which
+ * should be implemented for each distribution. You need to decide
+ * how to bin the distribution (use _adabin unless there's a nicer
+ * way for your distr) and how to choose the distribution's support.
+ */
+
math.import({
normal: jStat.normal,
beta: jStat.beta,
@@ -14,6 +17,9 @@ math.import({
});
class BaseDistributionBinned {
+ /**
+ * @param args
+ */
constructor(args) {
this._set_props();
this.max_bin_size = 0.5;
@@ -30,11 +36,18 @@ class BaseDistributionBinned {
[this.pdf_vals, this.divider_pts] = this.bin();
}
+ /**
+ * this is hacky but class properties aren't always supported
+ * @private
+ */
_set_props() {
- // this is hacky but class properties aren't always supported
throw new Error("NotImplementedError");
}
+ /**
+ * @returns {(number[]|[*])[]}
+ * @private
+ */
_adabin() {
let point = this.start_point;
let vals = [this.pdf_func(point)];
@@ -78,6 +91,10 @@ class BaseDistributionBinned {
throw new Error("NotImplementedError");
}
+ /**
+ * @param args
+ * @returns {(any|(function(*=): *))[]}
+ */
get_params_and_pdf_func(args) {
let args_str = args.toString() + ")";
let substr = this.name + ".pdf(x, " + args_str;
@@ -95,11 +112,17 @@ class BaseDistributionBinned {
}
class NormalDistributionBinned extends BaseDistributionBinned {
+ /**
+ * @private
+ */
_set_props() {
this.name = "normal";
this.param_names = ["mean", "std"];
}
+ /**
+ * @returns {(number|*)[]}
+ */
get_bounds() {
return [
this.params.mean - 4 * this.params.std,
@@ -107,22 +130,34 @@ class NormalDistributionBinned extends BaseDistributionBinned {
];
}
+ /**
+ * @returns {[[*], [*]]}
+ */
bin() {
return this._adabin(this.params.std);
}
}
class UniformDistributionBinned extends BaseDistributionBinned {
+ /**
+ * @private
+ */
_set_props() {
this.name = "uniform";
this.param_names = ["start_point", "end_point"];
this.num_bins = 200;
}
+ /**
+ * @returns {*[]}
+ */
get_bounds() {
return [this.params.start_point, this.params.end_point];
}
+ /**
+ * @returns {(*[])[]}
+ */
bin() {
let divider_pts = evenly_spaced_grid(
this.params.start_point,
@@ -138,6 +173,9 @@ class UniformDistributionBinned extends BaseDistributionBinned {
}
class LogNormalDistributionBinned extends BaseDistributionBinned {
+ /**
+ * @private
+ */
_set_props() {
this.name = "lognormal";
this.param_names = ["normal_mean", "normal_std"];
@@ -145,6 +183,12 @@ class LogNormalDistributionBinned extends BaseDistributionBinned {
this.n_largest_bound_sample = 10;
}
+ /**
+ * @param samples
+ * @param n
+ * @returns {any}
+ * @private
+ */
_nth_largest(samples, n) {
var largest_buffer = Array(n).fill(-Infinity);
for (const sample of samples) {
@@ -159,6 +203,9 @@ class LogNormalDistributionBinned extends BaseDistributionBinned {
return largest_buffer[n - 1];
}
+ /**
+ * @returns {(*|any)[]}
+ */
get_bounds() {
let samples = Array(this.n_bounds_samples)
.fill(0)
@@ -169,11 +216,20 @@ class LogNormalDistributionBinned extends BaseDistributionBinned {
];
}
+ /**
+ * @returns {[[*], [*]]}
+ */
bin() {
return this._adabin();
}
}
+/**
+ * @param start
+ * @param stop
+ * @param numel
+ * @returns {*[]}
+ */
function evenly_spaced_grid(start, stop, numel) {
return Array(numel)
.fill(0)
diff --git a/src/components/editor/main.js b/src/components/editor/main.js
index c44b314b..0f271d74 100644
--- a/src/components/editor/main.js
+++ b/src/components/editor/main.js
@@ -1,30 +1,57 @@
-// The main algorithmic work is done by functions in this module.
-// It also contains the main function, taking the user's string
-// and returning pdf values and x's.
+const _math = require("mathjs");
+const bst = require("binary-search-tree");
const distrs = require("./distribution.js").distrs;
const parse = require("./parse.js");
-const _math = require("mathjs");
const math = _math.create(_math.all);
-const bst = require("binary-search-tree");
const NUM_MC_SAMPLES = 300;
const OUTPUT_GRID_NUMEL = 300;
+/**
+ * The main algorithmic work is done by functions in this module.
+ * It also contains the main function, taking the user's string
+ * and returning pdf values and x's.
+ */
+
+/**
+ * @param start
+ * @param stop
+ * @param numel
+ * @returns {*[]}
+ */
function evenly_spaced_grid(start, stop, numel) {
return Array(numel)
.fill(0)
.map((_, idx) => start + (idx / numel) * (stop - start));
}
+/**
+ * Takes an array of strings like "normal(0, 1)" and
+ * returns the corresponding distribution objects
+ * @param substrings
+ * @returns {*}
+ */
function get_distributions(substrings) {
- // Takes an array of strings like "normal(0, 1)" and
- // returns the corresponding distribution objects
let names_and_args = substrings.map(parse.get_distr_name_and_args);
let pdfs = names_and_args.map(x => new distrs[x[0]](x[1]));
return pdfs;
}
+/**
+ * update the binary search tree with bin points of
+ * deterministic_pdf transformed by tansform func
+ * (transfrom func can be a stocahstic func with parameters
+ * sampled from mc_distrs)
+ *
+ * @param transform_func
+ * @param deterministic_pdf
+ * @param mc_distrs
+ * @param track_idx
+ * @param num_mc_samples
+ * @param bst_pts_and_idxs
+ * @returns {(number)[]}
+ */
function update_transformed_divider_points_bst(
transform_func,
deterministic_pdf,
@@ -33,10 +60,6 @@ function update_transformed_divider_points_bst(
num_mc_samples,
bst_pts_and_idxs
) {
- // update the binary search tree with bin points of
- // deterministic_pdf transformed by tansform func
- // (transfrom func can be a stocahstic func with parameters
- // sampled from mc_distrs)
var transformed_pts = [];
var pdf_inner_idxs = [];
var factors = [];
@@ -97,10 +120,17 @@ function update_transformed_divider_points_bst(
return [start_pt, end_pt];
}
+/**
+ * Take the binary search tree with transformed bin points,
+ * and an array of pdf values associated with the bins,
+ * and return a pdf over an evenly spaced grid
+ *
+ * @param pdf_vals
+ * @param bst_pts_and_idxs
+ * @param output_grid
+ * @returns {[]}
+ */
function get_final_pdf(pdf_vals, bst_pts_and_idxs, output_grid) {
- // Take the binary search tree with transformed bin points,
- // and an array of pdf values associated with the bins,
- // and return a pdf over an evenly spaced grid
var offset = output_grid[1] / 2 - output_grid[0] / 2;
var active_intervals = new Map();
var active_endpoints = new bst.AVLTree();
@@ -152,47 +182,66 @@ function get_final_pdf(pdf_vals, bst_pts_and_idxs, output_grid) {
return final_pdf_vals;
}
+/**
+ * Entrypoint. Pass user input strings to this function,
+ * get the corresponding pdf values and input points back.
+ * If the pdf requires monte carlo (it contains a between-distr function)
+ * we first determing which distr to have deterministic
+ * and which to sample from. This is decided based on which
+ * choice gives the least variance.
+ *
+ * @param user_input_string
+ * @returns {([]|*[])[]}
+ */
function get_pdf_from_user_input(user_input_string) {
- // Entrypoint. Pass user input strings to this function,
- // get the corresponding pdf values and input points back.
- // If the pdf requires monte carlo (it contains a between-distr function)
- // we first determing which distr to have deterministic
- // and whih to sample from. This is decided based on which
- // choice gives the least variance.
- let parsed = parse.parse_initial_string(user_input_string);
- let mm_args = parse.separate_mm_args(parsed.mm_args_string);
- const is_mm = mm_args.distrs.length > 0;
- let tree = new bst.AVLTree();
- let possible_start_pts = [];
- let possible_end_pts = [];
- let all_vals = [];
- let weights = is_mm ? math.compile(mm_args.weights).evaluate()._data : [1];
- let weights_sum = weights.reduce((a, b) => a + b);
- weights = weights.map(x => x / weights_sum);
- let n_iters = is_mm ? mm_args.distrs.length : 1;
- for (let i = 0; i < n_iters; ++i) {
- let distr_string = is_mm ? mm_args.distrs[i] : parsed.outer_string;
- var [deterministic_pdf, mc_distrs] = choose_pdf_func(distr_string);
- var grid_transform = get_grid_transform(distr_string);
- var [start_pt, end_pt] = update_transformed_divider_points_bst(
- grid_transform,
- deterministic_pdf,
- mc_distrs,
- i,
- NUM_MC_SAMPLES,
- tree
- );
- possible_start_pts.push(start_pt);
- possible_end_pts.push(end_pt);
- all_vals.push(deterministic_pdf.pdf_vals.map(x => x * weights[i]));
+ try{
+ let parsed = parse.parse_initial_string(user_input_string);
+ let mm_args = parse.separate_mm_args(parsed.mm_args_string);
+
+ const is_mm = mm_args.distrs.length > 0;
+ if (!parsed.outer_string) return [[], [], true];
+
+ let tree = new bst.AVLTree();
+ let possible_start_pts = [];
+ let possible_end_pts = [];
+ let all_vals = [];
+ let weights = is_mm ? math.compile(mm_args.weights).evaluate()._data : [1];
+ let weights_sum = weights.reduce((a, b) => a + b);
+ weights = weights.map(x => x / weights_sum);
+ let n_iters = is_mm ? mm_args.distrs.length : 1;
+
+ for (let i = 0; i < n_iters; ++i) {
+ let distr_string = is_mm ? mm_args.distrs[i] : parsed.outer_string;
+ var [deterministic_pdf, mc_distrs] = choose_pdf_func(distr_string);
+ var grid_transform = get_grid_transform(distr_string);
+ var [start_pt, end_pt] = update_transformed_divider_points_bst(
+ grid_transform,
+ deterministic_pdf,
+ mc_distrs,
+ i,
+ NUM_MC_SAMPLES,
+ tree
+ );
+ possible_start_pts.push(start_pt);
+ possible_end_pts.push(end_pt);
+ all_vals.push(deterministic_pdf.pdf_vals.map(x => x * weights[i]));
+ }
+
+ start_pt = Math.min(...possible_start_pts);
+ end_pt = Math.max(...possible_end_pts);
+
+ let output_grid = evenly_spaced_grid(start_pt, end_pt, OUTPUT_GRID_NUMEL);
+ let final_pdf_vals = get_final_pdf(all_vals, tree, output_grid);
+ return [final_pdf_vals, output_grid, false];
+ } catch (e) {
+ return [[], [], true];
}
- start_pt = Math.min(...possible_start_pts);
- end_pt = Math.max(...possible_end_pts);
- let output_grid = evenly_spaced_grid(start_pt, end_pt, OUTPUT_GRID_NUMEL);
- let final_pdf_vals = get_final_pdf(all_vals, tree, output_grid);
- return [final_pdf_vals, output_grid];
}
+/**
+ * @param vals
+ * @returns {number}
+ */
function variance(vals) {
var vari = 0;
for (let i = 0; i < vals[0].length; ++i) {
@@ -209,14 +258,24 @@ function variance(vals) {
return vari;
}
+/**
+ * @param array
+ * @param idx
+ * @returns {*[]}
+ */
function pluck_from_array(array, idx) {
return [array[idx], array.slice(0, idx).concat(array.slice(idx + 1))];
}
+/**
+ * If distr_string requires MC, try all possible
+ * choices for the deterministic distribution,
+ * and pick the one with the least variance.
+ *
+ * @param distr_string
+ * @returns {(*|*[])[]|*[]}
+ */
function choose_pdf_func(distr_string) {
- // If distr_string requires MC, try all possible
- // choices for the deterministic distribution,
- // and pick the one with the least variance.
var variances = [];
let transform_func = get_grid_transform(distr_string);
let substrings = parse.get_distr_substrings(distr_string);
@@ -259,6 +318,10 @@ function choose_pdf_func(distr_string) {
return [pdfs[best_idx], mc_distrs];
}
+/**
+ * @param distr_string
+ * @returns {function(*): *}
+ */
function get_grid_transform(distr_string) {
let substrings = parse.get_distr_substrings(distr_string);
let arg_strings = [];
diff --git a/src/components/editor/parse.js b/src/components/editor/parse.js
index e81818c9..11d1ef4b 100644
--- a/src/components/editor/parse.js
+++ b/src/components/editor/parse.js
@@ -1,7 +1,8 @@
-// Functions for parsing/processing user input strings are here
const _math = require("mathjs");
const math = _math.create(_math.all);
+// Functions for parsing/processing user input strings are here
+
const DISTR_REGEXS = [
/beta\(/g,
/(log)?normal\(/g,
@@ -10,6 +11,11 @@ const DISTR_REGEXS = [
/uniform\(/g
];
+/**
+ *
+ * @param user_input_string
+ * @returns {{mm_args_string: string, outer_string: string}}
+ */
function parse_initial_string(user_input_string) {
let outer_output_string = "";
let mm_args_string = "";
@@ -42,6 +48,10 @@ function parse_initial_string(user_input_string) {
};
}
+/**
+ * @param mm_args_string
+ * @returns {{distrs: [], weights: string}}
+ */
function separate_mm_args(mm_args_string) {
if (mm_args_string.endsWith(",")) {
mm_args_string = mm_args_string.slice(0, -1);
@@ -68,6 +78,10 @@ function separate_mm_args(mm_args_string) {
};
}
+/**
+ * @param distr_string
+ * @returns {[]}
+ */
function get_distr_substrings(distr_string) {
let substrings = [];
for (let regex of DISTR_REGEXS) {
@@ -92,6 +106,10 @@ function get_distr_substrings(distr_string) {
return substrings;
}
+/**
+ * @param substr
+ * @returns {(string|*)[]}
+ */
function get_distr_name_and_args(substr) {
let distr_name = "";
let args_str = "";