| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | |
| <title>Pan math fun</title> | |
| <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"> </script> | |
| <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"> </script> | |
| <script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js"> </script> | |
| <script type="text/javascript"> | |
| function solve_inv(opts) { | |
| return(opts.goal_success_levels/(opts.scale*(opts.start+opts.goal_success_levels))); | |
| } | |
| function solve_logistic(opts) { | |
| return 1/(opts.scale*(1+Math.exp(-( | |
| opts.start+ | |
| opts.goal_skip_weight*opts.goal_skip_levels+ | |
| opts.goal_success_weight*opts.goal_success_levels)))); | |
| } | |
| var model_map = { | |
| 'inv': solve_inv, | |
| 'logistic': solve_logistic | |
| }; | |
| function log_start_prob_to_start_val(prob, scale) { | |
| return -Math.log((1/(prob*scale))-1); | |
| } | |
| function inv_start_prob_to_b(prob, scale) { | |
| return (1/(prob*scale))-1; | |
| } | |
| var start_val_map = { | |
| 'inv': inv_start_prob_to_b, | |
| 'logistic': log_start_prob_to_start_val | |
| } | |
| function simulate_pan(start_prob, start_fx, lev_fx, success_weight, skip_weight, success_prob, psf, rune_count, max_levels) { | |
| var rune_findings = []; | |
| var start_val = start_fx(start_prob, psf); | |
| var successes = 0; | |
| var skips = 0; | |
| for (var i = 0; i < max_levels; i++) { | |
| var succ = Math.random() <= success_prob; | |
| if (succ) { | |
| successes += 1; | |
| } else { | |
| skips += 1; | |
| } | |
| var opts = { | |
| 'start': start_val, | |
| 'goal_skip_levels': skips, | |
| 'goal_skip_weight': skip_weight, | |
| 'goal_success_levels': successes, | |
| 'goal_success_weight': success_weight, | |
| 'scale': psf}; | |
| var level_prob = lev_fx(opts); | |
| var found = Math.random() < level_prob; | |
| if (found) { | |
| rune_findings.push(i); | |
| } | |
| if (rune_findings.length >= rune_count) { | |
| return rune_findings; | |
| } | |
| } | |
| while (rune_findings.length < rune_count) { | |
| rune_findings.push(max_levels); | |
| } | |
| return rune_findings; | |
| } | |
| function pan_monte_carlo(start_prob, start_fx, lev_fx, success_weight, skip_weight, success_prob, psf, rune_count, max_levels, iters) { | |
| var pan_runs = []; | |
| for (var i = 0; i < iters; i++) { | |
| pan_runs.push(simulate_pan(start_prob, start_fx, lev_fx, success_weight, skip_weight, success_prob, psf, rune_count, max_levels)); | |
| } | |
| return pan_runs | |
| } | |
| var canvas; | |
| $(document).ready(function() { | |
| var color_scale = d3.scale.ordinal() | |
| .domain([0,1,2,3]) | |
| .range([ | |
| 'rgb(186, 228, 188)', | |
| 'rgb(123, 204, 196)', | |
| 'rgb(67, 162, 202)', | |
| 'rgb(8, 104, 172)']) | |
| var h = 150; | |
| var w = 450; | |
| canvas = d3.select('#histograms').append('svg') | |
| .attr('width', w+'px') | |
| .attr('height', h+'px'); | |
| $('#compute').click(function(event) { | |
| var model = $('#model').val(); | |
| var start_fx = start_val_map[model]; | |
| var model_fx = model_map[model]; | |
| console.log(model); | |
| var start_prob = parseFloat($('#start_prob').val()); | |
| var success_weight = parseFloat($('#success_weight').val()); | |
| var skip_weight = parseFloat($('#skip_weight').val()); | |
| var success_prob = parseFloat($('#success_prob').val()); | |
| var psf = parseFloat($('#psf').val()); | |
| var start_val = start_fx(start_prob, psf); | |
| var max_levels = parseInt($('#max_levels').val(), 10); | |
| var iters = parseInt($('#iters').val(), 10); | |
| var res = $('#prob_results'); | |
| res.empty(); | |
| var labels = $('<tr></tr>').appendTo(res); | |
| labels.append("<th>level</th>"); | |
| var results = $('<tr></tr>').appendTo(res); | |
| results.append("<th>rune probability</th>"); | |
| $('#rune_stats').empty(); | |
| var stats_header = $('<tr></tr>').appendTo($('#rune_stats')); | |
| stats_header.append('<td>rune</td><td>mode</td>'); | |
| stats_header.append('<td>min</td><td>25%</td><td>50%</td><td>75%</td><td>max</td>'); | |
| // let's run our monte carlo! | |
| var rune_count = 4; | |
| var run_results = pan_monte_carlo(start_prob, start_fx, model_fx, | |
| success_weight, skip_weight, success_prob, psf, rune_count, max_levels, iters); | |
| var histograms = []; | |
| var max_y = 0; | |
| for (var i = 0; i < rune_count; i++) { | |
| var vals = run_results.map(function(e) {return e[i];}); | |
| var vals_sorted = vals.slice().sort(d3.ascending); | |
| var hist = d3.layout.histogram() | |
| .bins(max_levels) | |
| .range([0,max_levels])(vals); | |
| var yvals = hist.map(function(d) {return d.y;}); | |
| var this_max_y = d3.max(yvals); | |
| var max_y_idx = yvals.indexOf(this_max_y); | |
| max_y = d3.max([max_y, this_max_y]); | |
| histograms.push(hist); | |
| var stats_row = $('<tr></tr>').appendTo($('#rune_stats')); | |
| stats_row.append('<td>'+(i+1)+'</td><td>'+hist[max_y_idx].x+'</td>'); | |
| stats_row.append('<td>'+d3.quantile(vals_sorted, 0)+'</td>'); | |
| stats_row.append('<td>'+d3.quantile(vals_sorted, 0.25)+'</td>'); | |
| stats_row.append('<td>'+d3.quantile(vals_sorted, 0.5)+'</td>'); | |
| stats_row.append('<td>'+d3.quantile(vals_sorted, 0.75)+'</td>'); | |
| stats_row.append('<td>'+d3.quantile(vals_sorted, 1)+'</td>'); | |
| } | |
| // And draw it. | |
| canvas.selectAll('g').remove(); | |
| var x_scale = d3.scale.linear() | |
| .range([0,w]) | |
| .domain([0, max_levels]); | |
| var bar_width = w/max_levels; | |
| var y_scale = d3.scale.linear() | |
| .range([0, h]) | |
| .domain([0, max_y]); | |
| for (var i = histograms.length-1; i >= 0; i--) { | |
| var hist = histograms[i]; | |
| var chp = canvas.append('g'); | |
| chp.selectAll('rect') | |
| .data(hist) | |
| .enter().append('rect') | |
| .attr('x', function(d) { return x_scale(d.x);}) | |
| .attr('width', bar_width) | |
| .attr('y', function(d) { return h-y_scale(d.y); }) | |
| .attr('height', function(d) {return y_scale(d.y); }) | |
| .style('fill', color_scale(i)) | |
| .style('opacity', 0.7); | |
| } | |
| }) | |
| }); | |
| </script> | |
| </head> | |
| <body> | |
| <div> | |
| <label for="model">Model</label> | |
| <select id="model" name="model"> | |
| <option value="logistic">logistic: 1/m(e^-(b0+b1*succ+b2*skip))</option> | |
| <option value="inv">inverse: x/m(x+b0)</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="start_prob">Start probability (drives b0)</label> | |
| <input name="start_prob" id="start_prob" value="0.015" /> | |
| </div> | |
| <div> | |
| <label for="success_weight">Goal success weight (b1)</label> | |
| <input name="success_weight" id="success_weight" value="0.1" /> | |
| (Ignored for inverse model) | |
| </div> | |
| <div> | |
| <label for="skip_weight">Goal skip weight (b2)</label> | |
| <input name="skip_weight" id="skip_weight" value="0.1" /> | |
| (Ignored for inverse model) | |
| </div> | |
| <div> | |
| <label for="success_prob">Success probability</label> | |
| <input name="success_prob" id="success_prob" value="1" /> | |
| (Use 1 for inverse model) | |
| </div> | |
| <div> | |
| <label for="psf">Scale factor (m)</label> | |
| <input name="psf" id="psf" value="2" /> | |
| </div> | |
| <div> | |
| <label for="max_levels">Max levels</label> | |
| <input name="max_levels" id="max_levels" value="150" /> | |
| </div> | |
| <div> | |
| <label for="iters">Simulation iterations</label> | |
| <input name="iters" id="iters" value="10000" /> | |
| </div> | |
| <div> | |
| <input id="compute" type="button" value="Compute!" /> | |
| </div> | |
| <div id="histograms"> | |
| </div> | |
| <table id="rune_stats"> | |
| </table> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment