Init
This commit is contained in:
		
							parent
							
								
									1684d79d9b
								
							
						
					
					
						commit
						531df57242
					
				|  | @ -55,7 +55,10 @@ | |||
|     "react-dom": "16.12.0", | ||||
|     "reason-react": ">=0.7.0", | ||||
|     "reschema": "1.3.0", | ||||
|     "tailwindcss": "1.2.0" | ||||
|     "tailwindcss": "1.2.0", | ||||
|     "binary-search-tree": "0.2.6", | ||||
|     "jstat": "1.9.2", | ||||
|     "mathjs": "6.6.0" | ||||
|   }, | ||||
|   "alias": { | ||||
|     "react": "./node_modules/react", | ||||
|  |  | |||
							
								
								
									
										181
									
								
								src/components/editor/distribution.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/components/editor/distribution.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,181 @@ | |||
| // 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"); | ||||
| 
 | ||||
| math.import({ | ||||
|   normal: jStat.normal, | ||||
|   beta: jStat.beta, | ||||
|   lognormal: jStat.lognormal, | ||||
|   uniform: jStat.uniform | ||||
| }); | ||||
| 
 | ||||
| class BaseDistributionBinned { | ||||
|   constructor(args) { | ||||
|     this._set_props(); | ||||
|     this.max_bin_size = 0.5; | ||||
|     this.min_bin_size = 0; | ||||
|     this.increment = 0.001; | ||||
|     this.desired_delta = 0.01; | ||||
|     this.start_bin_size = 0.01; | ||||
| 
 | ||||
|     [this.params, this.pdf_func, this.sample] = this.get_params_and_pdf_func( | ||||
|       args | ||||
|     ); | ||||
| 
 | ||||
|     [this.start_point, this.end_point] = this.get_bounds(); | ||||
|     [this.pdf_vals, this.divider_pts] = this.bin(); | ||||
|   } | ||||
| 
 | ||||
|   _set_props() { | ||||
|     // this is hacky but class properties aren't always supported
 | ||||
|     throw new Error("NotImplementedError"); | ||||
|   } | ||||
| 
 | ||||
|   _adabin() { | ||||
|     let point = this.start_point; | ||||
|     let vals = [this.pdf_func(point)]; | ||||
|     let divider_pts = [point]; | ||||
|     let support = this.end_point - this.start_point; | ||||
|     let bin_size = this.start_bin_size * support; | ||||
| 
 | ||||
|     while (point < this.end_point) { | ||||
|       let val = this.pdf_func(point + bin_size); | ||||
|       if (Math.abs(val - vals[vals.length - 1]) > this.desired_delta) { | ||||
|         while ( | ||||
|           (Math.abs(val - vals[vals.length - 1]) > this.desired_delta) & | ||||
|           (bin_size - this.increment * support > this.min_bin_size) | ||||
|           ) { | ||||
|           bin_size -= this.increment; | ||||
|           val = this.pdf_func(point + bin_size); | ||||
|         } | ||||
|       } else if (Math.abs(val - vals[vals.length - 1]) < this.desired_delta) { | ||||
|         while ( | ||||
|           (Math.abs(val - vals[vals.length - 1]) < this.desired_delta) & | ||||
|           (bin_size < this.max_bin_size) | ||||
|           ) { | ||||
|           bin_size += this.increment; | ||||
|           val = this.pdf_func(point + bin_size); | ||||
|         } | ||||
|       } | ||||
|       point += bin_size; | ||||
|       vals.push(val); | ||||
|       divider_pts.push(point); | ||||
|     } | ||||
|     vals = vals.map((_, idx) => vals[idx] / 2 + vals[idx + 1] / 2); | ||||
|     vals = vals.slice(0, -1); | ||||
|     return [vals, divider_pts]; | ||||
|   } | ||||
| 
 | ||||
|   bin() { | ||||
|     throw new Error("NotImplementedError"); | ||||
|   } | ||||
| 
 | ||||
|   get_bounds() { | ||||
|     throw new Error("NotImplementedError"); | ||||
|   } | ||||
| 
 | ||||
|   get_params_and_pdf_func(args) { | ||||
|     let args_str = args.toString() + ")"; | ||||
|     let substr = this.name + ".pdf(x, " + args_str; | ||||
|     let compiled = math.compile(substr); | ||||
|     function pdf_func(x) { | ||||
|       return compiled.evaluate({ x: x }); | ||||
|     } | ||||
|     let mc_compiled = math.compile(this.name + ".sample(" + args_str); | ||||
|     let kv_pairs = this.param_names.map((val, idx) => [val, args[idx]]); | ||||
|     let params = Object.fromEntries(new Map(kv_pairs)); | ||||
|     return [params, pdf_func, mc_compiled.evaluate]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class NormalDistributionBinned extends BaseDistributionBinned { | ||||
|   _set_props() { | ||||
|     this.name = "normal"; | ||||
|     this.param_names = ["mean", "std"]; | ||||
|   } | ||||
|   get_bounds() { | ||||
|     return [ | ||||
|       this.params.mean - 4 * this.params.std, | ||||
|       this.params.mean + 4 * this.params.std | ||||
|     ]; | ||||
|   } | ||||
|   bin() { | ||||
|     return this._adabin(this.params.std); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class UniformDistributionBinned extends BaseDistributionBinned { | ||||
|   _set_props() { | ||||
|     this.name = "uniform"; | ||||
|     this.param_names = ["start_point", "end_point"]; | ||||
|     this.num_bins = 200; | ||||
|   } | ||||
|   get_bounds() { | ||||
|     return [this.params.start_point, this.params.end_point]; | ||||
|   } | ||||
|   bin() { | ||||
|     let divider_pts = evenly_spaced_grid( | ||||
|       this.params.start_point, | ||||
|       this.params.end_point, | ||||
|       this.num_bins | ||||
|     ); | ||||
|     let vals = divider_pts.map(x => | ||||
|       this.pdf_func(this.params.start_point / 2 + this.params.end_point / 2) | ||||
|     ); | ||||
|     vals = vals.slice(0, -1); | ||||
|     return [vals, divider_pts]; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class LogNormalDistributionBinned extends BaseDistributionBinned { | ||||
|   _set_props() { | ||||
|     this.name = "lognormal"; | ||||
|     this.param_names = ["normal_mean", "normal_std"]; | ||||
|     this.n_bounds_samples = 1000; | ||||
|     this.n_largest_bound_sample = 10; | ||||
|   } | ||||
| 
 | ||||
|   _nth_largest(samples, n) { | ||||
|     var largest_buffer = Array(n).fill(-Infinity); | ||||
|     for (const sample of samples) { | ||||
|       if (sample > largest_buffer[n - 1]) { | ||||
|         var i = n; | ||||
|         while ((i > 0) & (sample > largest_buffer[i - 1])) { | ||||
|           i -= 1; | ||||
|         } | ||||
|         largest_buffer[i] = sample; | ||||
|       } | ||||
|     } | ||||
|     return largest_buffer[n - 1]; | ||||
|   } | ||||
|   get_bounds() { | ||||
|     let samples = Array(this.n_bounds_samples) | ||||
|       .fill(0) | ||||
|       .map(() => this.sample()); | ||||
|     return [ | ||||
|       math.min(samples), | ||||
|       this._nth_largest(samples, this.n_largest_bound_sample) | ||||
|     ]; | ||||
|   } | ||||
|   bin() { | ||||
|     return this._adabin(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function evenly_spaced_grid(start, stop, numel) { | ||||
|   return Array(numel) | ||||
|     .fill(0) | ||||
|     .map((_, idx) => start + (idx / numel) * (stop - start)); | ||||
| } | ||||
| 
 | ||||
| const distrs = { | ||||
|   normal: NormalDistributionBinned, | ||||
|   lognormal: LogNormalDistributionBinned, | ||||
|   uniform: UniformDistributionBinned | ||||
| }; | ||||
| 
 | ||||
| exports.distrs = distrs; | ||||
							
								
								
									
										31
									
								
								src/components/editor/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/components/editor/index.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| import "./styles.css"; | ||||
| const embed = require("vega-embed").embed; | ||||
| const get_pdf_from_user_input = require("./main.js").get_pdf_from_user_input; | ||||
| 
 | ||||
| let [y, x] = get_pdf_from_user_input("normal(1, 1)  / normal(10, 1)"); | ||||
| 
 | ||||
| let pdf = x.map((val, idx) => ({ x: val, pdf: y[idx] })); | ||||
| 
 | ||||
| let spec = { | ||||
|   data: { | ||||
|     values: pdf | ||||
|   }, | ||||
|   mark: { type: "area", line: true }, | ||||
|   encoding: { | ||||
|     x: { field: "x", type: "quantitative" }, | ||||
|     y: { | ||||
|       field: "pdf", | ||||
|       type: "quantitative", | ||||
|       scale: { domain: [0, 3 * Math.max(...y)] } | ||||
|     } | ||||
|   }, | ||||
|   width: 500 | ||||
| }; | ||||
| 
 | ||||
| embed("#viz", spec); | ||||
| 
 | ||||
| console.log(y.reduce((a, b) => a + b)); | ||||
| 
 | ||||
| document.getElementById("app").innerHTML = ` | ||||
| <div id="viz"></div> | ||||
| `;
 | ||||
							
								
								
									
										278
									
								
								src/components/editor/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								src/components/editor/main.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,278 @@ | |||
| // 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 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; | ||||
| 
 | ||||
| function evenly_spaced_grid(start, stop, numel) { | ||||
|   return Array(numel) | ||||
|     .fill(0) | ||||
|     .map((_, idx) => start + (idx / numel) * (stop - start)); | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
| 
 | ||||
| function update_transformed_divider_points_bst( | ||||
|   transform_func, | ||||
|   deterministic_pdf, | ||||
|   mc_distrs, | ||||
|   track_idx, | ||||
|   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 = []; | ||||
|   var start_pt = Infinity; | ||||
|   var end_pt = -Infinity; | ||||
|   let use_mc = mc_distrs.length > 0; | ||||
|   var num_outer_iters = use_mc ? num_mc_samples : 1; | ||||
| 
 | ||||
|   for (let sample_idx = 0; sample_idx < num_outer_iters; ++sample_idx) { | ||||
|     var this_transformed_pts = deterministic_pdf.divider_pts; | ||||
|     if (use_mc) { | ||||
|       let samples = mc_distrs.map(x => x.sample()); | ||||
|       this_transformed_pts = this_transformed_pts.map(x => | ||||
|         transform_func([x].concat(samples)) | ||||
|       ); | ||||
|     } else { | ||||
|       this_transformed_pts = this_transformed_pts.map(x => transform_func([x])); | ||||
|     } | ||||
|     var this_transformed_pts_paired = []; | ||||
|     for (let tp_idx = 0; tp_idx < this_transformed_pts.length - 1; tp_idx++) { | ||||
|       let sorted = [ | ||||
|         this_transformed_pts[tp_idx], | ||||
|         this_transformed_pts[tp_idx + 1] | ||||
|       ].sort((a, b) => a - b); | ||||
|       if (sorted[0] < start_pt) { | ||||
|         start_pt = sorted[0]; | ||||
|       } | ||||
|       if (sorted[1] > end_pt) { | ||||
|         end_pt = sorted[1]; | ||||
|       } | ||||
|       this_transformed_pts_paired.push(sorted); | ||||
|     } | ||||
| 
 | ||||
|     transformed_pts = transformed_pts.concat(this_transformed_pts_paired); | ||||
| 
 | ||||
|     pdf_inner_idxs = pdf_inner_idxs.concat([ | ||||
|       ...Array(this_transformed_pts_paired.length).keys() | ||||
|     ]); | ||||
|     var this_factors = []; | ||||
|     for (let idx = 0; idx < this_transformed_pts_paired.length; idx++) { | ||||
|       this_factors.push( | ||||
|         (deterministic_pdf.divider_pts[idx + 1] - | ||||
|           deterministic_pdf.divider_pts[idx]) / | ||||
|         (this_transformed_pts_paired[idx][1] - | ||||
|           this_transformed_pts_paired[idx][0]) | ||||
|       ); | ||||
|     } | ||||
|     factors = factors.concat(this_factors); | ||||
|   } | ||||
|   for (let i = 0; i < transformed_pts.length; ++i) { | ||||
|     bst_pts_and_idxs.insert(transformed_pts[i][0], { | ||||
|       start: transformed_pts[i][0], | ||||
|       end: transformed_pts[i][1], | ||||
|       idx: [track_idx, pdf_inner_idxs[i]], | ||||
|       factor: factors[i] / num_outer_iters | ||||
|     }); | ||||
|   } | ||||
|   return [start_pt, end_pt]; | ||||
| } | ||||
| 
 | ||||
| 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(); | ||||
|   var final_pdf_vals = []; | ||||
|   for ( | ||||
|     let out_grid_idx = 0; | ||||
|     out_grid_idx < output_grid.length; | ||||
|     ++out_grid_idx | ||||
|   ) { | ||||
|     let startpoints_within_bin = bst_pts_and_idxs.betweenBounds({ | ||||
|       $gte: output_grid[out_grid_idx] - offset, | ||||
|       $lt: output_grid[out_grid_idx] + offset | ||||
|     }); | ||||
|     for (let interval of startpoints_within_bin) { | ||||
|       active_intervals.set(interval.idx, [ | ||||
|         interval.start, | ||||
|         interval.end, | ||||
|         interval.factor | ||||
|       ]); | ||||
|       active_endpoints.insert(interval.end, interval.idx); | ||||
|     } | ||||
|     var contrib = 0; | ||||
|     for (let [pdf_idx, bounds_and_ratio] of active_intervals.entries()) { | ||||
|       let overlap_start = Math.max( | ||||
|         output_grid[out_grid_idx] - offset, | ||||
|         bounds_and_ratio[0] | ||||
|       ); | ||||
|       let overlap_end = Math.min( | ||||
|         output_grid[out_grid_idx] + offset, | ||||
|         bounds_and_ratio[1] | ||||
|       ); | ||||
|       let interval_size = bounds_and_ratio[1] - bounds_and_ratio[0]; | ||||
|       let contrib_frac = | ||||
|         interval_size === 0 | ||||
|           ? 0 | ||||
|           : (overlap_end - overlap_start) * bounds_and_ratio[2]; | ||||
|       let t = contrib_frac * pdf_vals[pdf_idx[0]][pdf_idx[1]]; | ||||
|       contrib += t; | ||||
|     } | ||||
|     final_pdf_vals.push(contrib); | ||||
|     let endpoints_within_bin = active_endpoints.betweenBounds({ | ||||
|       $gte: output_grid[out_grid_idx] - offset, | ||||
|       $lt: output_grid[out_grid_idx] + offset | ||||
|     }); | ||||
|     for (let interval_idx of endpoints_within_bin) { | ||||
|       active_intervals.delete(interval_idx); | ||||
|     } | ||||
|   } | ||||
|   return final_pdf_vals; | ||||
| } | ||||
| 
 | ||||
| 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])); | ||||
|   } | ||||
|   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]; | ||||
| } | ||||
| 
 | ||||
| function variance(vals) { | ||||
|   var vari = 0; | ||||
|   for (let i = 0; i < vals[0].length; ++i) { | ||||
|     let mean = 0; | ||||
|     let this_vari = 0; | ||||
|     for (let val of vals) { | ||||
|       mean += val[i] / vals.length; | ||||
|     } | ||||
|     for (let val of vals) { | ||||
|       this_vari += (val[i] - mean) ** 2; | ||||
|     } | ||||
|     vari += this_vari; | ||||
|   } | ||||
|   return vari; | ||||
| } | ||||
| 
 | ||||
| function pluck_from_array(array, idx) { | ||||
|   return [array[idx], array.slice(0, idx).concat(array.slice(idx + 1))]; | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
|   var pdfs = get_distributions(substrings); | ||||
|   if (pdfs.length === 1) { | ||||
|     return [pdfs[0], []]; | ||||
|   } | ||||
|   var start_pt = 0; | ||||
|   var end_pt = 0; | ||||
|   for (let i = 0; i < pdfs.length; ++i) { | ||||
|     var outputs = []; | ||||
|     for (let j = 0; j < 20; ++j) { | ||||
|       let tree = new bst.AVLTree(); | ||||
|       let [deterministic_pdf, mc_distrs] = pluck_from_array(pdfs, i); | ||||
|       let [this_start_pt, this_end_pt] = update_transformed_divider_points_bst( | ||||
|         transform_func, | ||||
|         deterministic_pdf, | ||||
|         mc_distrs, | ||||
|         0, | ||||
|         10, | ||||
|         tree | ||||
|       ); | ||||
|       [start_pt, end_pt] = | ||||
|         j === 0 ? [this_start_pt, this_end_pt] : [start_pt, end_pt]; | ||||
|       var output_grid = evenly_spaced_grid(start_pt, end_pt, 100); | ||||
|       let final_pdf_vals = get_final_pdf( | ||||
|         [deterministic_pdf.pdf_vals], | ||||
|         tree, | ||||
|         output_grid | ||||
|       ); | ||||
|       outputs.push(final_pdf_vals); | ||||
|     } | ||||
|     variances.push(variance(outputs)); | ||||
|   } | ||||
|   let best_variance = Math.min(...variances); | ||||
|   let best_idx = variances | ||||
|     .map((val, idx) => [val, idx]) | ||||
|     .filter(x => x[0] === best_variance)[0][1]; | ||||
|   let mc_distrs = pdfs.slice(0, best_idx).concat(pdfs.slice(best_idx + 1)); | ||||
|   return [pdfs[best_idx], mc_distrs]; | ||||
| } | ||||
| 
 | ||||
| function get_grid_transform(distr_string) { | ||||
|   let substrings = parse.get_distr_substrings(distr_string); | ||||
|   let arg_strings = []; | ||||
|   for (let i = 0; i < substrings.length; ++i) { | ||||
|     distr_string = distr_string.replace(substrings[i], "x_" + i.toString()); | ||||
|     arg_strings.push("x_" + i.toString()); | ||||
|   } | ||||
|   let compiled = math.compile(distr_string); | ||||
|   function grid_transform(x) { | ||||
|     let kv_pairs = arg_strings.map((val, idx) => [val, x[idx]]); | ||||
|     let args_obj = Object.fromEntries(new Map(kv_pairs)); | ||||
|     return compiled.evaluate(args_obj); | ||||
|   } | ||||
|   return grid_transform; | ||||
| } | ||||
| 
 | ||||
| exports.get_pdf_from_user_input = get_pdf_from_user_input; | ||||
							
								
								
									
										119
									
								
								src/components/editor/parse.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/components/editor/parse.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,119 @@ | |||
| // Functions for parsing/processing user input strings are here
 | ||||
| const _math = require("mathjs"); | ||||
| const math = _math.create(_math.all); | ||||
| 
 | ||||
| const DISTR_REGEXS = [ | ||||
|   /beta\(/g, | ||||
|   /(log)?normal\(/g, | ||||
|   /multimodal\(/g, | ||||
|   /mm\(/g, | ||||
|   /uniform\(/g | ||||
| ]; | ||||
| 
 | ||||
| function parse_initial_string(user_input_string) { | ||||
|   let outer_output_string = ""; | ||||
|   let mm_args_string = ""; | ||||
|   let idx = 0; | ||||
|   while (idx < user_input_string.length) { | ||||
|     if ( | ||||
|       user_input_string.substring(idx - 11, idx) === "multimodal(" || | ||||
|       user_input_string.substring(idx - 3, idx) === "mm(" | ||||
|     ) { | ||||
|       let num_open_brackets = 1; | ||||
|       while (num_open_brackets > 0 && idx < user_input_string.length) { | ||||
|         mm_args_string += user_input_string[idx]; | ||||
|         idx += 1; | ||||
|         if (user_input_string[idx] === ")") { | ||||
|           num_open_brackets -= 1; | ||||
|         } else if (user_input_string[idx] === "(") { | ||||
|           num_open_brackets += 1; | ||||
|         } | ||||
|       } | ||||
|       outer_output_string += ")"; | ||||
|       idx += 1; | ||||
|     } else { | ||||
|       outer_output_string += user_input_string[idx]; | ||||
|       idx += 1; | ||||
|     } | ||||
|   } | ||||
|   return { | ||||
|     outer_string: outer_output_string, | ||||
|     mm_args_string: mm_args_string | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function separate_mm_args(mm_args_string) { | ||||
|   if (mm_args_string.endsWith(",")) { | ||||
|     mm_args_string = mm_args_string.slice(0, -1); | ||||
|   } | ||||
|   let args_array = []; | ||||
|   let num_open_brackets = 0; | ||||
|   let arg_substring = ""; | ||||
|   for (let char of mm_args_string) { | ||||
|     if (num_open_brackets === 0 && char === ",") { | ||||
|       args_array.push(arg_substring.trim()); | ||||
|       arg_substring = ""; | ||||
|     } else { | ||||
|       if (char === ")" || char === "]") { | ||||
|         num_open_brackets -= 1; | ||||
|       } else if (char === "(" || char === "[") { | ||||
|         num_open_brackets += 1; | ||||
|       } | ||||
|       arg_substring += char; | ||||
|     } | ||||
|   } | ||||
|   return { | ||||
|     distrs: args_array, | ||||
|     weights: arg_substring.trim() | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function get_distr_substrings(distr_string) { | ||||
|   let substrings = []; | ||||
|   for (let regex of DISTR_REGEXS) { | ||||
|     let matches = distr_string.matchAll(regex); | ||||
|     for (let match of matches) { | ||||
|       let idx = match.index + match[0].length; | ||||
|       let num_open_brackets = 1; | ||||
|       let distr_substring = ""; | ||||
|       while (num_open_brackets !== 0 && idx < distr_string.length) { | ||||
|         distr_substring += distr_string[idx]; | ||||
|         if (distr_string[idx] === "(") { | ||||
|           num_open_brackets += 1; | ||||
|         } else if (distr_string[idx] === ")") { | ||||
|           num_open_brackets -= 1; | ||||
|         } | ||||
|         idx += 1; | ||||
|       } | ||||
|       substrings.push((match[0] + distr_substring).trim()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return substrings; | ||||
| } | ||||
| 
 | ||||
| function get_distr_name_and_args(substr) { | ||||
|   let distr_name = ""; | ||||
|   let args_str = ""; | ||||
|   let args_flag = false; | ||||
|   for (let char of substr) { | ||||
|     if (!args_flag && char !== "(") { | ||||
|       distr_name += char; | ||||
|     } | ||||
|     if (args_flag && char !== ")") { | ||||
|       args_str += char; | ||||
|     } | ||||
|     if (char === "(") { | ||||
|       args_str += "["; | ||||
|       args_flag = true; | ||||
|     } | ||||
|   } | ||||
|   args_str += "]"; | ||||
|   let args = math.compile(args_str).evaluate()._data; | ||||
|   return [distr_name, args]; | ||||
| } | ||||
| 
 | ||||
| exports.get_distr_name_and_args = get_distr_name_and_args; | ||||
| exports.get_distr_substrings = get_distr_substrings; | ||||
| exports.separate_mm_args = separate_mm_args; | ||||
| exports.parse_initial_string = parse_initial_string; | ||||
							
								
								
									
										50
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								yarn.lock
									
									
									
									
									
								
							|  | @ -2333,6 +2333,13 @@ binary-extensions@^2.0.0: | |||
|   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" | ||||
|   integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== | ||||
| 
 | ||||
| binary-search-tree@0.2.6: | ||||
|   version "0.2.6" | ||||
|   resolved "https://registry.yarnpkg.com/binary-search-tree/-/binary-search-tree-0.2.6.tgz#c6d29194e286827fcffe079010e6bf77def10ce3" | ||||
|   integrity sha1-xtKRlOKGgn/P/geQEOa/d97xDOM= | ||||
|   dependencies: | ||||
|     underscore "~1.4.4" | ||||
| 
 | ||||
| bindings@^1.5.0: | ||||
|   version "1.5.0" | ||||
|   resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" | ||||
|  | @ -2889,7 +2896,7 @@ commander@2, commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@ | |||
|   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" | ||||
|   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== | ||||
| 
 | ||||
| complex.js@2.0.11: | ||||
| complex.js@2.0.11, complex.js@^2.0.11: | ||||
|   version "2.0.11" | ||||
|   resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.11.tgz#09a873fbf15ffd8c18c9c2201ccef425c32b8bf1" | ||||
|   integrity sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw== | ||||
|  | @ -3632,7 +3639,7 @@ decamelize@^1.2.0: | |||
|   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" | ||||
|   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= | ||||
| 
 | ||||
| decimal.js@10.2.0: | ||||
| decimal.js@10.2.0, decimal.js@^10.2.0: | ||||
|   version "10.2.0" | ||||
|   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" | ||||
|   integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== | ||||
|  | @ -4043,7 +4050,7 @@ escape-html@~1.0.3: | |||
|   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" | ||||
|   integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= | ||||
| 
 | ||||
| escape-latex@1.2.0: | ||||
| escape-latex@1.2.0, escape-latex@^1.2.0: | ||||
|   version "1.2.0" | ||||
|   resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" | ||||
|   integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== | ||||
|  | @ -4438,7 +4445,7 @@ form-data@~2.3.2: | |||
|     combined-stream "^1.0.6" | ||||
|     mime-types "^2.1.12" | ||||
| 
 | ||||
| fraction.js@4.0.12: | ||||
| fraction.js@4.0.12, fraction.js@^4.0.12: | ||||
|   version "4.0.12" | ||||
|   resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.12.tgz#0526d47c65a5fb4854df78bc77f7bec708d7b8c3" | ||||
|   integrity sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA== | ||||
|  | @ -5410,7 +5417,7 @@ iterall@^1.2.1, iterall@^1.2.2: | |||
|   resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" | ||||
|   integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== | ||||
| 
 | ||||
| javascript-natural-sort@0.7.1: | ||||
| javascript-natural-sort@0.7.1, javascript-natural-sort@^0.7.1: | ||||
|   version "0.7.1" | ||||
|   resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" | ||||
|   integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= | ||||
|  | @ -6320,6 +6327,11 @@ jstat@1.9.0: | |||
|   resolved "https://registry.yarnpkg.com/jstat/-/jstat-1.9.0.tgz#96a625f5697566f6ba3b15832fb371f9451b8614" | ||||
|   integrity sha512-xSsSJ3qY4rS+u8+dAwRcJ0LQGxNdibdW6rSalNPZDbLYkW1C7b0/j79IxXtQjrweqMNI3asN7FCIPceNSIJr2g== | ||||
| 
 | ||||
| jstat@1.9.2: | ||||
|   version "1.9.2" | ||||
|   resolved "https://registry.yarnpkg.com/jstat/-/jstat-1.9.2.tgz#cd2d24df200fd3488861dc7868be01ff65a238cc" | ||||
|   integrity sha512-nc3uAadgrWWvJz6RyXUFN0lvTWEXYxMVIrm6ZVoOh4YPLvukLKYpqMofKIE2ReWkL7gFw6hEo6VWZjotYW2Bsw== | ||||
| 
 | ||||
| kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: | ||||
|   version "3.2.2" | ||||
|   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" | ||||
|  | @ -6559,6 +6571,20 @@ mathjs@5.10.3: | |||
|     tiny-emitter "2.1.0" | ||||
|     typed-function "1.1.0" | ||||
| 
 | ||||
| mathjs@6.6.0: | ||||
|   version "6.6.0" | ||||
|   resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-6.6.0.tgz#0d71c7cc6b50bd112e160b55703a395caf4db4b1" | ||||
|   integrity sha512-gYYc1+irFbwuwYUx6O8G2YauvbD1+tPBbq829PaxAiRWpPzPEE8pvwGgvdMuk6c3pqhm6Do/mN26vLiQP46H5A== | ||||
|   dependencies: | ||||
|     complex.js "^2.0.11" | ||||
|     decimal.js "^10.2.0" | ||||
|     escape-latex "^1.2.0" | ||||
|     fraction.js "^4.0.12" | ||||
|     javascript-natural-sort "^0.7.1" | ||||
|     seed-random "^2.2.0" | ||||
|     tiny-emitter "^2.1.0" | ||||
|     typed-function "^1.1.1" | ||||
| 
 | ||||
| md5.js@^1.3.4: | ||||
|   version "1.3.5" | ||||
|   resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" | ||||
|  | @ -9235,7 +9261,7 @@ screenfull@^5.0.0: | |||
|   resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7" | ||||
|   integrity sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ== | ||||
| 
 | ||||
| seed-random@2.2.0: | ||||
| seed-random@2.2.0, seed-random@^2.2.0: | ||||
|   version "2.2.0" | ||||
|   resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" | ||||
|   integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= | ||||
|  | @ -9989,7 +10015,7 @@ timsort@^0.3.0: | |||
|   resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" | ||||
|   integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= | ||||
| 
 | ||||
| tiny-emitter@2.1.0: | ||||
| tiny-emitter@2.1.0, tiny-emitter@^2.1.0: | ||||
|   version "2.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" | ||||
|   integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== | ||||
|  | @ -10176,6 +10202,11 @@ typed-function@1.1.0: | |||
|   resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-1.1.0.tgz#ea149706e0fb42aca1791c053a6d94ccd6c4fdcb" | ||||
|   integrity sha512-TuQzwiT4DDg19beHam3E66oRXhyqlyfgjHB/5fcvsRXbfmWPJfto9B4a0TBdTrQAPGlGmXh/k7iUI+WsObgORA== | ||||
| 
 | ||||
| typed-function@^1.1.1: | ||||
|   version "1.1.1" | ||||
|   resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-1.1.1.tgz#a1316187ec3628c9e219b91ca96918660a10138e" | ||||
|   integrity sha512-RbN7MaTQBZLJYzDENHPA0nUmWT0Ex80KHItprrgbTPufYhIlTePvCXZxyQK7wgn19FW5bnuaBIKcBb5mRWjB1Q== | ||||
| 
 | ||||
| typedarray-to-buffer@^3.1.5: | ||||
|   version "3.1.5" | ||||
|   resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" | ||||
|  | @ -10208,6 +10239,11 @@ uncss@^0.17.2: | |||
|     postcss-selector-parser "6.0.2" | ||||
|     request "^2.88.0" | ||||
| 
 | ||||
| underscore@~1.4.4: | ||||
|   version "1.4.4" | ||||
|   resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" | ||||
|   integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ= | ||||
| 
 | ||||
| unicode-canonical-property-names-ecmascript@^1.0.4: | ||||
|   version "1.0.4" | ||||
|   resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user