├── .gitignore ├── LICENSE.txt ├── README.md ├── bin └── p.js ├── package.json ├── pragmatics.pjs ├── probabilistic ├── STKernel.js ├── control.js ├── erp.js ├── index.js ├── inference.js ├── marginalize.js ├── memoize.js ├── trace.js ├── transform.js └── util.js ├── sandbox.js ├── test.pjs └── webppl ├── codemirror ├── codemirror.css ├── codemirror.js ├── continuecomment.js ├── docs.css ├── javascript.js └── matchbrackets.js ├── index.html └── pragmaticstest.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | webppl/probabilistic.js 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | probabilistic-js Release License 3 | =============================================================================== 4 | MIT License 5 | 6 | Copyright (C) 2013 Stanford University. 7 | All rights reserved. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the "Software"), to deal in 11 | the Software without restriction, including without limitation the rights to 12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 13 | the Software, and to permit persons to whom the Software is furnished to do so, 14 | subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 21 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 22 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 23 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | probabilistic-js 2 | ================ 3 | 4 | Turning Javascript into a probabilistic programming language, following the approach presented in [this paper](http://www.stanford.edu/~ngoodman/papers/lightweight-mcmc-aistats2011.pdf) and [this talk](http://videolectures.net/aistats2011_wingate_lightweight/). 5 | 6 | probabilistic-js works via _source code transformation_: you write probabilistic code in `.pjs` files which are transformed into plain-old deterministic Javascript in `.js` files before being executed. For example: 7 | 8 | node bin/p.js [-p] ./probcode.pjs 9 | 10 | The `-p` option, if present, will keep around the transformed `.js` code, which can be useful for debugging in some cases. Take a look at `test.pjs` for some simple examples. 11 | 12 | Programs written in probabilistic-js can also be run in the browser via [browserify](https://github.com/substack/node-browserify). The `webppl` directory contains the framework for a simple example (using the excellent [CodeMirror](http://codemirror.net/) widget). Just use the command: 13 | 14 | browserify -r ./probabilistic/index -r ./probabilistic/transform -r ./probabilistic/util > webppl/probabilistic.js 15 | 16 | Or if you don't have browserify set up with a command alias, call it with `node node_modules/browserify/bin/cmd.js` 17 | 18 | A running instance of this web demo can be found [here](http://dritchie.github.io/probabilistic-js). -------------------------------------------------------------------------------- /bin/p.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs") 2 | var path = require("path") 3 | var transform = require("probabilistic/transform") 4 | var browserify = require("browserify") 5 | 6 | if (require.main === module) 7 | { 8 | var usage = "usage: node p.js [-p] srcfile" 9 | 10 | var numargs = process.argv.length 11 | if (numargs < 3 || numargs > 4 || (numargs == 4 && process.argv[2] != "-p")) 12 | { 13 | console.log(usage) 14 | process.exit(1) 15 | } 16 | 17 | var srcfile 18 | if (numargs == 3) 19 | srcfile = process.argv[2] 20 | else 21 | srcfile = process.argv[3] 22 | srcfile = fs.realpathSync(srcfile) 23 | var keepCompiledCode = (numargs === 4) 24 | 25 | // Flatten module dependency graph using browserify 26 | // ISSUE: browserify replaces certain core node modules (such as 'process') with 27 | // browser-compatible shims that may not have the full functionality of the 28 | // original module or may depend on browser concepts (e.g. window) existing. 29 | // This is not ideal for running probabilistic-js on the command line through 30 | // node, but I'm willing to tolerate it for now b/c browser execution will 31 | // be the more common use-case. 32 | // FIX: browserify uses a module 'browser-builtins' to get these shims. It would 33 | // be a simple matter of replacing the 'browserBuiltins' var with '{}' to fix 34 | // this issue; however, modifying browserify comes with its own baggage. I'm 35 | // going to try and convince substack to make a change to the code that will 36 | // allow us to toggle browser-builtins on/off. 37 | var b = browserify(srcfile) 38 | function browserifyDone(err, src) 39 | { 40 | // Run probTransform for callsite naming 41 | src = transform.probTransform(src) 42 | 43 | // Write this "compiled" code to a .js file 44 | var compiledfile = path.join(path.dirname(srcfile), path.basename(srcfile).replace(path.extname(srcfile), ".js")) 45 | fs.writeFileSync(compiledfile, src) 46 | 47 | // 'require' the compiled code file to run it 48 | try 49 | { 50 | require(compiledfile) 51 | } 52 | finally 53 | { 54 | // If the user gave us the -p option, keep the compiled code around. 55 | // Otehrwise, delete it. 56 | if (!keepCompiledCode) 57 | fs.unlinkSync(compiledfile) 58 | } 59 | } 60 | var bundlestream = b.bundle({},browserifyDone) 61 | } 62 | 63 | 64 | // TODO: node package file that specifies all the dependencies. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "probabilistic-js", 3 | "version": "0.0.1", 4 | "description": "probabilistic programming (in the browser, even!) using JS", 5 | "main": "probabilistic/index.js", 6 | "bin": { 7 | "pjs": "bin/p.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/dritchie/probabilistic-js.git" 12 | }, 13 | "dependencies": { 14 | "esprima": "~1.0.4", 15 | "escodegen": "~0.0.26", 16 | "browserify": "~2.29.1" 17 | }, 18 | "author" : { 19 | "name" : "Daniel Ritchie", 20 | "email" : "dritchie@stanford.edu", 21 | "url" : "http://stanford.edu/~dritchie" 22 | }, 23 | "license" : "MIT" 24 | } -------------------------------------------------------------------------------- /pragmatics.pjs: -------------------------------------------------------------------------------- 1 | /* 2 | testing the nested-query pragmatics model in js 3 | */ 4 | 5 | //var util = require("./probabilistic/util") 6 | //util.openModule(util) 7 | //var pr = require("./probabilistic/index") 8 | //util.openModule(pr) 9 | 10 | 11 | ///////////////////////////// 12 | ////there is a useful pattern. if we have a marginalized fn: 13 | //var foo = marginalize(prob(function(x) { ...})) 14 | ////and we condition on it returning a particular value: 15 | //condition(foo(x) == val) 16 | ////we should avoid sampling the foo ERP... instead use isConditioned: 17 | //var foo = marginalizeConditioned(prob(function(x) { ...})) 18 | //condition(foo(x, val) == val) 19 | ////this avoids setting and resampling the conditioned ERP. (does not work with rejection sampling!) 20 | ///////////////////////////// 21 | 22 | var meaning = function(utterance) 23 | { 24 | if(utterance == "foo"){return function(w){return w}} //"foo" is true only in true worlds. 25 | return function(w){return true} //"bar" is always true 26 | } 27 | 28 | var listener = function(utterance) { 29 | var world = flip(); //world prior 30 | //speaker(world, utterance) 31 | condition(speaker.conditionTo(utterance)(world) == utterance) 32 | return world 33 | } 34 | 35 | 36 | var speaker = marginalize( 37 | function(world) { 38 | var utterance = flip() ? "foo" : "bar" 39 | //litlistener(utterance, world) 40 | condition(litlistener.conditionTo(world)(utterance) == world) 41 | return utterance 42 | }, samplefn, samplesPerLevel) 43 | 44 | 45 | var litlistener = marginalize( 46 | function(utterance) 47 | { 48 | var world = flip(); //world prior 49 | condition(meaning(utterance)(world)) //simple stand-in for truth-functional meaning.. 50 | return world 51 | }, samplefn, samplesPerLevel) 52 | 53 | ///////////////////////////// 54 | 55 | 56 | //means=[] 57 | //for(i=0;i<10;i++) 58 | //{ 59 | // var hist = distrib( , 60 | // traceMH, 61 | // 100) 62 | // console.log(hist[0]) 63 | // means[i] = hist[0] 64 | //} 65 | 66 | var comp = function() {return listener("bar")} 67 | 68 | var samplefn = traceMH //bunchaRejectionSample 69 | 70 | var samplesPerLevel = 500 71 | 72 | function variance(values) 73 | { 74 | sq = [] 75 | m = mean(values) 76 | for(i=0; i1e-12) { 23 | bin= (bin+1)%numprobs 24 | //put as much or source into next bin as will fit.. 25 | var fill = Math.min(source, infill[bin]) 26 | source -= fill 27 | infill[bin] -= fill 28 | transition[sourcebin][bin]=fill 29 | } 30 | } 31 | return transition 32 | } 33 | 34 | 35 | function STKernel() 36 | { 37 | this.proposalsMade = 0 38 | this.STproposalsMade = 0 39 | //NOTE: Only make ST updates to non-structural, enumerable vars: 40 | this.pred = function(rec){return !rec.structural && (typeof rec.erp.nextVal === 'function')} 41 | } 42 | 43 | STKernel.prototype.next = function STKernel_next(trace) { 44 | this.proposalsMade += 1 45 | 46 | var trace = trace.deepcopy() 47 | var currNames = trace.freeVarNames(this.pred) 48 | 49 | var name = util.randomChoice(currNames) 50 | var v = trace.getRecord(name) 51 | var origval = v.val 52 | 53 | //enumerate all the values of the chosen variable, score trace for each 54 | var vals = [] 55 | var probs = [] 56 | var val = v.erp.nextVal(null, v.params) 57 | while(val!=null) { 58 | vals.push(val) 59 | v.val = val 60 | v.logprob = v.erp.logprob(val, v.params) 61 | trace.traceUpdate(true) 62 | probs.push(trace.conditionsSatisfied?Math.exp(trace.logprob):0) 63 | val = v.erp.nextVal(val, v.params) 64 | } 65 | 66 | //normalize 67 | var n=0 68 | for(var i=0; i a; if the remainder of the model results in an error for b > a 56 | // then we're in trouble; inference can't proceed. by declaring 57 | // compareSupport(), we catch this problem earlier (inside of 58 | // RandomExecutionTrace.prototype.lookup), avoiding model code 59 | // errors 60 | 61 | // RandomPrimitive.prototype.compareSupport = function ERP_compareSupport(params) { 62 | // ... 63 | // } 64 | 65 | 66 | /////////////////////////////////////////////////////////////////////////////// 67 | 68 | 69 | function FlipRandomPrimitive() {} 70 | FlipRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 71 | 72 | FlipRandomPrimitive.prototype.sample_impl = function Flip_sample_impl(params) 73 | { 74 | return (Math.random() < params[0])+0 75 | } 76 | 77 | FlipRandomPrimitive.prototype.logprob = function Flip_logprob(val, params) 78 | { 79 | return Math.log(val ? params[0] : 1-params[0]) 80 | } 81 | 82 | FlipRandomPrimitive.prototype.proposal = function Flip_proposal(currval, params) 83 | { 84 | return !currval 85 | } 86 | 87 | FlipRandomPrimitive.prototype.logProposalProb = function Flip_logProposalProb(currval, propval, params) 88 | { 89 | return 0 90 | } 91 | 92 | FlipRandomPrimitive.prototype.nextVal = function Flip_nextVal(currval, params) 93 | { 94 | 95 | if (currval == null) { 96 | return 0; 97 | } else if (currval === 0) { 98 | return 1 99 | } 100 | return null; 101 | }; 102 | 103 | var flipInst = new FlipRandomPrimitive() 104 | var flip = function flip(p, isStructural, conditionedValue) 105 | { 106 | p = (p == undefined) ? 0.5 : p 107 | return flipInst.sample([p], isStructural, conditionedValue) + 0 108 | } 109 | 110 | 111 | /////////////////////////////////////////////////////////////////////////////// 112 | 113 | 114 | function MultinomialRandomPrimitive() {} 115 | MultinomialRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 116 | 117 | function multinomial_sample(theta) 118 | { 119 | var k = theta.length 120 | var thetasum = 0 121 | for (var i = 0; i < k; i++) {thetasum += theta[i]} 122 | var x = Math.random() * thetasum 123 | var probAccum = 0 124 | for(var i=0; i= x) {return i} //FIXME: if x=0 returns i=0, but this isn't right if theta[0]==0... 127 | } 128 | return k 129 | } 130 | 131 | function multinomial_logprob(n, theta) 132 | { 133 | var k = theta.length 134 | if (n < 0 || n >= k) 135 | { 136 | return -Infinity 137 | } 138 | n = Math.round(n) 139 | var thetasum = 0 140 | for (var i = 0; i < k; i++) 141 | thetasum += theta[i] 142 | return Math.log(theta[n]/thetasum) 143 | } 144 | 145 | MultinomialRandomPrimitive.prototype.compareSupport = function Multinomial_compareSupport(params1, params2) { 146 | return (params1.length == params2.length) 147 | } 148 | 149 | MultinomialRandomPrimitive.prototype.sample_impl = function Multinomial_sample_impl(params) 150 | { 151 | return multinomial_sample(params) 152 | } 153 | 154 | MultinomialRandomPrimitive.prototype.logprob = function Multinomial_logprob(val, params) 155 | { 156 | return multinomial_logprob(val, params) 157 | } 158 | 159 | // Multinomial with currval projected out 160 | MultinomialRandomPrimitive.prototype.proposal = function Multinomial_proposal(currval, params) 161 | { 162 | var newparams = params.slice() 163 | newparams[currval] = 0 164 | return multinomial_sample(newparams) 165 | } 166 | 167 | // Multinomial with currval projected out 168 | MultinomialRandomPrimitive.prototype.logProposalProb = function Multinomial_logProposalProb(currval, propval, params) 169 | { 170 | var newparams = params.slice() 171 | newparams[currval] = 0 172 | return multinomial_logprob(propval, newparams) 173 | } 174 | 175 | // try an index. if it has zero probability, keep trying 176 | // until we find one that doesn't 177 | MultinomialRandomPrimitive.prototype.nextVal = function Multinomial_nextVal(currval, params) 178 | { 179 | 180 | if (currval == null) { 181 | return 0 182 | } else if (currval < params.length-1) { 183 | return currval+1 184 | } 185 | return null 186 | 187 | } 188 | 189 | var multinomialInst = new MultinomialRandomPrimitive() 190 | var multinomial = function multinomial(theta, isStructural, conditionedValue) 191 | { 192 | return multinomialInst.sample(theta, isStructural, conditionedValue) + 0 193 | } 194 | var multinomialDraw = function multinomialDraw(items, probs, isStructural, conditionedValue) 195 | { 196 | var conditionedValueIndex = items.map(JSON.stringify).indexOf(JSON.stringify(conditionedValue)) 197 | conditionedValue = (conditionedValueIndex == -1) ? undefined : conditionedValueIndex 198 | var result = items[multinomial(probs, isStructural, conditionedValue)] 199 | return result; 200 | } 201 | 202 | var uniformDraw = function uniformDraw(items, isStructural, conditionedValue) 203 | { 204 | var probs = [] 205 | for (var i = 0; i < items.length; i++) 206 | probs[i] = 1/items.length 207 | return items[multinomial(probs, isStructural, conditionedValue)] 208 | } 209 | 210 | /////////////////////////////////////////////////////////////////////////////// 211 | 212 | 213 | function UniformRandomPrimitive() {} 214 | UniformRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 215 | 216 | 217 | UniformRandomPrimitive.prototype.compareSupport = function Uniform_compareSupport(params1, params2) { 218 | return params1[0] == params2[0] && params1[1] == params2[1]; 219 | } 220 | 221 | UniformRandomPrimitive.prototype.sample_impl = function Uniform_sample_impl(params) 222 | { 223 | var u = Math.random() 224 | return (1-u)*params[0] + u*params[1] 225 | } 226 | 227 | UniformRandomPrimitive.prototype.logprob = function Uniform_logprob(val, params) 228 | { 229 | if (val < params[0] || val > params[1]) 230 | return -Infinity 231 | return -Math.log(params[1] - params[0]) 232 | } 233 | 234 | var uniformInst = new UniformRandomPrimitive() 235 | var uniform = function(lo, hi, isStructural, conditionedValue) 236 | { 237 | return uniformInst.sample([lo, hi], isStructural, conditionedValue) + 0 238 | } 239 | 240 | 241 | /////////////////////////////////////////////////////////////////////////////// 242 | 243 | 244 | function GaussianRandomPrimitive() {} 245 | GaussianRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 246 | 247 | function gaussian_sample(mu,sigma) 248 | { 249 | var u, v, x, y, q; 250 | 251 | do 252 | { 253 | u = 1 - Math.random(); 254 | v = 1.7156 * (Math.random() - .5); 255 | x = u - 0.449871; 256 | y = Math.abs(v) + 0.386595; 257 | q = x*x + y*(0.196*y - 0.25472*x); 258 | } 259 | while(q >= 0.27597 && (q > 0.27846 || v*v > -4 * u * u * Math.log(u))) 260 | 261 | return mu + sigma*v/u; 262 | } 263 | 264 | function gaussian_logprob(x, mu, sigma) 265 | { 266 | return -.5*(1.8378770664093453 + 2*Math.log(sigma) + (x - mu)*(x - mu)/(sigma*sigma)) 267 | } 268 | 269 | GaussianRandomPrimitive.prototype.sample_impl = function Gaussian_sample_impl(params) 270 | { 271 | return gaussian_sample(params[0], params[1]) 272 | } 273 | 274 | GaussianRandomPrimitive.prototype.logprob = function Gaussian_logprob(val, params) 275 | { 276 | return gaussian_logprob(val, params[0], params[1]) 277 | } 278 | 279 | // Drift kernel 280 | GaussianRandomPrimitive.prototype.proposal = function Gaussian_proposal(currval, params) 281 | { 282 | return gaussian_sample(currval, params[1]) 283 | } 284 | 285 | // Drift kernel 286 | GaussianRandomPrimitive.prototype.logProposalProb = function Gaussian_logProposalProb(currval, propval, params) 287 | { 288 | return gaussian_logprob(propval, currval, params[1]) 289 | } 290 | 291 | var gaussianInst = new GaussianRandomPrimitive() 292 | var gaussian = function gaussian(mu, sigma, isStructural, conditionedValue) 293 | { 294 | return gaussianInst.sample([mu, sigma], isStructural, conditionedValue) + 0 295 | } 296 | 297 | /////////////////////////////////////////////////////////////////////////////// 298 | 299 | 300 | function GammaRandomPrimitive() {} 301 | GammaRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 302 | 303 | function gamma_sample(a,b) 304 | { 305 | if(a < 1) return gamma_sample(1+a,b) * Math.pow(Math.random(), 1/a); 306 | 307 | var x,v,u; 308 | var d = a-1/3; 309 | var c = 1/Math.sqrt(9*d); 310 | 311 | while(true) 312 | { 313 | do{x = gaussian_sample(0,1); v = 1+c*x;} while(v <= 0); 314 | 315 | v=v*v*v; 316 | u=Math.random(); 317 | 318 | if((u < 1 - .331*x*x*x*x) || (Math.log(u) < .5*x*x + d*(1 - v + Math.log(v)))) return b*d*v; 319 | } 320 | } 321 | 322 | var cof = [76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5] 323 | function log_gamma(xx) 324 | { 325 | var x = xx - 1.0; 326 | var tmp = x + 5.5; tmp -= (x + 0.5)*Math.log(tmp); 327 | var ser=1.000000000190015; 328 | for (j=0;j<=5;j++){ x++; ser += cof[j]/x; } 329 | return -tmp+Math.log(2.5066282746310005*ser); 330 | } 331 | 332 | function gamma_logprob(x,a,b) 333 | { 334 | return (a - 1)*Math.log(x) - x/b - log_gamma(a) - a*Math.log(b); 335 | } 336 | 337 | GammaRandomPrimitive.prototype.sample_impl = function Gamma_sample_impl(params) 338 | { 339 | return gamma_sample(params[0], params[1]) 340 | } 341 | 342 | GammaRandomPrimitive.prototype.logprob = function Gamma_logprob(val, params) 343 | { 344 | return gamma_logprob(val, params[0], params[1]) 345 | } 346 | 347 | var gammaInst = new GammaRandomPrimitive() 348 | var gamma = function gamma(a, b, isStructural, conditionedValue) 349 | { 350 | return gammaInst.sample([a, b], isStructural, conditionedValue) + 0 351 | } 352 | 353 | 354 | /////////////////////////////////////////////////////////////////////////////// 355 | 356 | function ExponentialRandomPrimitive() {} 357 | ExponentialRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 358 | 359 | // We can just generate from a uniform on the unit interval, 360 | // take its log, and divide by the negative of the rate 361 | // HT http://en.wikipedia.org/wiki/Exponential_distribution#Generating_exponential_variates 362 | function exponential_sample(a) { 363 | var u = Math.random(); 364 | return Math.log(u) / (-1 * a); 365 | } 366 | 367 | ExponentialRandomPrimitive.prototype.sample_impl = function Exponential_sample_impl(params) 368 | { 369 | return exponential_sample(params[0]) 370 | } 371 | 372 | function exponential_logprob(val, a) { 373 | return Math.log(a) - a * val; 374 | } 375 | 376 | ExponentialRandomPrimitive.prototype.logprob = function Exponential_logprob(val, params) 377 | { 378 | return exponential_logprob(val, params[0]) 379 | } 380 | 381 | var exponentialInst = new ExponentialRandomPrimitive() 382 | 383 | var exponential = function exponential(a, isStructural, conditionedValue) 384 | { 385 | return exponentialInst.sample([a], isStructural, conditionedValue) + 0 386 | } 387 | 388 | 389 | /////////////////////////////////////////////////////////////////////////////// 390 | 391 | 392 | 393 | function BetaRandomPrimitive() {} 394 | BetaRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 395 | 396 | function beta_sample(a, b) 397 | { 398 | var x = gamma_sample(a, 1); 399 | return x / (x + gamma_sample(b, 1)); 400 | } 401 | 402 | function log_beta(a, b) 403 | { 404 | return log_gamma(a) + log_gamma(b) - log_gamma(a+b) 405 | } 406 | 407 | function beta_logprob(x, a, b) 408 | { 409 | if (x > 0 && x < 1) 410 | return (a-1)*Math.log(x) + (b-1)*Math.log(1-x) - log_beta(a,b) 411 | else return -Infinity 412 | } 413 | 414 | BetaRandomPrimitive.prototype.sample_impl = function Beta_sample_impl(params) 415 | { 416 | return beta_sample(params[0], params[1]) 417 | } 418 | 419 | BetaRandomPrimitive.prototype.logprob = function Beta_logprob(val, params) 420 | { 421 | return beta_logprob(val, params[0], params[1]) 422 | } 423 | 424 | var betaInst = new BetaRandomPrimitive() 425 | var beta = function beta(a, b, isStructural, conditionedValue) 426 | { 427 | return betaInst.sample([a, b], isStructural, conditionedValue) + 0 428 | } 429 | 430 | 431 | /////////////////////////////////////////////////////////////////////////////// 432 | 433 | 434 | function BinomialRandomPrimitive() {} 435 | BinomialRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 436 | 437 | function binomial_sample(p,n) 438 | { 439 | var k = 0; 440 | var N = 10; 441 | 442 | var a, b; 443 | while(n > N) 444 | { 445 | a = 1 + n/2; 446 | b = 1 + n-a; 447 | 448 | var x = beta_sample(a,b); 449 | 450 | if(x >= p){ n = a-1; p /= x; } 451 | else{ k += a; n = b - 1; p = (p-x) / (1-x); } 452 | } 453 | 454 | var u; 455 | for(var i=0; i= n) return -Infinity 478 | var q = 1-p 479 | var S = s + inv2 480 | var T = n - s - inv2 481 | var d1 = s + inv6 - (n + inv3) * p 482 | var d2 = q/(s+inv2) - p/(T+inv2) + (q-inv2)/(n+1) 483 | var d2 = d1 + 0.02*d2 484 | var num = 1 + q * g(S/(n*p)) + p * g(T/(n*q)) 485 | var den = (n + inv6) * p * q 486 | var z = num / den 487 | var invsd = Math.sqrt(z) 488 | z = d2 * invsd 489 | return gaussian_logprob(z, 0, 1) + Math.log(invsd) 490 | } 491 | 492 | BinomialRandomPrimitive.prototype.sample_impl = function Binomial_sample_impl(params) 493 | { 494 | return binomial_sample(params[0], params[1]) 495 | } 496 | 497 | BinomialRandomPrimitive.prototype.logprob = function Binomial_logprob(val, params) 498 | { 499 | return binomial_logprob(val, params[0], params[1]) 500 | } 501 | 502 | var binomialInst = new BinomialRandomPrimitive() 503 | var binomial = function binomial(p, n, isStructural, conditionedValue) 504 | { 505 | return binomialInst.sample([p,n], isStructural, conditionedValue) + 0 506 | } 507 | 508 | 509 | /////////////////////////////////////////////////////////////////////////////// 510 | 511 | 512 | function PoissonRandomPrimitive() {} 513 | PoissonRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 514 | 515 | function poisson_sample(mu) 516 | { 517 | var k = 0; 518 | 519 | while(mu > 10) 520 | { 521 | var m = 7/8*mu; 522 | var x = gamma_sample(m, 1); 523 | 524 | if (x > mu) 525 | { 526 | return (k + binomial_sample(mu/x, m-1)) | 0; 527 | } 528 | else 529 | { 530 | mu -= x; 531 | k += m; 532 | } 533 | } 534 | 535 | var emu = Math.exp(-mu); 536 | var p = 1; 537 | do{ p *= Math.random(); k++; } while(p > emu); 538 | 539 | return (k-1) | 0; 540 | } 541 | 542 | function fact(x) 543 | { 544 | var t=1; 545 | while(x>1) t*=x--; 546 | return t; 547 | } 548 | 549 | function lnfact(x) 550 | { 551 | if (x < 1) x = 1; 552 | 553 | if (x < 12) return Math.log(fact(Math.round(x))); 554 | 555 | var invx = 1 / x; 556 | var invx2 = invx * invx; 557 | var invx3 = invx2 * invx; 558 | var invx5 = invx3 * invx2; 559 | var invx7 = invx5 * invx2; 560 | 561 | var sum = ((x + 0.5) * Math.log(x)) - x; 562 | sum += Math.log(2*Math.PI) / 2; 563 | sum += (invx / 12) - (invx3 / 360); 564 | sum += (invx5 / 1260) - (invx7 / 1680); 565 | 566 | return sum; 567 | } 568 | 569 | function poisson_logprob(k, mu) 570 | { 571 | return k * Math.log(mu) - mu - lnfact(k) 572 | } 573 | 574 | PoissonRandomPrimitive.prototype.sample_impl = function Poisson_sample_impl(params) 575 | { 576 | return poisson_sample(params[0]) 577 | } 578 | 579 | PoissonRandomPrimitive.prototype.logprob = function Poisson_logprob(val, params) 580 | { 581 | return poisson_logprob(val, params[0]) 582 | } 583 | 584 | var poissonInst = new PoissonRandomPrimitive() 585 | var poisson = function poisson(mu, isStructural, conditionedValue) 586 | { 587 | return poissonInst.sample([mu], isStructural, conditionedValue) + 0 588 | } 589 | 590 | 591 | /////////////////////////////////////////////////////////////////////////////// 592 | 593 | 594 | function DirichletRandomPrimitive() {} 595 | DirichletRandomPrimitive.prototype = Object.create(RandomPrimitive.prototype) 596 | 597 | function dirichlet_sample(alpha) 598 | { 599 | var ssum = 0 600 | var theta = [] 601 | var t; 602 | for (var i = 0; i < alpha.length; i++) 603 | { 604 | t = gamma_sample(alpha[i], 1) 605 | theta[i] = t 606 | ssum = ssum + t 607 | } 608 | for (var i = 0; i < theta.length; i++) { 609 | theta[i] /= ssum 610 | theta[i] = Math.max(Number.MIN_VALUE, theta[i]) 611 | } 612 | return theta 613 | } 614 | 615 | function dirichlet_logprob(theta, alpha) 616 | { 617 | var asum = 0 618 | for (var i = 0; i < alpha.length; i++) asum += alpha[i] 619 | var logp = log_gamma(asum) 620 | for (var i = 0; i < alpha.length; i++) 621 | { 622 | logp += (alpha[i]-1)*Math.log(theta[i]) 623 | logp -= log_gamma(alpha[i]) 624 | } 625 | return logp 626 | } 627 | 628 | DirichletRandomPrimitive.prototype.sample_impl = function Dirichlet_sample_impl(params) 629 | { 630 | return dirichlet_sample(params) 631 | } 632 | 633 | DirichletRandomPrimitive.prototype.logprob = function Dirichlet_logprob(val, params) 634 | { 635 | return dirichlet_logprob(val, params) 636 | } 637 | 638 | var dirichletInst = new DirichletRandomPrimitive() 639 | var dirichlet = function dirichlet(alpha, isStructural, conditionedValue) 640 | { 641 | return dirichletInst.sample(alpha, isStructural, conditionedValue).concat([]) 642 | } 643 | 644 | 645 | /////////////////////////////////////////////////////////////////////////////// 646 | 647 | 648 | module.exports = 649 | { 650 | RandomPrimitive: RandomPrimitive, 651 | flip: flip, 652 | multinomial_logprob: multinomial_logprob, 653 | multinomial: multinomial, 654 | multinomialDraw: multinomialDraw, 655 | uniformDraw: uniformDraw, 656 | uniform: uniform, 657 | gaussian_logprob: gaussian_logprob, 658 | gaussian: gaussian, 659 | gamma_logprob: gamma_logprob, 660 | gamma: gamma, 661 | beta_logprob: beta_logprob, 662 | beta: beta, 663 | binomial_logprob: binomial_logprob, 664 | binomial: binomial, 665 | poisson_logprob: poisson_logprob, 666 | poisson: poisson, 667 | dirichlet_logprob: dirichlet_logprob, 668 | dirichlet: dirichlet, 669 | exponential: exponential, 670 | exponential_logprob: exponential_logprob 671 | } 672 | -------------------------------------------------------------------------------- /probabilistic/index.js: -------------------------------------------------------------------------------- 1 | var trace = require("./trace") 2 | var erp = require("./erp") 3 | var control = require("./control") 4 | var inference = require("./inference") 5 | var memoize = require("./memoize") 6 | var marginalize = require("./marginalize") 7 | var transform = require("./transform") 8 | 9 | module.exports = {} 10 | 11 | // Forward trace exports 12 | for (var prop in trace) 13 | module.exports[prop] = trace[prop] 14 | 15 | // Forward erp exports 16 | for (var prop in erp) 17 | module.exports[prop] = erp[prop] 18 | 19 | // Forward control exports 20 | for (var prop in control) 21 | module.exports[prop] = control[prop] 22 | 23 | // Forward inference exports 24 | for (var prop in inference) 25 | module.exports[prop] = inference[prop] 26 | 27 | // Forward memoize exports 28 | for (var prop in memoize) 29 | module.exports[prop] = memoize[prop] 30 | 31 | // Forward marginalize exports 32 | for (var prop in marginalize) 33 | module.exports[prop] = marginalize[prop] 34 | 35 | // Forward transform exports 36 | for (var prop in transform) 37 | module.exports[prop] = transform[prop] 38 | 39 | //// We also forward 'openModule' from util.js, 40 | //// b/c this makes source transformation easier. 41 | var util = require("./util") 42 | module.exports["openModule"] = util.openModule -------------------------------------------------------------------------------- /probabilistic/inference.js: -------------------------------------------------------------------------------- 1 | var trace = require("./trace") 2 | var util = require("./util") 3 | var STKernel = require("./STKernel") 4 | 5 | /* 6 | Compute the discrete distribution over the given computation 7 | Only appropriate for computations that return a discrete value 8 | (Variadic arguments are arguments to the sampling function) 9 | */ 10 | function distrib(computation, samplingFn) 11 | { 12 | var args = Array.prototype.slice.apply(arguments) 13 | var hist = {} 14 | var samps = samplingFn.apply(this, [computation].concat(args.slice(2))) 15 | for (var i = 0; i < samps.length; i++) 16 | { 17 | var stringrep = JSON.stringify(samps[i].sample) 18 | var prevval = hist[stringrep] || 0 19 | hist[stringrep] = prevval + 1 20 | } 21 | for (var s in hist) 22 | hist[s] /= samps.length 23 | return hist 24 | } 25 | 26 | /* 27 | Compute the mean of a set of values 28 | */ 29 | function mean(values) 30 | { 31 | var m = values[0] 32 | var n = values.length 33 | for (var i = 1; i < n; i++) 34 | m += values[i] 35 | return m / n 36 | } 37 | 38 | /* 39 | Compute the expected value of a computation 40 | Only appropraite for computations whose return value is a number 41 | */ 42 | function expectation(computation, samplingFn) 43 | { 44 | var args = Array.prototype.slice.apply(arguments) 45 | var samps = samplingFn.apply(this, [computation].concat(args.slice(2))) 46 | return mean(samps.map(function(s) { return s.sample })) 47 | } 48 | 49 | /* 50 | Maximum a posteriori inference (returns the highest probability sample) 51 | */ 52 | function MAP(computation, samplingFn) 53 | { 54 | var args = Array.prototype.slice.apply(arguments) 55 | var samps = samplingFn.apply(this, [computation].concat(args.slice(2))) 56 | var maxelem = {sample: null, logprob: -Infinity} 57 | var s = null 58 | for (var i = 0; i < samps.length; i++) 59 | { 60 | s = samps[i] 61 | if (s.logprob > maxelem.logprob) 62 | maxelem = s 63 | } 64 | return maxelem.sample 65 | } 66 | 67 | /* 68 | Rejection sample a result from computation that satisfies all 69 | conditioning expressions. 70 | Note: doesn't work if the desired log probability is higher than the 71 | probability of generating the state by running the program forwards 72 | (such as with a factor statement) 73 | */ 74 | function rejectionSample(computation) 75 | { 76 | var tr = trace.newTrace(computation) 77 | while (Math.log(Math.random()) > tr.logprob - tr.genlogprob) { 78 | tr = trace.newTrace(computation) 79 | } 80 | 81 | return tr.returnValue 82 | } 83 | 84 | ////same but return a bunch of samples in data structure that matches traceMH 85 | //function bunchaRejectionSample(computation, numsamps) 86 | //{ 87 | // var samps = [] 88 | // 89 | // for(i=0;i 0) 355 | { 356 | var lerpTrace = new LARJInterpolationTrace(oldStructTrace, newStructTrace) 357 | var prevAccepted = this.diffusionKernel.proposalsAccepted 358 | for (var aStep = 0; aStep < this.annealSteps; aStep++) 359 | { 360 | lerpTrace.alpha = aStep/(this.annealSteps-1) 361 | annealingLpRatio += lerpTrace.logprob 362 | lerpTrace = this.diffusionKernel.next(lerpTrace) 363 | annealingLpRatio -= lerpTrace.logprob 364 | } 365 | this.annealingProposalsMade += this.annealSteps 366 | this.annealingProposalsAccepted += (this.diffusionKernel.proposalsAccepted - prevAccepted) 367 | oldStructTrace = lerpTrace.trace1 368 | newStructTrace = lerpTrace.trace2 369 | } 370 | 371 | // Finalize accept/reject decision 372 | v = newStructTrace.getRecord(name) 373 | var rvsPropLP = v.erp.logProposalProb(propval, origval, v.params) + oldStructTrace.lpDiff(newStructTrace) - Math.log(newNumVars) 374 | var acceptanceProb = newStructTrace.logprob - currTrace.logprob + rvsPropLP - fwdPropLP + annealingLpRatio 375 | if (newStructTrace.conditionsSatisfied && Math.log(Math.random()) < acceptanceProb) 376 | { 377 | this.jumpProposalsAccepted++ 378 | return newStructTrace 379 | } 380 | return currTrace 381 | } 382 | 383 | LARJKernel.prototype.stats = function LARJKernel_stats() 384 | { 385 | var overallProposalsMade = this.jumpProposalsMade + this.diffusionProposalsMade 386 | var overallProposalsAccepted = this.jumpProposalsAccepted + this.diffusionProposalsAccepted 387 | if (this.diffusionProposalsMade > 0) 388 | { 389 | console.log("Diffusion acceptance ratio: " + (this.diffusionProposalsAccepted/this.diffusionProposalsMade) + 390 | " (" + this.diffusionProposalsAccepted + "/" + this.diffusionProposalsMade + ")") 391 | } 392 | if (this.jumpProposalsMade > 0) 393 | { 394 | console.log("Jump acceptance ratio: " + (this.jumpProposalsAccepted/this.jumpProposalsMade) + 395 | " (" + this.jumpProposalsAccepted + "/" + this.jumpProposalsMade + ")") 396 | } 397 | if (this.annealingProposalsMade > 0) 398 | { 399 | console.log("Annealing acceptance ratio: " + (this.annealingProposalsAccepted/this.annealingProposalsMade) + 400 | " (" + this.annealingProposalsAccepted + "/" + this.annealingProposalsMade + ")") 401 | } 402 | console.log("Overall acceptance ratio: " + (overallProposalsAccepted/overallProposalsMade) + 403 | " (" + overallProposalsAccepted + "/" + overallProposalsMade + ")") 404 | } 405 | 406 | function mcmc_thunk(computation, kernel, lag, verbose, init) { 407 | lag = (lag === undefined ? 1 : lag) 408 | init = (init == undefined ? "rejection" : init) 409 | kernel = (kernel == undefined ? new RandomWalkKernel() : kernel) 410 | var currentTrace = trace.newTrace(computation, init) 411 | return function() { 412 | for (var i = 0; i < lag; i++) { 413 | currentTrace = kernel.next(currentTrace); 414 | } 415 | return currentTrace.returnValue; 416 | } 417 | } 418 | 419 | /* 420 | Do MCMC for 'numsamps' iterations using a given transition kernel 421 | */ 422 | function mcmc(computation, kernel, numsamps, lag, verbose, init) 423 | { 424 | lag = (lag === undefined ? 1 : lag) 425 | init = (init == undefined ? "rejection" : init) 426 | var currentTrace = trace.newTrace(computation, init) 427 | var samps = [] 428 | var iters = numsamps*lag 429 | var t0 = new Date().getTime() 430 | for (var i = 0; i < iters; i++) 431 | { 432 | currentTrace = kernel.next(currentTrace) 433 | if (i % lag === 0) 434 | samps.push({sample: currentTrace.returnValue, logprob: currentTrace.logprob}) 435 | } 436 | if (verbose) 437 | { 438 | kernel.stats() 439 | var t1 = new Date().getTime() 440 | var tsecs = (t1-t0)/1000 441 | console.log("Time: ", tsecs) 442 | } 443 | return samps 444 | } 445 | 446 | /* 447 | Mixture kernel 448 | */ 449 | function mixKernel(k1,k2) { //FIXME: variable number, variable weights 450 | this.k1=k1 451 | this.k2=k2 452 | } 453 | 454 | mixKernel.prototype.next = function(trace){return Math.random()>0.5?this.k1.next(trace):this.k2.next(trace)} 455 | 456 | mixKernel.prototype.stats =function(){this.k1.stats(); this.k2.stats()} 457 | 458 | /* 459 | Sample from a probabilistic computation for some 460 | number of iterations using single-variable-proposal 461 | Metropolis-Hastings 462 | */ 463 | function traceMH(computation, numsamps, lag, verbose, init) 464 | { 465 | lag = (lag === undefined ? 1 : lag) 466 | return mcmc(computation, new RandomWalkKernel(), numsamps, lag, verbose, init) 467 | } 468 | 469 | 470 | /* 471 | Sample from a probabilistic computation using locally 472 | annealed reversible jump mcmc 473 | */ 474 | function LARJMH(computation, numsamps, annealSteps, jumpFreq, lag, verbose) 475 | { 476 | lag = (lag === undefined ? 1: lag) 477 | return mcmc(computation, 478 | new LARJKernel(new RandomWalkKernel(function(rec){return !rec.structural}), annealSteps, jumpFreq), 479 | numsamps, lag, verbose) 480 | } 481 | 482 | /* 483 | Sample from a probabilistic computation using Suwa-Todo (irreversible kernel) 484 | */ 485 | function traceST(computation, numsamps, lag, verbose, init) 486 | { 487 | lag = (lag === undefined ? 1 : lag) 488 | //make MH proposals to structural or non-enumerable ERPs: 489 | var rwkernel = new RandomWalkKernel(function(rec){return rec.structural || (typeof rec.erp.nextVal != 'function')}) 490 | var kernel = new mixKernel(new STKernel.STKernel(), rwkernel) 491 | return mcmc(computation, kernel, numsamps, lag, verbose, init) 492 | } 493 | 494 | 495 | /* 496 | Create conditional thunk. 497 | Options includes the algorithm and any algorithm-specific params. 498 | FIXME: do we need to return a thunk or an ERP (that knows how to score itself)? 499 | FIXME: finish 500 | */ 501 | 502 | function conditional(computation, options) { 503 | switch (options.algorithm) { 504 | case "traceMH": 505 | 506 | return mcmc_thunk(computation, new RandomWalkKernel(), options.lag, options.verbose, options.init) 507 | 508 | case "traceST": 509 | var rwkernel = new RandomWalkKernel(function(rec){return rec.structural || (typeof rec.erp.nextVal != 'function')}) 510 | var kernel = new mixKernel(new STKernel.STKernel(), rwkernel) 511 | return mcmc_thunk(computation, kernel, options.lag, options.verbose, options.init) 512 | 513 | case "LARJMH": 514 | 515 | return mcmc_thunk(computation, 516 | new LARJKernel(new RandomWalkKernel(function(rec){return !rec.structural}), annealSteps, jumpFreq), 517 | options.lag, options.verbose) 518 | 519 | case "enumerate": 520 | var items = [] 521 | var probs = [] 522 | var enumeration = enumerateDist(computation) 523 | for (key in enumeration) { 524 | items.push(enumeration[key].val) 525 | probs.push(enumeration[key].prob) 526 | } 527 | return function() { 528 | return items[util.discreteChoice(probs)]; 529 | } 530 | 531 | default: //rejection 532 | return function() { 533 | return rejectionSample(computation) 534 | } 535 | 536 | } 537 | 538 | } 539 | 540 | 541 | 542 | module.exports = 543 | { 544 | distrib: distrib, 545 | mean: mean, 546 | expectation: expectation, 547 | MAP: MAP, 548 | rejectionSample: rejectionSample, 549 | enumerateDist: enumerateDist, 550 | // bunchaRejectionSample: bunchaRejectionSample, 551 | mcmc_thunk: mcmc_thunk, 552 | traceMH: traceMH, 553 | LARJMH: LARJMH, 554 | traceST: traceST, 555 | conditional: conditional 556 | } 557 | -------------------------------------------------------------------------------- /probabilistic/marginalize.js: -------------------------------------------------------------------------------- 1 | 2 | //this file provides a utility for marginalizing a function. 3 | //it caches the marginal for each argument set, thus providing a simple dynamic programming construct. 4 | //note that because a marginal is created, this acts as a query boundary for any free random variables or constraints within the function. 5 | 6 | var erp = require("./erp") 7 | var inference = require("./inference") 8 | var trace = require("./trace") 9 | 10 | function MarginalRandomPrimitive(fn, samplingFn, samplingFnArgs) { 11 | this.fn = fn 12 | this.samplingFn = samplingFn 13 | this.samplingFnArgs = samplingFnArgs 14 | this.cache = {} 15 | } 16 | 17 | //why create an object to assign the primitive? instead of just assigning the primitive? 18 | MarginalRandomPrimitive.prototype = new erp.RandomPrimitive //Object.create(erp.RandomPrimitive.prototype) 19 | 20 | MarginalRandomPrimitive.prototype.clearCache = function clearCache() { 21 | this.cache = {} 22 | } 23 | 24 | //this is going to work ok, because traceUpdate is written properly to that it sets asside current trace state and reinstates it when done, hence nesting will do the right thing... 25 | MarginalRandomPrimitive.prototype.getDist = function getDist(args) { 26 | 27 | var stringedargs = JSON.stringify(args) //stringify to use as key. needed? 28 | 29 | if(!(stringedargs in this.cache)) { 30 | 31 | // console.log("Generating marginal for args " +stringedargs) 32 | 33 | var dist = {} 34 | var fn = this.fn 35 | //FIXME: check that the computation runs with same address in all locations 36 | var computation = function computation(){return fn.apply(this, args)} 37 | var samps = this.samplingFn.apply(this, [computation].concat(this.samplingFnArgs)) 38 | //var samps = inference.traceMH(computation, 100, 1) //TODO: which inference fn..? may want rejection or enumeration sometimes. 39 | 40 | for(i in samps) 41 | { 42 | var v = samps[i].sample 43 | if(dist[v] == undefined){dist[v]={}; dist[v].prob=0; dist[v].val=v} 44 | dist[v].prob = dist[v].prob + 1 45 | } 46 | for(v in dist) 47 | { 48 | dist[v].prob /= samps.length 49 | } 50 | 51 | this.cache[stringedargs] = dist 52 | } //else console.log("Re-using marginal for args " +stringedargs) 53 | return this.cache[stringedargs] 54 | } 55 | 56 | 57 | MarginalRandomPrimitive.prototype.sample_impl = function Marginal_sample_impl(params) 58 | { 59 | //note: assumes dist is normalized. 60 | var dist = this.getDist(params) 61 | var x = Math.random() 62 | var accum = 0 63 | for(v in dist) 64 | { 65 | accum += dist[v].prob //could store the CDF to avoid this sum. 66 | if(accum>=x) {return dist[v].val} 67 | } 68 | } 69 | 70 | MarginalRandomPrimitive.prototype.logprob = function Marginal_logprob(val, params) 71 | { 72 | //note: assumes dist is normalized. 73 | var dist = this.getDist(params) 74 | if(dist[val] == undefined) {return -Infinity} 75 | return Math.log(dist[val].prob) 76 | } 77 | 78 | 79 | //assume fn is a function to be marginalized.. 80 | //returns an ERP that computes and caches marginals of the original function. 81 | //computes marginal by using samplingFn and variadic args are args to the sampling function. 82 | marginalize = function marginalize(fn, samplingFn) 83 | { 84 | var samplingFnArgs = Array.prototype.slice.apply(arguments).slice(2) 85 | 86 | if(samplingFn == undefined){samplingFn = inference.traceMH; samplingFnArgs = [100, 1]} 87 | 88 | var marginalInt = new MarginalRandomPrimitive(fn, samplingFn, samplingFnArgs) 89 | 90 | var marginal = function marginal()//variadic.. 91 | { 92 | var args = Array.prototype.slice.apply(arguments) 93 | return marginalInt.sample(args) 94 | } 95 | 96 | //this lets you set a conditioned value for the marginal ERP, even though it's variadic: 97 | marginal.conditionTo = function conditionTo(conditionedValue){ 98 | return function(){ 99 | var args = Array.prototype.slice.apply(arguments) 100 | return marginalInt.sample(args,undefined,conditionedValue) 101 | } 102 | } 103 | 104 | marginal.clearCache = function() {marginalInt.clearCache()} 105 | 106 | marginal.logprob = function logprob(){ //variadic 107 | var args = Array.prototype.slice.apply(arguments) 108 | var val = args.pop() 109 | return marginalInt.logprob(val,args) 110 | } 111 | 112 | return marginal 113 | } 114 | 115 | 116 | /////////////////////////////////////////////////////////////////////////////// 117 | 118 | 119 | module.exports = 120 | { 121 | marginalize: marginalize 122 | } 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /probabilistic/memoize.js: -------------------------------------------------------------------------------- 1 | var trace = require("./trace") 2 | 3 | /* 4 | Wrapper around a function to memoize its results 5 | */ 6 | function mem(fn) 7 | { 8 | if (typeof fn !== 'function' ) { 9 | throw new Error('mem requires a function') 10 | } 11 | var cache = {} 12 | return function() 13 | { 14 | var str = JSON.stringify(arguments) 15 | var val = cache[str] 16 | if (val === undefined) 17 | { 18 | val = fn.apply(this, arguments) 19 | cache[str] = val 20 | } 21 | return val 22 | } 23 | } 24 | 25 | module.exports = 26 | { 27 | mem: mem 28 | } 29 | -------------------------------------------------------------------------------- /probabilistic/trace.js: -------------------------------------------------------------------------------- 1 | var util = require("./util") 2 | 3 | /* 4 | Callsite name management 5 | */ 6 | 7 | var idstack = [""] 8 | function enterfn(id) { idstack.push(idstack[idstack.length-1] + ":" + id) } 9 | function leavefn(id) { idstack.pop() } 10 | //Return the current structural name, as determined by the interpreter stack and loop counters of the trace: 11 | function currentName(trace){ 12 | var id = idstack[idstack.length-1] 13 | var loopnum = trace.loopcounters[id] || 0 14 | trace.loopcounters[id] = loopnum + 1 15 | return id + "." + loopnum 16 | } 17 | 18 | 19 | /* 20 | Variables generated by ERPs 21 | */ 22 | function RandomVariableRecord(name, erp, params, val, logprob, structural, conditioned) 23 | { 24 | this.name = name 25 | this.erp = erp 26 | this.params = params 27 | this.val = val 28 | this.logprob = logprob 29 | this.active = false //this is true if var is touched by traceUpdate (for keeping track of unused vars after MH proposal) 30 | this.structural = structural 31 | this.conditioned = conditioned 32 | } 33 | 34 | RandomVariableRecord.prototype.copy = function copy() 35 | { 36 | return new RandomVariableRecord(this.name, this.erp, this.params, this.val, 37 | this.logprob, this.structural, this.conditioned) 38 | } 39 | 40 | 41 | /* 42 | Execution trace generated by a probabilistic program. 43 | Tracks the random choices made and accumulates probabilities. 44 | */ 45 | function RandomExecutionTrace(computation, init) 46 | { 47 | init = (init == undefined ? "rejection" : init) 48 | this.computation = computation 49 | this.vars = {} 50 | this.varlist = [] 51 | this.currVarIndex = 0 52 | this.logprob = 0.0 53 | this.newlogprob = 0.0 54 | this.oldlogprob = 0.0 55 | this.loopcounters = {} 56 | this.conditionsSatisfied = false 57 | this.returnValue = null 58 | this.structureIsFixed = false 59 | this.enumerate = false //Enumeration mode sets new ERP calls to start of domain. 60 | 61 | if (init == "rejection") { 62 | while (!this.conditionsSatisfied) 63 | { 64 | this.reset() 65 | } 66 | } else if (init=="enumerate") { 67 | this.enumerate=true 68 | this.traceUpdate() 69 | this.enumerate=false 70 | while (!this.conditionsSatisfied) { 71 | if (!this.nextEnumState()) //try going to next state. 72 | {this.reset()} //if we didn't find a satsfying state, reset. 73 | } 74 | } else if (init=="lessdumb") { 75 | //if(this.enumerate) throw error("how did enumerate get turned on?") 76 | this.traceUpdate() 77 | var i=1, esteps=1 78 | while (!this.conditionsSatisfied || this.logprob === -Infinity) { 79 | if(i%esteps == 0) { 80 | //reset and initialize randomly: 81 | this.reset() 82 | //do more enumeration after every restart: 83 | esteps += 10 84 | } else { 85 | if (!this.nextEnumState()) //try going to next state. 86 | {this.reset()} //if we didn't find a satsfying state, reset. 87 | } 88 | i += 1 89 | } 90 | } 91 | } 92 | 93 | RandomExecutionTrace.prototype.reset = function reset() 94 | { 95 | this.vars = {} 96 | this.varlist = [] 97 | this.traceUpdate() 98 | } 99 | 100 | 101 | RandomExecutionTrace.prototype.deepcopy = function deepcopy() 102 | { 103 | var newdb = new RandomExecutionTrace(this.computation, false) 104 | newdb.logprob = this.logprob 105 | newdb.oldlogprob = this.oldlogprob 106 | newdb.newlogprob = this.newlogprob 107 | newdb.conditionsSatisfied = this.conditionsSatisfied 108 | newdb.returnValue = this.returnValue 109 | 110 | for (var i = 0; i < this.varlist.length; i++) 111 | { 112 | var newvar = this.varlist[i].copy() 113 | newdb.varlist.push(newvar) 114 | newdb.vars[newvar.name] = newvar 115 | } 116 | 117 | return newdb 118 | } 119 | 120 | //RandomExecutionTrace.prototype.freeVarNames = function freeVarNames(structural, nonstructural) 121 | //{ 122 | // structural = (structural == undefined ? true : structural) 123 | // nonstructural = (nonstructural == undefined ? true : nonstructural) 124 | // var names = [] 125 | // for (var i=0, rec; rec = this.varlist[i]; i++) 126 | // { 127 | // if () 128 | // {names.push(rec.name)} 129 | // } 130 | // return names 131 | //} 132 | RandomExecutionTrace.prototype.freeVarNames = function freeVarNames(pred) 133 | { 134 | pred = (pred == undefined ? function(r){return true} : pred) 135 | var names = [] 136 | for (var i=0, rec; rec = this.varlist[i]; i++) 137 | { 138 | if (!rec.conditioned && pred(rec)) {names.push(rec.name)} 139 | } 140 | return names 141 | } 142 | 143 | /* 144 | Names of variables that this trace has that the other does not 145 | */ 146 | RandomExecutionTrace.prototype.varDiff = function varDiff(other) 147 | { 148 | var arr = [] 149 | for (var name in this.vars) 150 | { 151 | if (!other.vars[name]) 152 | arr.push(name) 153 | } 154 | return arr 155 | } 156 | 157 | /* 158 | Difference in log probability between this trace and the other 159 | due to variables that this one has that the other does not 160 | */ 161 | RandomExecutionTrace.prototype.lpDiff = function lpDiff(other) 162 | { 163 | return this.varDiff(other) 164 | .map(function(name) {return this.vars[name].logprob}.bind(this)) 165 | .reduce(function(a,b) {return a+b}, 0) 166 | } 167 | 168 | /* 169 | The singleton trace object 170 | */ 171 | var trace = null 172 | 173 | /* 174 | Run computation and update this trace accordingly 175 | */ 176 | RandomExecutionTrace.prototype.traceUpdate = function traceUpdate(structureIsFixed) 177 | { 178 | 179 | if (gensym) { 180 | gensym.__gensymcounter__ = 0; 181 | } 182 | 183 | structureIsFixed = (structureIsFixed===undefined?false:structureIsFixed) 184 | 185 | 186 | 187 | var origtrace = trace 188 | trace = this 189 | 190 | this.logprob = 0.0 191 | this.newlogprob = 0.0 192 | this.genlogprob = 0.0 193 | this.loopcounters = {} 194 | this.conditionsSatisfied = true 195 | this.currVarIndex = 0 196 | this.structureIsFixed = structureIsFixed 197 | 198 | // If updating this trace can change the variable structure, then we 199 | // clear out the flat list of variables beforehand 200 | if (!structureIsFixed) { 201 | var oldvarlist = this.varlist 202 | this.varlist=[] 203 | } 204 | 205 | // Run the computation, creating/looking up random variables 206 | this.returnValue = this.computation() 207 | 208 | // Clean up 209 | this.loopcounters = {} 210 | 211 | this.oldlogprob = 0.0 212 | if (!structureIsFixed) { 213 | // Clear out any old random values that are no longer reachable 214 | for(var i=0,rec; rec = oldvarlist[i]; i++) { 215 | // before checking to see if the old random value is active, 216 | // make sure that it was not supplanted by a new value 217 | // (e.g., change of support) 218 | if( this.vars[rec.name] == rec && !rec.active) { 219 | if (!rec.conditioned) { 220 | this.oldlogprob += rec.logprob 221 | } 222 | delete this.vars[rec.name] 223 | } 224 | } 225 | } 226 | 227 | //reset active record marks for next traceUpdate.. 228 | for(var i=0, v; v=this.varlist[i]; i++) {v.active = false} 229 | 230 | // Reset the original singleton trace 231 | trace = origtrace 232 | } 233 | 234 | /* 235 | Propose a random change to a random variable 'varname' 236 | Returns a new sample trace from the computation and the 237 | forward and reverse probabilities of this proposal 238 | */ 239 | RandomExecutionTrace.prototype.proposeChange = function proposeChange(varname) 240 | { 241 | var nextTrace = this.deepcopy() 242 | var v = nextTrace.getRecord(varname) 243 | var propval = v.erp.proposal(v.val, v.params) 244 | var fwdPropLP = v.erp.logProposalProb(v.val, propval, v.params) 245 | var rvsPropLP = v.erp.logProposalProb(propval, v.val, v.params) 246 | v.val = propval 247 | v.logprob = v.erp.logprob(v.val, v.params) 248 | nextTrace.traceUpdate(!v.structural) 249 | fwdPropLP += nextTrace.genlogprob 250 | rvsPropLP += nextTrace.oldlogprob 251 | return [nextTrace, fwdPropLP, rvsPropLP] 252 | } 253 | 254 | 255 | /* 256 | Looks up the value of a random variable. 257 | Creates the variable if it does not already exist 258 | */ 259 | RandomExecutionTrace.prototype.lookup = function lookup(erp, params, isStructural, conditionedValue) 260 | { 261 | var record = null 262 | var name = null 263 | 264 | // If structure of this trace is fixed get variable from flatlist, otherwise do slower structural lookup 265 | if (this.structureIsFixed) { 266 | record = this.varlist[this.currVarIndex] 267 | } else { 268 | name = currentName(this) 269 | record = this.vars[name] 270 | if (record) { 271 | if (record.erp !== erp) { 272 | record = null 273 | } else { 274 | if ( record.structural !== isStructural || 275 | // if this ERP compares supports but we fail the 276 | // comparison, say that there's no record 277 | (erp.compareSupport && !erp.compareSupport(params, record.params)) 278 | ) { 279 | record = null; 280 | } 281 | } 282 | } 283 | } 284 | 285 | // If we didn't find the variable, create a new one 286 | if (!record) 287 | { 288 | if (this.enumerate && typeof erp.nextVal === 'function') { // If we are doing ennumeration init new vars to first val in domain: 289 | var val = erp.nextVal(null, params) 290 | } else { 291 | var val = (conditionedValue == undefined) ? erp.sample_impl(params) : conditionedValue 292 | } 293 | var ll = erp.logprob(val, params) 294 | this.newlogprob += ll 295 | if (conditionedValue == undefined) this.genlogprob += ll 296 | record = new RandomVariableRecord(name, erp, params, val, ll, isStructural, conditionedValue !== undefined) 297 | this.vars[name] = record 298 | } 299 | // Otherwise, reuse the variable we found, but check if its parameters/conditioning 300 | // status have changed 301 | else { 302 | record.conditioned = (conditionedValue != undefined) 303 | var hasChanges = false 304 | if (!util.arrayEquals(record.params, params)) 305 | { 306 | record.params = params 307 | hasChanges = true 308 | } 309 | if (conditionedValue != undefined && conditionedValue != record.val) 310 | { 311 | record.val = conditionedValue 312 | record.conditioned = true 313 | hasChanges = true 314 | } 315 | if (hasChanges) {record.logprob = erp.logprob(record.val, record.params)} 316 | } 317 | 318 | // Finish up and return 319 | this.currVarIndex++ 320 | this.logprob += record.logprob 321 | record.active = true 322 | if (!this.structureIsFixed){ this.varlist.push(record)} 323 | return record.val 324 | } 325 | 326 | // Simply retrieve the variable record associated with 'name' 327 | RandomExecutionTrace.prototype.getRecord = function getRecord(name) 328 | { 329 | return this.vars[name] 330 | } 331 | 332 | // Add a new factor into the log-likelihood of this trace 333 | RandomExecutionTrace.prototype.addFactor = function addFactor(num) 334 | { 335 | this.logprob += num 336 | } 337 | 338 | // Condition the trace on the value of a boolean expression 339 | RandomExecutionTrace.prototype.conditionOn = function conditionOn(boolexpr) 340 | { 341 | // Was formerely using the assignment operator (&&), but it doesn't work 342 | // if boolexpr is an array or object. 343 | this.conditionsSatisfied = this.conditionsSatisfied && boolexpr 344 | } 345 | 346 | 347 | //Next state for enumeration: 348 | RandomExecutionTrace.prototype.nextEnumState = function nextEnumState(strict) { 349 | this.enumerate=true 350 | var names = this.freeVarNames() 351 | 352 | var newval = null 353 | while (newval == null) { 354 | 355 | // if we are out of names it means we're done enumerating with no satisfying execution, return null. 356 | if (names.length == 0) {this.enumerate=false; return null} 357 | 358 | //otherwise get next var: 359 | var varname = names.pop() 360 | var v = this.getRecord(varname) 361 | 362 | //if the domain is enumerable, go to next value: 363 | if (typeof v.erp.nextVal === 'function') { 364 | var newval = v.erp.nextVal(v.val, v.params) 365 | if (newval == null) { 366 | v.val = v.erp.nextVal(null, v.params) //get first in domain 367 | } else { 368 | v.val = newval 369 | } 370 | v.logprob = v.erp.logprob(v.val, v.params) 371 | } else if (strict) { 372 | throw new Error("Cannot enumerate over continuous values") 373 | } 374 | } 375 | this.traceUpdate() 376 | this.enumerate=false 377 | return this 378 | } 379 | 380 | 381 | // Exported functions for interacting with the global trace 382 | 383 | function lookupVariableValue(erp, params, isStructural, conditionedValue) 384 | { 385 | if (!trace) 386 | { 387 | return conditionedValue === undefined ? erp.sample_impl(params) : conditionedValue 388 | } 389 | else 390 | { 391 | return trace.lookup(erp, params, isStructural, conditionedValue) 392 | } 393 | } 394 | 395 | function newTrace(computation, init) 396 | { 397 | return new RandomExecutionTrace(computation, init) 398 | } 399 | 400 | function factor(num) 401 | { 402 | if (trace) 403 | trace.addFactor(num) 404 | } 405 | 406 | function condition(boolexpr) 407 | { 408 | if (trace) 409 | trace.conditionOn(boolexpr) 410 | } 411 | 412 | 413 | module.exports = 414 | { 415 | enterfn: enterfn, 416 | leavefn: leavefn, 417 | // setmaxid: setmaxid, 418 | lookupVariableValue: lookupVariableValue, 419 | newTrace: newTrace, 420 | factor: factor, 421 | condition: condition 422 | } 423 | -------------------------------------------------------------------------------- /probabilistic/transform.js: -------------------------------------------------------------------------------- 1 | var esprima = require("esprima") 2 | var escodegen = require("escodegen") 3 | var estraverse = require("escodegen/node_modules/estraverse") 4 | 5 | 6 | // Add a string format method 7 | if (!String.prototype.format) { 8 | String.prototype.format = function() { 9 | var args = arguments; 10 | return this.replace(/{(\d+)}/g, function(match, number) { 11 | return typeof args[number] != 'undefined' 12 | ? args[number] 13 | : match 14 | ; 15 | }); 16 | }; 17 | } 18 | 19 | var replcode = "(function() {enterfn({0}); var ret = __p_REPLACEME_p__; leavefn(); return ret; }).apply(this)" 20 | 21 | 22 | function makeWrappedCallReplacer(callNode) 23 | { 24 | var replacer = 25 | { 26 | enter: function(node) 27 | { 28 | if (node.type == estraverse.Syntax.Identifier && 29 | node.name == "__p_REPLACEME_p__") 30 | { 31 | return callNode 32 | } 33 | return node 34 | } 35 | } 36 | return replacer 37 | } 38 | 39 | var nextid = 0 40 | var callWrapper = 41 | { 42 | enter: function(node) 43 | { 44 | if (!node.skip && node.type == estraverse.Syntax.CallExpression) 45 | { 46 | var replacer = makeWrappedCallReplacer(node) 47 | var wrapast = esprima.parse(replcode.format(nextid)).body[0].expression 48 | nextid++ 49 | 50 | // We do NOT wrap the calls to enterfn, the fn itself, or leavefn 51 | wrapast.callee.object.body.body[0].expression.skip = true 52 | node.skip = true 53 | wrapast.callee.object.body.body[2].expression.skip = true 54 | 55 | // To preserve source map information 56 | wrapast.loc = node.loc 57 | wrapast.callee.object.body.body[1].loc = node.callee.loc 58 | 59 | estraverse.replace(wrapast, replacer) 60 | 61 | // OK, now we need to extract and evaluate any & all args to this call 62 | // *before* passing them to the call. This is because if we leave it them 63 | // inline, the order of evaluation might get messed up. 64 | // For example, if we have a function call as one of the args, then this call 65 | // will see the id of the outer function call on the stack, which does not reflect 66 | // the execution structure of the original program. 67 | for (var i = 0; i < node.arguments.length; i++) 68 | { 69 | var arg = node.arguments[i] 70 | var decl = 71 | { 72 | type: "VariableDeclaration", 73 | declarations: 74 | [{ 75 | type: "VariableDeclarator", 76 | id: {type: "Identifier", name: "arg"+i}, 77 | init: arg, 78 | loc: arg.loc 79 | }], 80 | kind: "var", 81 | loc: arg.loc 82 | } 83 | node.arguments[i] = {type: "Identifier", name: "arg"+i} 84 | wrapast.callee.object.body.body.splice(i, 0, decl) 85 | } 86 | 87 | return wrapast 88 | } 89 | return node 90 | } 91 | } 92 | 93 | var preamble = "var __pr = null\ntry {\n\t__pr = require('probabilistic/index')\n} catch (e) {\n\t__pr = require('./probabilistic/index')\n}\n__pr.openModule(__pr);\n" 94 | 95 | /* 96 | Transform a string of code by the above two transformations 97 | */ 98 | function probTransform(codeString) 99 | { 100 | var ast = esprima.parse(codeString) 101 | estraverse.replace(ast, callWrapper) 102 | //+ "__pr.setmaxid(" + nextid + ");\n" 103 | return preamble + escodegen.generate(ast) 104 | } 105 | 106 | /* 107 | Same as above, but takes an AST instead of a code string 108 | */ 109 | function probTransformAST(ast) 110 | { 111 | estraverse.replace(ast, callWrapper) 112 | ast["body"].unshift(esprima.parse(preamble)) 113 | return ast 114 | } 115 | 116 | 117 | module.exports = 118 | { 119 | probTransformAST: probTransformAST, 120 | probTransform: probTransform 121 | } 122 | 123 | "var x = foo(1, bar(), 3)" -------------------------------------------------------------------------------- /probabilistic/util.js: -------------------------------------------------------------------------------- 1 | 2 | function openModule(mod, prefix) 3 | { 4 | // '.' in scheme tranforms to '_46' in js 5 | prefix = typeof prefix === 'undefined' ? "" : prefix + "_46"; 6 | for (var prop in mod) 7 | { 8 | if (mod.hasOwnProperty(prop)) 9 | { 10 | global[prefix+prop] = mod[prop] 11 | } 12 | } 13 | } 14 | 15 | function arrayEquals(a1, a2) 16 | { 17 | var n = a1.length 18 | if (n !== a2.length) 19 | return false 20 | for (var i = 0; i < n; i++) 21 | { 22 | if (a1[i] !== a2[i]) 23 | return false 24 | } 25 | return true 26 | } 27 | 28 | function randomChoice(arr) 29 | { 30 | return arr[Math.floor(Math.random()*arr.length)] 31 | } 32 | 33 | function discreteChoice(theta) 34 | { 35 | var k = theta.length 36 | var thetasum = 0 37 | for (var i = 0; i < k; i++) {thetasum += theta[i]} 38 | var x = Math.random() * thetasum 39 | var probAccum = 0 40 | for(var i=0; i= x) {return i} //FIXME: if x=0 returns i=0, but this isn't right if theta[0]==0... 43 | } 44 | return k 45 | } 46 | 47 | function keys(obj) 48 | { 49 | var a = [] 50 | var i = 0 51 | for (var prop in obj) 52 | { 53 | a[i] = prop 54 | i++ 55 | } 56 | return a 57 | } 58 | 59 | module.exports = 60 | { 61 | openModule: openModule, 62 | arrayEquals: arrayEquals, 63 | randomChoice: randomChoice, 64 | discreteChoice: discreteChoice, 65 | keys: keys 66 | } 67 | -------------------------------------------------------------------------------- /sandbox.js: -------------------------------------------------------------------------------- 1 | // var trace = require('./probabilistic/trace') 2 | // var util = require('./probabilistic/util') 3 | // util.openModule(trace) 4 | 5 | // function foo() 6 | // { 7 | // //console.log(arguments.callee.caller.caller); 8 | // //console.log(arguments.callee); 9 | // //console.log(trace.getStack(0, 1)[0].getFunction().name) 10 | // // var frameobj = trace.getStack(0, 1)[0]; var frameobj2 = trace.derp() 11 | // // console.log(frameobj.getFileName()) 12 | // // console.log(frameobj.getLineNumber()) 13 | // // console.log(frameobj.getColumnNumber()) 14 | // // console.log(frameobj.pos) 15 | // // console.log(frameobj2.pos) 16 | // } 17 | // foo = prob(foo) 18 | // foo = prob(foo) 19 | // console.log(foo.__probabilistic_lexical_id) 20 | 21 | // function foo() 22 | // { 23 | // function bar() 24 | // { 25 | // function baz() 26 | // { 27 | // var s = getStack(1, 1) 28 | // console.log(s.length) 29 | // console.log(s[0].getFunction().name) 30 | // } 31 | // baz() 32 | // } 33 | // bar() 34 | // } 35 | // foo() 36 | 37 | 38 | ///////////////////////////////////////////////////// 39 | 40 | // /* 41 | // Testing if V8 interns the source code string for functions 42 | // */ 43 | 44 | // var foo = function() 45 | // { 46 | // console.log('derp') 47 | // return 42 48 | // } 49 | 50 | // var bar = function() 51 | // { 52 | // console.log('derp') 53 | // return 42 54 | // } 55 | 56 | // var str1 = "Hey there" 57 | // var str2 = "Hey there" 58 | 59 | // var str3 = "Hey" + "there" 60 | // var str4 = "Hey" + "there" 61 | 62 | // // Function object comparison 63 | // var taccum = 0 64 | // for (var i = 0; i < 1000000; i++) 65 | // { 66 | // var t = new Date() 67 | // var eq = (foo == bar) 68 | // var t2 = new Date() 69 | // taccum += (t2.getTime() - t.getTime()) 70 | // } 71 | // console.log("Time for function object comparison: " + taccum) 72 | 73 | // // String literal comparison 74 | // var taccum = 0 75 | // for (var i = 0; i < 1000000; i++) 76 | // { 77 | // var t = new Date() 78 | // var eq = (str1 == str2) 79 | // var t2 = new Date() 80 | // taccum += (t2.getTime() - t.getTime()) 81 | // } 82 | // console.log("Time for string literal comparison: " + taccum) 83 | 84 | // // String nonliteral comparison 85 | // var taccum = 0 86 | // for (var i = 0; i < 1000000; i++) 87 | // { 88 | // var t = new Date() 89 | // var eq = (str3 == str4) 90 | // var t2 = new Date() 91 | // taccum += (t2.getTime() - t.getTime()) 92 | // } 93 | // console.log("Time for string nonliteral comparison: " + taccum) 94 | 95 | // // Function string comparison 96 | // var taccum = 0 97 | // for (var i = 0; i < 1000000; i++) 98 | // { 99 | // var t = new Date() 100 | // var eq = (foo.toString() == bar.toString()) 101 | // var t2 = new Date() 102 | // taccum += (t2.getTime() - t.getTime()) 103 | // } 104 | // console.log("Time for function string comparison: " + taccum) 105 | 106 | 107 | ///////////////////////////////////////////////////// 108 | 109 | 110 | // Function.prototype.__defineGetter__("__testThingy", function() 111 | // { 112 | // throw new Error("error in " + this.name + "!") 113 | // }) 114 | 115 | // function foo() 116 | // { 117 | // return 42 118 | // } 119 | 120 | // foo.__testThingy 121 | 122 | 123 | ///////////////////////////////////////////////////// 124 | 125 | // var foo = prob(function foo() 126 | // { 127 | // var tr = trace.getGlobalTrace() 128 | // console.log(tr.currentName(0)) 129 | // var bar = prob(function bar() 130 | // { 131 | // console.log(tr.currentName(0)) 132 | // var baz = prob(function baz() 133 | // { 134 | // console.log(tr.currentName(0)) 135 | // }) 136 | // baz() 137 | // }) 138 | // bar() 139 | // }) 140 | 141 | // var tr = trace.newTrace(foo) 142 | // //tr.traceUpdate() 143 | 144 | 145 | ///////////////////////////////////////////////////// 146 | 147 | // var erp = require("./probabilistic/erp") 148 | // for (var i = 0; i < 20; i++) 149 | // console.log(erp.flip()) 150 | 151 | ///////////////////////////////////////////////////// 152 | 153 | // function foo() 154 | // { 155 | // console.log(trace.getStack(8, 1)[0]) 156 | // } 157 | // foo() 158 | 159 | ///////////////////////////////////////////////////// 160 | 161 | // var pr = null 162 | // try 163 | // { 164 | // pr = require("probabilistic") 165 | // } catch (e) 166 | // { 167 | // pr = require("./probabilistic") 168 | // } 169 | 170 | // console.log(pr) 171 | 172 | ///////////////////////////////////////////////////// 173 | 174 | // var esprima = require("esprima") 175 | // var code = "(function() { return 5; })()" 176 | // // var code = "foo()" 177 | // var ast = esprima.parse(code) 178 | // console.log(ast.body[0].expression) 179 | 180 | ///////////////////////////////////////////////////// 181 | 182 | // // Add a string format method 183 | // if (!String.prototype.format) { 184 | // String.prototype.format = function() { 185 | // var args = arguments; 186 | // return this.replace(/{(\d+)}/g, function(match, number) { 187 | // return typeof args[number] != 'undefined' 188 | // ? args[number] 189 | // : match 190 | // ; 191 | // }); 192 | // }; 193 | // } 194 | 195 | // var esprima = require("esprima") 196 | // var estraverse = require("escodegen/node_modules/estraverse") 197 | // var escodegen = require("escodegen") 198 | 199 | // var code = "var y = foo(1.0, 42); var z = bar(2.0, 99);" 200 | // var replcode = "(function() {s.push({0}); var x = __p_REPLACEME_p__; s.pop(); return x; })()" 201 | 202 | // function makeWrappedCallReplacer(callNode) 203 | // { 204 | // var replacer = 205 | // { 206 | // enter: function(node) 207 | // { 208 | // if (node.type == estraverse.Syntax.Identifier && 209 | // node.name == "__p_REPLACEME_p__") 210 | // { 211 | // return callNode 212 | // } 213 | // return node 214 | // } 215 | // } 216 | // return replacer 217 | // } 218 | 219 | // var nextid = 0 220 | // var callWrapper = 221 | // { 222 | // enter: function(node) 223 | // { 224 | // if (node.skip) return estraverse.VisitorOption.Skip; 225 | // if (node.type == estraverse.Syntax.CallExpression) 226 | // { 227 | // var replacer = makeWrappedCallReplacer(node) 228 | // var wrapast = esprima.parse(replcode.format(nextid)).body[0].expression 229 | // nextid++ 230 | // wrapast.callee.skip = true 231 | // estraverse.replace(wrapast, replacer) 232 | // return wrapast 233 | // } 234 | // return node 235 | // } 236 | // } 237 | 238 | // var ast = esprima.parse(code) 239 | // estraverse.replace(ast, callWrapper) 240 | // console.log(escodegen.generate(ast)) 241 | 242 | ///////////////////////////////////////////////////// 243 | 244 | console.log("hi") 245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /test.pjs: -------------------------------------------------------------------------------- 1 | var samples = 150 2 | var lag = 20 3 | var runs = 5 4 | var errorTolerance = 0.07 5 | 6 | var failed=[] 7 | 8 | function test(name, estimates, trueExpectation, tolerance) 9 | { 10 | tolerance = (tolerance === undefined ? errorTolerance : tolerance) 11 | console.log("test: " + name + "...") 12 | var errors = estimates.map(function(est) { return Math.abs(est - trueExpectation) }) 13 | var meanAbsError = mean(errors) 14 | if (meanAbsError > tolerance) { 15 | failed.push(name) 16 | console.log(" failed! True mean: " + trueExpectation + " | Test mean: " + mean(estimates)) 17 | } 18 | else console.log(" passed.") 19 | } 20 | 21 | function mhtest(name, computation, trueExpectation, tolerance) 22 | { 23 | tolerance = (tolerance === undefined ? errorTolerance : tolerance) 24 | test(name, repeat(runs, function() { return expectation(computation, traceMH, samples, lag) }), trueExpectation, tolerance) 25 | //test(name, repeat(runs, function() { return expectation(computation, LARJMH, samples, 0, undefined, lag) }), trueExpectation, tolerance) 26 | } 27 | 28 | function larjtest(name, computation, trueExpectation, tolerance) 29 | { 30 | tolerance = (tolerance === undefined ? errorTolerance : tolerance) 31 | test(name, repeat(runs, function() { return expectation(computation, LARJMH, samples, 10, undefined, lag) }), trueExpectation, tolerance) 32 | } 33 | 34 | function eqtest(name, estvalues, truevalues, tolerance) 35 | { 36 | tolerance = (tolerance === undefined ? errorTolerance : tolerance) 37 | console.log("test: " + name + "...") 38 | if (estvalues.length !== truevalues.length) throw new Error("lengths must be equal!") 39 | for (var i = 0; i < estvalues.length; i++) 40 | { 41 | var estv = estvalues[i] 42 | var truev = truevalues[i] 43 | if (Math.abs(estv - truev) > tolerance) 44 | { 45 | failed.push(name) 46 | console.log(" failed! True value: " + truev + " | Test value: " + estv) 47 | return 48 | } 49 | } 50 | console.log(" passed.") 51 | } 52 | 53 | /////////////////////////////////////////////////////////////////////////////// 54 | 55 | var d1 = new Date() 56 | 57 | console.log("starting tests...") 58 | 59 | /* 60 | ERP Tests 61 | */ 62 | 63 | test( 64 | "flip sample", 65 | repeat(runs, function() { return mean(repeat(samples, function() { return flip(0.7) }))}), 66 | 0.7) 67 | 68 | mhtest( 69 | "flip query", 70 | function() { return flip(0.7, false) }, 71 | 0.7) 72 | 73 | test( 74 | "uniform sample", 75 | repeat(runs, function() { return mean(repeat(samples, function() { return uniform(0.1, 0.4) }))}), 76 | 0.5*(.1+.4)) 77 | 78 | mhtest( 79 | "uniform query", 80 | function() { return uniform(0.1, 0.4, false) }, 81 | 0.5*(.1+.4)) 82 | 83 | test( 84 | "multinomial sample", 85 | repeat(runs, function() { return mean(repeat(samples, function() { return multinomialDraw([.2, .3, .4], [.2, .6, .2]) }))}), 86 | 0.2*.2 + 0.6*.3 + 0.2*.4) 87 | 88 | mhtest( 89 | "multinomial query", 90 | function() { return multinomialDraw([.2, .3, .4], [.2, .6, .2], false) }, 91 | 0.2*.2 + 0.6*.3 + 0.2*.4) 92 | 93 | eqtest( 94 | "multinomial lp", 95 | [ 96 | multinomial_logprob(0, [.2, .6, .2]), 97 | multinomial_logprob(1, [.2, .6, .2]), 98 | multinomial_logprob(2, [.2, .6, .2]) 99 | ], 100 | [Math.log(0.2), Math.log(0.6), Math.log(0.2)]) 101 | 102 | test( 103 | "gaussian sample", 104 | repeat(runs, function() { return mean(repeat(samples, function() { return gaussian(0.1, 0.5) }))}), 105 | 0.1) 106 | 107 | mhtest( 108 | "gaussian query", 109 | function() { return gaussian(0.1, 0.5, false) }, 110 | 0.1) 111 | 112 | eqtest( 113 | "gaussian lp", 114 | [ 115 | gaussian_logprob(0, 0.1, 0.5), 116 | gaussian_logprob(0.25, 0.1, 0.5), 117 | gaussian_logprob(0.6, 0.1, 0.5) 118 | ], 119 | [-0.2457913526447274, -0.27079135264472737, -0.7257913526447274]) 120 | 121 | test( 122 | "gamma sample", 123 | repeat(runs, function() { return mean(repeat(samples, function() { return gamma(2, 2)/10 }))}), 124 | 0.4) 125 | 126 | mhtest( 127 | "gamma query", 128 | function() { return gamma(2, 2, false)/10 }, 129 | 0.4) 130 | 131 | eqtest( 132 | "gamma lp", 133 | [ 134 | gamma_logprob(1, 2, 2), 135 | gamma_logprob(4, 2, 2), 136 | gamma_logprob(8, 2, 2) 137 | ], 138 | [-1.8862944092546166, -2.000000048134726, -3.306852867574781]) 139 | 140 | test( 141 | "beta sample", 142 | repeat(runs, function() { return mean(repeat(samples, function() { return beta(2, 5) }))}), 143 | 2.0/(2+5)) 144 | 145 | mhtest( 146 | "beta query", 147 | function() { return beta(2, 5, false) }, 148 | 2.0/(2+5)) 149 | 150 | eqtest( 151 | "beta lp", 152 | [ 153 | beta_logprob(.1, 2, 5), 154 | beta_logprob(.2, 2, 5), 155 | beta_logprob(.6, 2, 5) 156 | ], 157 | [0.677170196389683, 0.899185234324094, -0.7747911992475776]) 158 | 159 | test( 160 | "binomial sample", 161 | repeat(runs, function() { return mean(repeat(samples, function() { return binomial(.5, 40)/40 }))}), 162 | 0.5) 163 | 164 | mhtest( 165 | "binomial query", 166 | function() { return binomial(.5, 40, false)/40 }, 167 | 0.5) 168 | 169 | eqtest( 170 | "binomial lp", 171 | [ 172 | binomial_logprob(15, .5, 40), 173 | binomial_logprob(20, .5, 40), 174 | binomial_logprob(30, .5, 40) 175 | ], 176 | [-3.3234338674089985, -2.0722579911387817, -7.2840211276953575]) 177 | 178 | test( 179 | "poisson sample", 180 | repeat(runs, function() { return mean(repeat(samples, function() { return poisson(4)/10 }))}), 181 | 0.4) 182 | 183 | mhtest( 184 | "poisson query", 185 | function() { return poisson(4, false)/10 }, 186 | 0.4) 187 | 188 | eqtest( 189 | "poisson lp", 190 | [ 191 | poisson_logprob(2, 4), 192 | poisson_logprob(5, 4), 193 | poisson_logprob(7, 4) 194 | ], 195 | [-1.9205584583201643, -1.8560199371825927, -2.821100833226181]) 196 | 197 | 198 | /* 199 | Tests adapted from Church 200 | */ 201 | 202 | mhtest( 203 | "setting a flip", 204 | function() 205 | { 206 | var a = 1/1000 207 | condition(flip(a, false)) 208 | return a 209 | }, 210 | 1/1000, 211 | 0.000000000000001) 212 | 213 | mhtest( 214 | "and conditioned on or", 215 | function() 216 | { 217 | var a = flip(0.5, false) 218 | var b = flip(0.5, false) 219 | condition(a || b) 220 | return (a && b) 221 | }, 222 | 1/3) 223 | 224 | mhtest( 225 | "and conditioned on or, biased flip", 226 | function() 227 | { 228 | var a = flip(0.3, false) 229 | var b = flip(0.3, false) 230 | condition(a || b) 231 | return (a && b) 232 | }, 233 | (0.3*0.3) / (0.3*0.3 + 0.7*0.3 + 0.3*0.7)) 234 | 235 | mhtest( 236 | "contitioned flip", 237 | function() 238 | { 239 | var bitflip = function(fidelity, x) 240 | { 241 | return flip(x ? fidelity : 1-fidelity, false) 242 | } 243 | var hyp = flip(0.7, false) 244 | condition(bitflip(0.8, hyp)) 245 | return hyp 246 | }, 247 | (0.7*0.8) / (0.7*0.8 + 0.3*0.2)) 248 | 249 | mhtest( 250 | "random 'if' with random branches, unconditioned", 251 | function() 252 | { 253 | if (flip(0.7)) 254 | return flip(0.2, false) 255 | else 256 | return flip(0.8, false) 257 | }, 258 | 0.7*0.2 + 0.3*0.8) 259 | 260 | mhtest( 261 | "flip with random weight, unconditioned", 262 | function() 263 | { 264 | return flip(flip(0.7, false) ? 0.2 : 0.8, false) 265 | }, 266 | 0.7*0.2 + 0.3*0.8) 267 | 268 | mhtest( 269 | "random procedure application, unconditioned", 270 | function() 271 | { 272 | var proc = flip(0.7) ? 273 | function (x) { return flip(0.2, false) } : 274 | function (x) { return flip(0.8, false) } 275 | return proc(1) 276 | }, 277 | 0.7*0.2 + 0.3*0.8) 278 | 279 | mhtest( 280 | "conditioned multinomial", 281 | function() 282 | { 283 | var hyp = multinomialDraw(['b', 'c', 'd'], [0.1, 0.6, 0.3], false) 284 | var observe = function (x) 285 | { 286 | if (flip(0.8, false)) 287 | return x 288 | else 289 | return 'b' 290 | } 291 | condition(observe(hyp) == 'b') 292 | return (hyp == 'b') 293 | }, 294 | 0.357) 295 | 296 | mhtest( 297 | "recursive stochastic fn, unconditioned (tail recursive)", 298 | function() 299 | { 300 | var powerLaw = function (p, x) 301 | { 302 | if (flip(p, true)) 303 | return x 304 | else 305 | return powerLaw(p, x+1) 306 | } 307 | var a = powerLaw(0.3, 1) 308 | return a < 5 309 | }, 310 | 0.7599) 311 | 312 | mhtest( 313 | "recursive stochastic fn, unconditioned", 314 | function() 315 | { 316 | var powerLaw = function (p, x) 317 | { 318 | if (flip(p, true)) 319 | return x 320 | else 321 | return 0 + powerLaw(p, x+1) 322 | } 323 | var a = powerLaw(0.3, 1) 324 | return a < 5 325 | }, 326 | 0.7599) 327 | 328 | mhtest( 329 | "memoized flip, unconditioned", 330 | function() 331 | { 332 | var proc = mem(function (x) { return flip(0.8, false) }) 333 | var p11 = proc(1) 334 | var p21 = proc(2) 335 | var p12 = proc(1) 336 | var p22 = proc(2) 337 | return p11 && p21 && p12 && p22 338 | }, 339 | 0.64) 340 | 341 | mhtest( 342 | "memoized flip, conditioned", 343 | function() 344 | { 345 | var proc = mem(function (x) { return flip(0.2, false) }) 346 | var p11 = proc(1) 347 | var p21 = proc(2) 348 | var p22 = proc(2) 349 | var p23 = proc(2) 350 | condition(p11 || p21 || p22 || p23) 351 | return proc(1) 352 | }, 353 | 0.5555555555555555) 354 | 355 | mhtest( 356 | "bound symbol used inside memoizer, unconditioned", 357 | function() 358 | { 359 | var a = flip(0.8, false) 360 | var proc = mem(function (x) { return a }) 361 | var p11 = proc(1) 362 | var p12 = proc(1) 363 | return p11 && p12 364 | }, 365 | 0.8) 366 | 367 | mhtest( 368 | "memoized flip with random argument, unconditioned", 369 | function() 370 | { 371 | var proc = mem(function (x) { return flip(0.8, false) }) 372 | var p1 = proc(uniformDraw([1,2,3])) 373 | var p2 = proc(uniformDraw([1,2,3])) 374 | return p1 && p2 375 | }, 376 | 0.6933333333333334) 377 | 378 | mhtest( 379 | "memoized random procedure, unconditioned", 380 | function() 381 | { 382 | var proc = flip(0.7) ? 383 | function (x) { return flip(0.2, false)} : 384 | function (x) { return flip(0.8, false)} 385 | var memproc = mem(proc) 386 | var mp1 = memproc(1) 387 | var mp2 = memproc(2) 388 | return mp1 && mp2 389 | }, 390 | 0.22) 391 | 392 | mhtest( 393 | "mh-query over rejection query for conditioned flip", 394 | function() 395 | { 396 | var bitflip = function (fidelity, x) 397 | { 398 | return flip(x ? fidelity : 1-fidelity, false) 399 | } 400 | var innerQuery = function() 401 | { 402 | var a = flip(0.7, false) 403 | condition(bitflip(0.8, a)) 404 | return a 405 | } 406 | return rejectionSample(innerQuery) 407 | }, 408 | 0.903225806451613) 409 | 410 | mhtest( 411 | "trans-dimensional", 412 | function() 413 | { 414 | var a = flip(0.9, true) ? beta(1, 5, false) : 0.7 415 | var b = flip(a, false) 416 | condition(b) 417 | return a 418 | }, 419 | 0.417) 420 | 421 | larjtest( 422 | "trans-dimensional (LARJ)", 423 | function() 424 | { 425 | var a = flip(0.9, true) ? beta(1, 5, false) : 0.7 426 | var b = flip(a, false) 427 | condition(b) 428 | return a 429 | }, 430 | 0.417) 431 | 432 | mhtest( 433 | "memoized flip in if branch (create/destroy memprocs), unconditioned", 434 | function() 435 | { 436 | var a = flip() ? mem(flip) : mem(flip) 437 | var b = a() 438 | return b 439 | }, 440 | 0.5) 441 | 442 | 443 | test( 444 | "marginalized procedure", 445 | repeat(runs, 446 | function() { 447 | var foo = marginalize(function foo(x) {return flip() + flip(x)}, traceMH, 500) 448 | var computation = function() {return flip() + foo(0.1) + foo(0.6) + foo(0.1)} 449 | return expectation(computation, traceMH, samples, lag) 450 | }), 451 | 2.8000000000000003, 452 | 0.1) 453 | 454 | //mhtest("nested query", 455 | // function() { 456 | // ... 457 | // }, 458 | // ?) 459 | 460 | /* 461 | Tests for things specific to new implementation 462 | */ 463 | 464 | 465 | mhtest( 466 | "native loop", 467 | function() 468 | { 469 | var accum = 0 470 | for (var i = 0; i < 4; i++) 471 | accum += flip(0.5, false) 472 | return accum/4 473 | }, 474 | 0.5) 475 | 476 | mhtest( 477 | "directly conditioning variable values", 478 | function() 479 | { 480 | var accum = 0 481 | for (var i = 0; i < 10; i++) 482 | { 483 | if (i < 5) 484 | accum += flip(0.5, false, 1) 485 | else 486 | accum += flip(0.5, false) 487 | } 488 | return accum / 10 489 | }, 490 | 0.75) 491 | 492 | 493 | var d2 = new Date() 494 | console.log("time: " + (d2.getTime() - d1.getTime()) / 1000) 495 | 496 | if (failed.length == 0) { 497 | console.log("tests done!") 498 | } else { 499 | console.log("some tests failed: " + failed + "!!!") 500 | } 501 | -------------------------------------------------------------------------------- /webppl/codemirror/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | white-space: nowrap; 32 | } 33 | .CodeMirror-linenumbers {} 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | } 40 | 41 | /* CURSOR */ 42 | 43 | .CodeMirror div.CodeMirror-cursor { 44 | border-left: 1px solid black; 45 | z-index: 3; 46 | } 47 | /* Shown when moving in bi-directional text */ 48 | .CodeMirror div.CodeMirror-secondarycursor { 49 | border-left: 1px solid silver; 50 | } 51 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 52 | width: auto; 53 | border: 0; 54 | background: #7e7; 55 | z-index: 1; 56 | } 57 | /* Can style cursor different in overwrite (non-insert) mode */ 58 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 59 | 60 | .cm-tab { display: inline-block; } 61 | 62 | /* DEFAULT THEME */ 63 | 64 | .cm-s-default .cm-keyword {color: #708;} 65 | .cm-s-default .cm-atom {color: #219;} 66 | .cm-s-default .cm-number {color: #164;} 67 | .cm-s-default .cm-def {color: #00f;} 68 | .cm-s-default .cm-variable {color: black;} 69 | .cm-s-default .cm-variable-2 {color: #05a;} 70 | .cm-s-default .cm-variable-3 {color: #085;} 71 | .cm-s-default .cm-property {color: black;} 72 | .cm-s-default .cm-operator {color: black;} 73 | .cm-s-default .cm-comment {color: #a50;} 74 | .cm-s-default .cm-string {color: #a11;} 75 | .cm-s-default .cm-string-2 {color: #f50;} 76 | .cm-s-default .cm-meta {color: #555;} 77 | .cm-s-default .cm-error {color: #f00;} 78 | .cm-s-default .cm-qualifier {color: #555;} 79 | .cm-s-default .cm-builtin {color: #30a;} 80 | .cm-s-default .cm-bracket {color: #997;} 81 | .cm-s-default .cm-tag {color: #170;} 82 | .cm-s-default .cm-attribute {color: #00c;} 83 | .cm-s-default .cm-header {color: blue;} 84 | .cm-s-default .cm-quote {color: #090;} 85 | .cm-s-default .cm-hr {color: #999;} 86 | .cm-s-default .cm-link {color: #00c;} 87 | 88 | .cm-negative {color: #d44;} 89 | .cm-positive {color: #292;} 90 | .cm-header, .cm-strong {font-weight: bold;} 91 | .cm-em {font-style: italic;} 92 | .cm-link {text-decoration: underline;} 93 | 94 | .cm-invalidchar {color: #f00;} 95 | 96 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 97 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 98 | 99 | /* STOP */ 100 | 101 | /* The rest of this file contains styles related to the mechanics of 102 | the editor. You probably shouldn't touch them. */ 103 | 104 | .CodeMirror { 105 | line-height: 1; 106 | position: relative; 107 | overflow: hidden; 108 | background: white; 109 | color: black; 110 | } 111 | 112 | .CodeMirror-scroll { 113 | /* 30px is the magic margin used to hide the element's real scrollbars */ 114 | /* See overflow: hidden in .CodeMirror */ 115 | margin-bottom: -30px; margin-right: -30px; 116 | padding-bottom: 30px; padding-right: 30px; 117 | height: 100%; 118 | outline: none; /* Prevent dragging from highlighting the element */ 119 | position: relative; 120 | } 121 | .CodeMirror-sizer { 122 | position: relative; 123 | } 124 | 125 | /* The fake, visible scrollbars. Used to force redraw during scrolling 126 | before actuall scrolling happens, thus preventing shaking and 127 | flickering artifacts. */ 128 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { 129 | position: absolute; 130 | z-index: 6; 131 | display: none; 132 | } 133 | .CodeMirror-vscrollbar { 134 | right: 0; top: 0; 135 | overflow-x: hidden; 136 | overflow-y: scroll; 137 | } 138 | .CodeMirror-hscrollbar { 139 | bottom: 0; left: 0; 140 | overflow-y: hidden; 141 | overflow-x: scroll; 142 | } 143 | .CodeMirror-scrollbar-filler { 144 | right: 0; bottom: 0; 145 | z-index: 6; 146 | } 147 | 148 | .CodeMirror-gutters { 149 | position: absolute; left: 0; top: 0; 150 | height: 100%; 151 | padding-bottom: 30px; 152 | z-index: 3; 153 | } 154 | .CodeMirror-gutter { 155 | white-space: normal; 156 | height: 100%; 157 | padding-bottom: 30px; 158 | margin-bottom: -32px; 159 | display: inline-block; 160 | /* Hack to make IE7 behave */ 161 | *zoom:1; 162 | *display:inline; 163 | } 164 | .CodeMirror-gutter-elt { 165 | position: absolute; 166 | cursor: default; 167 | z-index: 4; 168 | } 169 | 170 | .CodeMirror-lines { 171 | cursor: text; 172 | } 173 | .CodeMirror pre { 174 | /* Reset some styles that the rest of the page might have set */ 175 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 176 | border-width: 0; 177 | background: transparent; 178 | font-family: inherit; 179 | font-size: inherit; 180 | margin: 0; 181 | white-space: pre; 182 | word-wrap: normal; 183 | line-height: inherit; 184 | color: inherit; 185 | z-index: 2; 186 | position: relative; 187 | overflow: visible; 188 | } 189 | .CodeMirror-wrap pre { 190 | word-wrap: break-word; 191 | white-space: pre-wrap; 192 | word-break: normal; 193 | } 194 | .CodeMirror-linebackground { 195 | position: absolute; 196 | left: 0; right: 0; top: 0; bottom: 0; 197 | z-index: 0; 198 | } 199 | 200 | .CodeMirror-linewidget { 201 | position: relative; 202 | z-index: 2; 203 | overflow: auto; 204 | } 205 | 206 | .CodeMirror-widget { 207 | display: inline-block; 208 | } 209 | 210 | .CodeMirror-wrap .CodeMirror-scroll { 211 | overflow-x: hidden; 212 | } 213 | 214 | .CodeMirror-measure { 215 | position: absolute; 216 | width: 100%; height: 0px; 217 | overflow: hidden; 218 | visibility: hidden; 219 | } 220 | .CodeMirror-measure pre { position: static; } 221 | 222 | .CodeMirror div.CodeMirror-cursor { 223 | position: absolute; 224 | visibility: hidden; 225 | border-right: none; 226 | width: 0; 227 | } 228 | .CodeMirror-focused div.CodeMirror-cursor { 229 | visibility: visible; 230 | } 231 | 232 | .CodeMirror-selected { background: #d9d9d9; } 233 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 234 | 235 | .cm-searching { 236 | background: #ffa; 237 | background: rgba(255, 255, 0, .4); 238 | } 239 | 240 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 241 | .CodeMirror span { *vertical-align: text-bottom; } 242 | 243 | @media print { 244 | /* Hide the cursor when printing */ 245 | .CodeMirror div.CodeMirror-cursor { 246 | visibility: hidden; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /webppl/codemirror/continuecomment.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var modes = ["clike", "css", "javascript"]; 3 | for (var i = 0; i < modes.length; ++i) 4 | CodeMirror.extendMode(modes[i], {blockCommentStart: "/*", 5 | blockCommentEnd: "*/", 6 | blockCommentContinue: " * "}); 7 | 8 | function continueComment(cm) { 9 | var pos = cm.getCursor(), token = cm.getTokenAt(pos); 10 | var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode; 11 | var space; 12 | 13 | if (token.type == "comment" && mode.blockCommentStart) { 14 | var end = token.string.indexOf(mode.blockCommentEnd); 15 | var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found; 16 | if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) { 17 | // Comment ended, don't continue it 18 | } else if (token.string.indexOf(mode.blockCommentStart) == 0) { 19 | space = full.slice(0, token.start); 20 | if (!/^\s*$/.test(space)) { 21 | space = ""; 22 | for (var i = 0; i < token.start; ++i) space += " "; 23 | } 24 | } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 && 25 | found + mode.blockCommentContinue.length > token.start && 26 | /^\s*$/.test(full.slice(0, found))) { 27 | space = full.slice(0, found); 28 | } 29 | } 30 | 31 | if (space != null) 32 | cm.replaceSelection("\n" + space + mode.blockCommentContinue, "end"); 33 | else 34 | return CodeMirror.Pass; 35 | } 36 | 37 | CodeMirror.defineOption("continueComments", null, function(cm, val, prev) { 38 | if (prev && prev != CodeMirror.Init) 39 | cm.removeKeyMap("continueComment"); 40 | var map = {name: "continueComment"}; 41 | map[typeof val == "string" ? val : "Enter"] = continueComment; 42 | cm.addKeyMap(map); 43 | }); 44 | })(); 45 | -------------------------------------------------------------------------------- /webppl/codemirror/docs.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Droid Sans, Arial, sans-serif; 3 | line-height: 1.5; 4 | max-width: 64.3em; 5 | margin: 3em auto; 6 | padding: 0 1em; 7 | } 8 | 9 | h1 { 10 | letter-spacing: -3px; 11 | font-size: 3.23em; 12 | font-weight: bold; 13 | margin: 0; 14 | } 15 | 16 | h2 { 17 | font-size: 1.23em; 18 | font-weight: bold; 19 | margin: .5em 0; 20 | letter-spacing: -1px; 21 | } 22 | 23 | h3 { 24 | font-size: 1.1em; 25 | font-weight: bold; 26 | margin: .4em 0; 27 | } 28 | 29 | pre { 30 | background-color: #eee; 31 | -moz-border-radius: 6px; 32 | -webkit-border-radius: 6px; 33 | border-radius: 6px; 34 | padding: 1em; 35 | } 36 | 37 | pre.code { 38 | margin: 0 1em; 39 | } 40 | 41 | .grey { 42 | background-color: #eee; 43 | border-radius: 6px; 44 | margin-bottom: 1.65em; 45 | margin-top: 0.825em; 46 | padding: 0.825em 1.65em; 47 | position: relative; 48 | } 49 | 50 | img.logo { 51 | position: absolute; 52 | right: -1em; 53 | bottom: 4px; 54 | max-width: 23.6875em; /* Scale image down with text to prevent clipping */ 55 | } 56 | 57 | .grey > pre { 58 | background:none; 59 | border-radius:0; 60 | padding:0; 61 | margin:0; 62 | font-size:2.2em; 63 | line-height:1.2em; 64 | } 65 | 66 | a:link, a:visited, .quasilink { 67 | color: #df0019; 68 | cursor: pointer; 69 | text-decoration: none; 70 | } 71 | 72 | a:hover, .quasilink:hover { 73 | color: #800004; 74 | } 75 | 76 | h1 a:link, h1 a:visited, h1 a:hover { 77 | color: black; 78 | } 79 | 80 | ul { 81 | margin: 0; 82 | padding-left: 1.2em; 83 | } 84 | 85 | a.download { 86 | color: white; 87 | background-color: #df0019; 88 | width: 100%; 89 | display: block; 90 | text-align: center; 91 | font-size: 1.23em; 92 | font-weight: bold; 93 | text-decoration: none; 94 | -moz-border-radius: 6px; 95 | -webkit-border-radius: 6px; 96 | border-radius: 6px; 97 | padding: .5em 0; 98 | margin-bottom: 1em; 99 | } 100 | 101 | a.download:hover { 102 | background-color: #bb0010; 103 | } 104 | 105 | .rel { 106 | margin-bottom: 0; 107 | } 108 | 109 | .rel-note { 110 | color: #777; 111 | font-size: .9em; 112 | margin-top: .1em; 113 | } 114 | 115 | .logo-braces { 116 | color: #df0019; 117 | position: relative; 118 | top: -4px; 119 | } 120 | 121 | .blk { 122 | float: left; 123 | } 124 | 125 | .left { 126 | margin-right: 20.68em; 127 | max-width: 37em; 128 | padding-right: 6.53em; 129 | padding-bottom: 1em; 130 | } 131 | 132 | .left1 { 133 | width: 15.24em; 134 | padding-right: 6.45em; 135 | } 136 | 137 | .left2 { 138 | max-width: 15.24em; 139 | } 140 | 141 | .right { 142 | width: 20.68em; 143 | margin-left: -20.68em; 144 | } 145 | 146 | .leftbig { 147 | width: 42.44em; 148 | padding-right: 6.53em; 149 | } 150 | 151 | .rightsmall { 152 | width: 15.24em; 153 | } 154 | 155 | .clear:after { 156 | visibility: hidden; 157 | display: block; 158 | font-size: 0; 159 | content: " "; 160 | clear: both; 161 | height: 0; 162 | } 163 | .clear { display: inline-block; } 164 | /* start commented backslash hack \*/ 165 | * html .clear { height: 1%; } 166 | .clear { display: block; } 167 | /* close commented backslash hack */ 168 | -------------------------------------------------------------------------------- /webppl/codemirror/javascript.js: -------------------------------------------------------------------------------- 1 | // TODO actually recognize syntax of TypeScript constructs 2 | 3 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 4 | var indentUnit = config.indentUnit; 5 | var jsonMode = parserConfig.json; 6 | var isTS = parserConfig.typescript; 7 | 8 | // Tokenizer 9 | 10 | var keywords = function(){ 11 | function kw(type) {return {type: type, style: "keyword"};} 12 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 13 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 14 | 15 | var jsKeywords = { 16 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 17 | "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, 18 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 19 | "function": kw("function"), "catch": kw("catch"), 20 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 21 | "in": operator, "typeof": operator, "instanceof": operator, 22 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 23 | "this": kw("this") 24 | }; 25 | 26 | // Extend the 'normal' keywords with the TypeScript language extensions 27 | if (isTS) { 28 | var type = {type: "variable", style: "variable-3"}; 29 | var tsKeywords = { 30 | // object-like things 31 | "interface": kw("interface"), 32 | "class": kw("class"), 33 | "extends": kw("extends"), 34 | "constructor": kw("constructor"), 35 | 36 | // scope modifiers 37 | "public": kw("public"), 38 | "private": kw("private"), 39 | "protected": kw("protected"), 40 | "static": kw("static"), 41 | 42 | "super": kw("super"), 43 | 44 | // types 45 | "string": type, "number": type, "bool": type, "any": type 46 | }; 47 | 48 | for (var attr in tsKeywords) { 49 | jsKeywords[attr] = tsKeywords[attr]; 50 | } 51 | } 52 | 53 | return jsKeywords; 54 | }(); 55 | 56 | var isOperatorChar = /[+\-*&%=<>!?|~^]/; 57 | 58 | function chain(stream, state, f) { 59 | state.tokenize = f; 60 | return f(stream, state); 61 | } 62 | 63 | function nextUntilUnescaped(stream, end) { 64 | var escaped = false, next; 65 | while ((next = stream.next()) != null) { 66 | if (next == end && !escaped) 67 | return false; 68 | escaped = !escaped && next == "\\"; 69 | } 70 | return escaped; 71 | } 72 | 73 | // Used as scratch variables to communicate multiple values without 74 | // consing up tons of objects. 75 | var type, content; 76 | function ret(tp, style, cont) { 77 | type = tp; content = cont; 78 | return style; 79 | } 80 | 81 | function jsTokenBase(stream, state) { 82 | var ch = stream.next(); 83 | if (ch == '"' || ch == "'") 84 | return chain(stream, state, jsTokenString(ch)); 85 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 86 | return ret(ch); 87 | else if (ch == "0" && stream.eat(/x/i)) { 88 | stream.eatWhile(/[\da-f]/i); 89 | return ret("number", "number"); 90 | } 91 | else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { 92 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 93 | return ret("number", "number"); 94 | } 95 | else if (ch == "/") { 96 | if (stream.eat("*")) { 97 | return chain(stream, state, jsTokenComment); 98 | } 99 | else if (stream.eat("/")) { 100 | stream.skipToEnd(); 101 | return ret("comment", "comment"); 102 | } 103 | else if (state.lastType == "operator" || state.lastType == "keyword c" || 104 | /^[\[{}\(,;:]$/.test(state.lastType)) { 105 | nextUntilUnescaped(stream, "/"); 106 | stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla 107 | return ret("regexp", "string-2"); 108 | } 109 | else { 110 | stream.eatWhile(isOperatorChar); 111 | return ret("operator", null, stream.current()); 112 | } 113 | } 114 | else if (ch == "#") { 115 | stream.skipToEnd(); 116 | return ret("error", "error"); 117 | } 118 | else if (isOperatorChar.test(ch)) { 119 | stream.eatWhile(isOperatorChar); 120 | return ret("operator", null, stream.current()); 121 | } 122 | else { 123 | stream.eatWhile(/[\w\$_]/); 124 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 125 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) : 126 | ret("variable", "variable", word); 127 | } 128 | } 129 | 130 | function jsTokenString(quote) { 131 | return function(stream, state) { 132 | if (!nextUntilUnescaped(stream, quote)) 133 | state.tokenize = jsTokenBase; 134 | return ret("string", "string"); 135 | }; 136 | } 137 | 138 | function jsTokenComment(stream, state) { 139 | var maybeEnd = false, ch; 140 | while (ch = stream.next()) { 141 | if (ch == "/" && maybeEnd) { 142 | state.tokenize = jsTokenBase; 143 | break; 144 | } 145 | maybeEnd = (ch == "*"); 146 | } 147 | return ret("comment", "comment"); 148 | } 149 | 150 | // Parser 151 | 152 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true}; 153 | 154 | function JSLexical(indented, column, type, align, prev, info) { 155 | this.indented = indented; 156 | this.column = column; 157 | this.type = type; 158 | this.prev = prev; 159 | this.info = info; 160 | if (align != null) this.align = align; 161 | } 162 | 163 | function inScope(state, varname) { 164 | for (var v = state.localVars; v; v = v.next) 165 | if (v.name == varname) return true; 166 | } 167 | 168 | function parseJS(state, style, type, content, stream) { 169 | var cc = state.cc; 170 | // Communicate our context to the combinators. 171 | // (Less wasteful than consing up a hundred closures on every call.) 172 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; 173 | 174 | if (!state.lexical.hasOwnProperty("align")) 175 | state.lexical.align = true; 176 | 177 | while(true) { 178 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 179 | if (combinator(type, content)) { 180 | while(cc.length && cc[cc.length - 1].lex) 181 | cc.pop()(); 182 | if (cx.marked) return cx.marked; 183 | if (type == "variable" && inScope(state, content)) return "variable-2"; 184 | return style; 185 | } 186 | } 187 | } 188 | 189 | // Combinator utils 190 | 191 | var cx = {state: null, column: null, marked: null, cc: null}; 192 | function pass() { 193 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 194 | } 195 | function cont() { 196 | pass.apply(null, arguments); 197 | return true; 198 | } 199 | function register(varname) { 200 | function inList(list) { 201 | for (var v = list; v; v = v.next) 202 | if (v.name == varname) return true; 203 | return false; 204 | } 205 | var state = cx.state; 206 | if (state.context) { 207 | cx.marked = "def"; 208 | if (inList(state.localVars)) return; 209 | state.localVars = {name: varname, next: state.localVars}; 210 | } else { 211 | if (inList(state.globalVars)) return; 212 | state.globalVars = {name: varname, next: state.globalVars}; 213 | } 214 | } 215 | 216 | // Combinators 217 | 218 | var defaultVars = {name: "this", next: {name: "arguments"}}; 219 | function pushcontext() { 220 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 221 | cx.state.localVars = defaultVars; 222 | } 223 | function popcontext() { 224 | cx.state.localVars = cx.state.context.vars; 225 | cx.state.context = cx.state.context.prev; 226 | } 227 | function pushlex(type, info) { 228 | var result = function() { 229 | var state = cx.state; 230 | state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); 231 | }; 232 | result.lex = true; 233 | return result; 234 | } 235 | function poplex() { 236 | var state = cx.state; 237 | if (state.lexical.prev) { 238 | if (state.lexical.type == ")") 239 | state.indented = state.lexical.indented; 240 | state.lexical = state.lexical.prev; 241 | } 242 | } 243 | poplex.lex = true; 244 | 245 | function expect(wanted) { 246 | return function(type) { 247 | if (type == wanted) return cont(); 248 | else if (wanted == ";") return pass(); 249 | else return cont(arguments.callee); 250 | }; 251 | } 252 | 253 | function statement(type) { 254 | if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); 255 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 256 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 257 | if (type == "{") return cont(pushlex("}"), block, poplex); 258 | if (type == ";") return cont(); 259 | if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse(cx.state.indented)); 260 | if (type == "function") return cont(functiondef); 261 | if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), 262 | poplex, statement, poplex); 263 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 264 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 265 | block, poplex, poplex); 266 | if (type == "case") return cont(expression, expect(":")); 267 | if (type == "default") return cont(expect(":")); 268 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 269 | statement, poplex, popcontext); 270 | return pass(pushlex("stat"), expression, expect(";"), poplex); 271 | } 272 | function expression(type) { 273 | return expressionInner(type, maybeoperatorComma); 274 | } 275 | function expressionNoComma(type) { 276 | return expressionInner(type, maybeoperatorNoComma); 277 | } 278 | function expressionInner(type, maybeop) { 279 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 280 | if (type == "function") return cont(functiondef); 281 | if (type == "keyword c") return cont(maybeexpression); 282 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); 283 | if (type == "operator") return cont(expression); 284 | if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop); 285 | if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop); 286 | return cont(); 287 | } 288 | function maybeexpression(type) { 289 | if (type.match(/[;\}\)\],]/)) return pass(); 290 | return pass(expression); 291 | } 292 | 293 | function maybeoperatorComma(type, value) { 294 | if (type == ",") return cont(expression); 295 | return maybeoperatorNoComma(type, value, maybeoperatorComma); 296 | } 297 | function maybeoperatorNoComma(type, value, me) { 298 | if (!me) me = maybeoperatorNoComma; 299 | if (type == "operator") { 300 | if (/\+\+|--/.test(value)) return cont(me); 301 | if (value == "?") return cont(expression, expect(":"), expression); 302 | return cont(expression); 303 | } 304 | if (type == ";") return; 305 | if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me); 306 | if (type == ".") return cont(property, me); 307 | if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, me); 308 | } 309 | function maybelabel(type) { 310 | if (type == ":") return cont(poplex, statement); 311 | return pass(maybeoperatorComma, expect(";"), poplex); 312 | } 313 | function property(type) { 314 | if (type == "variable") {cx.marked = "property"; return cont();} 315 | } 316 | function objprop(type, value) { 317 | if (type == "variable") { 318 | cx.marked = "property"; 319 | if (value == "get" || value == "set") return cont(getterSetter); 320 | } else if (type == "number" || type == "string") { 321 | cx.marked = type + " property"; 322 | } 323 | if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expressionNoComma); 324 | } 325 | function getterSetter(type) { 326 | if (type == ":") return cont(expression); 327 | if (type != "variable") return cont(expect(":"), expression); 328 | cx.marked = "property"; 329 | return cont(functiondef); 330 | } 331 | function commasep(what, end) { 332 | function proceed(type) { 333 | if (type == ",") { 334 | var lex = cx.state.lexical; 335 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 336 | return cont(what, proceed); 337 | } 338 | if (type == end) return cont(); 339 | return cont(expect(end)); 340 | } 341 | return function(type) { 342 | if (type == end) return cont(); 343 | else return pass(what, proceed); 344 | }; 345 | } 346 | function block(type) { 347 | if (type == "}") return cont(); 348 | return pass(statement, block); 349 | } 350 | function maybetype(type) { 351 | if (type == ":") return cont(typedef); 352 | return pass(); 353 | } 354 | function typedef(type) { 355 | if (type == "variable"){cx.marked = "variable-3"; return cont();} 356 | return pass(); 357 | } 358 | function vardef1(type, value) { 359 | if (type == "variable") { 360 | register(value); 361 | return isTS ? cont(maybetype, vardef2) : cont(vardef2); 362 | } 363 | return pass(); 364 | } 365 | function vardef2(type, value) { 366 | if (value == "=") return cont(expressionNoComma, vardef2); 367 | if (type == ",") return cont(vardef1); 368 | } 369 | function maybeelse(indent) { 370 | return function(type, value) { 371 | if (type == "keyword b" && value == "else") { 372 | cx.state.lexical = new JSLexical(indent, 0, "form", null, cx.state.lexical); 373 | return cont(statement, poplex); 374 | } 375 | return pass(); 376 | }; 377 | } 378 | function forspec1(type) { 379 | if (type == "var") return cont(vardef1, expect(";"), forspec2); 380 | if (type == ";") return cont(forspec2); 381 | if (type == "variable") return cont(formaybein); 382 | return pass(expression, expect(";"), forspec2); 383 | } 384 | function formaybein(_type, value) { 385 | if (value == "in") return cont(expression); 386 | return cont(maybeoperatorComma, forspec2); 387 | } 388 | function forspec2(type, value) { 389 | if (type == ";") return cont(forspec3); 390 | if (value == "in") return cont(expression); 391 | return pass(expression, expect(";"), forspec3); 392 | } 393 | function forspec3(type) { 394 | if (type != ")") cont(expression); 395 | } 396 | function functiondef(type, value) { 397 | if (type == "variable") {register(value); return cont(functiondef);} 398 | if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); 399 | } 400 | function funarg(type, value) { 401 | if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();} 402 | } 403 | 404 | // Interface 405 | 406 | return { 407 | startState: function(basecolumn) { 408 | return { 409 | tokenize: jsTokenBase, 410 | lastType: null, 411 | cc: [], 412 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 413 | localVars: parserConfig.localVars, 414 | globalVars: parserConfig.globalVars, 415 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 416 | indented: 0 417 | }; 418 | }, 419 | 420 | token: function(stream, state) { 421 | if (stream.sol()) { 422 | if (!state.lexical.hasOwnProperty("align")) 423 | state.lexical.align = false; 424 | state.indented = stream.indentation(); 425 | } 426 | if (state.tokenize != jsTokenComment && stream.eatSpace()) return null; 427 | var style = state.tokenize(stream, state); 428 | if (type == "comment") return style; 429 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 430 | return parseJS(state, style, type, content, stream); 431 | }, 432 | 433 | indent: function(state, textAfter) { 434 | if (state.tokenize == jsTokenComment) return CodeMirror.Pass; 435 | if (state.tokenize != jsTokenBase) return 0; 436 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; 437 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; 438 | var type = lexical.type, closing = firstChar == type; 439 | if (parserConfig.statementIndent != null) { 440 | if (type == ")" && lexical.prev && lexical.prev.type == "stat") lexical = lexical.prev; 441 | if (lexical.type == "stat") return lexical.indented + parserConfig.statementIndent; 442 | } 443 | 444 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0); 445 | else if (type == "form" && firstChar == "{") return lexical.indented; 446 | else if (type == "form") return lexical.indented + indentUnit; 447 | else if (type == "stat") 448 | return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0); 449 | else if (lexical.info == "switch" && !closing) 450 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 451 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 452 | else return lexical.indented + (closing ? 0 : indentUnit); 453 | }, 454 | 455 | electricChars: ":{}", 456 | 457 | jsonMode: jsonMode 458 | }; 459 | }); 460 | 461 | CodeMirror.defineMIME("text/javascript", "javascript"); 462 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 463 | CodeMirror.defineMIME("application/javascript", "javascript"); 464 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 465 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 466 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); 467 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 468 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 469 | -------------------------------------------------------------------------------- /webppl/codemirror/matchbrackets.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && 3 | (document.documentMode == null || document.documentMode < 8); 4 | 5 | var Pos = CodeMirror.Pos; 6 | 7 | var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; 8 | function findMatchingBracket(cm) { 9 | var maxScanLen = cm.state._matchBrackets.maxScanLineLength || 10000; 10 | 11 | var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1; 12 | var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; 13 | if (!match) return null; 14 | var forward = match.charAt(1) == ">", d = forward ? 1 : -1; 15 | var style = cm.getTokenAt(Pos(cur.line, pos + 1)).type; 16 | 17 | var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; 18 | function scan(line, lineNo, start) { 19 | if (!line.text) return; 20 | var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1; 21 | if (line.text.length > maxScanLen) return null; 22 | var checkTokenStyles = line.text.length < 1000; 23 | if (start != null) pos = start + d; 24 | for (; pos != end; pos += d) { 25 | var ch = line.text.charAt(pos); 26 | if (re.test(ch) && (!checkTokenStyles || cm.getTokenAt(Pos(lineNo, pos + 1)).type == style)) { 27 | var match = matching[ch]; 28 | if (match.charAt(1) == ">" == forward) stack.push(ch); 29 | else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; 30 | else if (!stack.length) return {pos: pos, match: true}; 31 | } 32 | } 33 | } 34 | for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) { 35 | if (i == cur.line) found = scan(line, i, pos); 36 | else found = scan(cm.getLineHandle(i), i); 37 | if (found) break; 38 | } 39 | return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos), match: found && found.match}; 40 | } 41 | 42 | function matchBrackets(cm, autoclear) { 43 | // Disable brace matching in long lines, since it'll cause hugely slow updates 44 | var maxHighlightLen = cm.state._matchBrackets.maxHighlightLineLength || 1000; 45 | var found = findMatchingBracket(cm); 46 | if (!found || cm.getLine(found.from.line).length > maxHighlightLen || 47 | found.to && cm.getLine(found.to.line).length > maxHighlightLen) 48 | return; 49 | 50 | var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; 51 | var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style}); 52 | var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style}); 53 | // Kludge to work around the IE bug from issue #1193, where text 54 | // input stops going to the textare whever this fires. 55 | if (ie_lt8 && cm.state.focused) cm.display.input.focus(); 56 | var clear = function() { 57 | cm.operation(function() { one.clear(); two && two.clear(); }); 58 | }; 59 | if (autoclear) setTimeout(clear, 800); 60 | else return clear; 61 | } 62 | 63 | var currentlyHighlighted = null; 64 | function doMatchBrackets(cm) { 65 | cm.operation(function() { 66 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} 67 | if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false); 68 | }); 69 | } 70 | 71 | CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { 72 | if (old && old != CodeMirror.Init) 73 | cm.off("cursorActivity", doMatchBrackets); 74 | if (val) { 75 | cm.state._matchBrackets = typeof val == "object" ? val : {}; 76 | cm.on("cursorActivity", doMatchBrackets); 77 | } 78 | }); 79 | 80 | CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); 81 | CodeMirror.defineExtension("findMatchingBracket", function(){return findMatchingBracket(this);}); 82 | })(); 83 | -------------------------------------------------------------------------------- /webppl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Probabilistic Javascript in the Browser! 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

Probabilistic Javascript in the Browser!

20 | 21 |
38 | 39 | 136 | 137 | 140 | 141 |
142 |
143 |

Output:

144 |
145 | 148 |

Info:

149 |
150 |
151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /webppl/pragmaticstest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Probabilistic Javascript in the Browser! 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

Probabilistic Javascript in the Browser!

17 | 18 |
56 | 57 | 109 | 110 | 113 | 114 |
115 |
116 |

Output:

117 |
118 | 121 |
122 | 123 | 124 | 125 | --------------------------------------------------------------------------------