Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
stuhlmueller committed Sep 9, 2015
2 parents 4551d40 + 049e17d commit c1f6ef4
Show file tree
Hide file tree
Showing 65 changed files with 409 additions and 144 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ node_js:
- "0.12"
- "0.11"
- "0.10"
- "4.0.0"

install:
- npm install
Expand Down
4 changes: 2 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ var jslintSettings = {
},
wppl: {
src: [
'tests/test-data/models/*.wppl',
'tests/test-data/**/*.wppl',
'examples/*.wppl'
]
}
};
module.exports = function(grunt) {
grunt.initConfig({
nodeunit: {
all: ['tests/*.js']
all: ['tests/test-*.js']
},
jshint: {
files: [
Expand Down
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ To debug WebPPL programs running in Chrome, enable [pause on JavaScript exceptio
// 1. Install node-inspector (only need to do this once)
npm install -g node-inspector

// 2. Add "debugger;" statements to my-program.js to indicate breakpoints
// 2. Add "debugger;" statements to my-program.wppl to indicate breakpoints

// 3. Run your compiled program in debug mode (will pause automatically)
node --debug-brk webppl my-program.js
node --debug-brk webppl my-program.wppl

// 4. (In separate terminal:) Load node inspector, resume program execution in node-inspector
node-inspector
Expand Down Expand Up @@ -125,7 +125,7 @@ Packages can extend WebPPL in three ways:
You can automatically prepend WebPPL files to your code by added a `wppl` entry to `package.json`. For example:

{
"name": "my-package"
"name": "my-package",
"webppl": {
"wppl": ["myLibrary.wppl"]
}
Expand All @@ -144,11 +144,16 @@ For example, if the package `my-package` contains this file:

Then the function `myAdd` will be available in WebPPL as `myPackage.myAdd`.

If your Javascript isn't in an `index.js` file in the root of the package, you should indicate the entry point to your package by adding a `main` entry to `package.json`.
If your Javascript isn't in an `index.js` file in the root of the package, you should indicate the entry point to your package by adding a `main` entry to `package.json`. For example:

{
"name": "my-package",
"main": "src/main.js"
}

Note that packages must export functions as properties of an object. Exporting functions directly will not work as expected.

### Additional header files
#### Additional header files

Sometimes, it is useful to define external functions that are able to access WebPPL internals. Header files have access to the following:

Expand All @@ -174,7 +179,7 @@ Let's use the example of a function that makes the current address available in
2. Add a `headers` entry to `package.json`:

{
"name": "my-package"
"name": "my-package",
"webppl": {
"headers": ["addressHeader.js"]
}
Expand Down
2 changes: 1 addition & 1 deletion examples/ldaCollapsed.wppl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ var mapObject = function(fn, obj) {
},
_.pairs(obj))
);
}
};


// Model
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "webppl",
"description": "Probabilistic programming for the web",
"version": "0.1.0",
"version": "0.1.1",
"author": "webppl contributors",
"main": "src/main.js",
"repository": {
Expand Down Expand Up @@ -39,7 +39,7 @@
"through2": "^2.0.0"
},
"scripts": {
"test": "nodeunit tests"
"test": "nodeunit tests/test-*.js"
},
"bugs": {
"url": "https://github.com/probmods/webppl/issues"
Expand Down
126 changes: 89 additions & 37 deletions src/erp.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,60 @@ ERP.prototype.entropy = function() {
}
this.entropy = function() {return e};
return e;
}
};

ERP.prototype.parameterized = true;

ERP.prototype.withParameters = function(params) {
var erp = new ERP();
_.forEach(this, function(v, k) {erp[k] = v;});
var sampler = this.sample;
erp.sample = function(ps) {return sampler(params)};
var scorer = this.score;
erp.score = function(ps, val) {return scorer(params, val)};
if (this.support) {
var support = this.support;
erp.support = function(ps) {return support(params)};
}
erp.parameterized = false;
return erp;
};

// ERP serializer (allows JSON.stringify)
ERP.prototype.toJSON = function() {
if (this.parameterized || this.support === undefined) {
throw 'Cannot serialize ERP: ' + this.sample.name;
} else {
var support = this.support([]);
var probs = support.map(function(s) {return Math.exp(this.score([], s));}, this);
var erpJSON = {probs: probs, support: support};
this.toJSON = function() {return erpJSON};
return erpJSON;
}
};

ERP.prototype.print = function() {
console.log('ERP:');
var json = this.toJSON();
_.zip(json.probs, json.support)
.sort(function(a, b) { return b[0] - a[0]; })
.forEach(function(val) {console.log(' ' + util.serialize(val[1]) + ' : ' + val[0]);})
};

var serializeERP = function(erp) {
return util.serialize(erp);
};

// ERP deserializers
var deserializeERP = function(JSONString) {
var obj = util.deserialize(JSONString);
if (!obj.probs || !obj.support) {
throw 'Cannot deserialize a non-ERP JSON object: ' + JSONString;
}
return makeCategoricalERP(obj.probs,
obj.support,
_.omit(obj, 'probs', 'support'));
};

var uniformERP = new ERP(
function uniformSample(params) {
Expand Down Expand Up @@ -169,7 +222,10 @@ function multivariateGaussianScore(params, x) {
return -0.5 * (coeffs + exponents);
}

var multivariateGaussianERP = new ERP(multivariateGaussianSample, multivariateGaussianScore);
var multivariateGaussianERP = new ERP(
multivariateGaussianSample,
multivariateGaussianScore
);

var discreteERP = new ERP(
function discreteSample(params) {
Expand Down Expand Up @@ -500,6 +556,7 @@ function makeMarginalERP(marginal) {
return lk ? Math.log(lk.prob) : -Infinity;
},
{
parameterized: false,
support: function(params) {
return supp;
}
Expand All @@ -510,42 +567,26 @@ function makeMarginalERP(marginal) {
return dist;
}

// Make an ERP that assigns probability 1 to a single value, probability 0 to everything else
var makeDeltaERP = function(v) {
var stringifiedValue = JSON.stringify(v);
return new ERP(
function deltaSample(params) {
return v;
},
function deltaScore(params, val) {
if (JSON.stringify(val) === stringifiedValue) {
return 0;
} else {
return -Infinity;
}
},
{
support: function deltaSupport(params) {
return [v];
}
}
);
};

var makeCategoricalERP = function(ps, vs) {
// note: ps is expected to be normalized
var makeCategoricalERP = function(ps, vs, extraParams) {
var dist = {};
var auxParams = {};
vs.forEach(function(v, i) {dist[JSON.stringify(v)] = {val: v, prob: ps[i]}})
auxParams['parameterized'] = false;
auxParams['support'] = function categoricalSupport(params) {return vs};
if (extraParams) {
_.each(extraParams, function(v, k) {auxParams[k] = v;})
}
var categoricalSample = vs.length === 1 ?
function(params) { return vs[0]; } :
function(params) { return vs[multinomialSample(ps)]; };
return new ERP(
function categoricalSample(params) {
return vs[multinomialSample(ps)];
},
categoricalSample,
function categoricalScore(params, val) {
var i = vs.indexOf(val);
return i < 0 ? -Infinity : Math.log(ps[i]);
var lk = dist[JSON.stringify(val)];
return lk ? Math.log(lk.prob) : -Infinity;
},
{
support: function categoricalSupport(params) {
return vs
}
}
auxParams
);
};

Expand Down Expand Up @@ -584,8 +625,18 @@ var makeMultiplexERP = function(vs, erps) {
);
};

function isErp(x) {
return x && _.isFunction(x.score) && _.isFunction(x.sample);
}

function isErpWithSupport(x) {
return isErp(x) && _.isFunction(x.support);
}

module.exports = {
ERP: ERP,
serializeERP: serializeERP,
deserializeERP: deserializeERP,
bernoulliERP: bernoulliERP,
betaERP: betaERP,
binomialERP: binomialERP,
Expand All @@ -600,7 +651,8 @@ module.exports = {
randomIntegerERP: randomIntegerERP,
uniformERP: uniformERP,
makeMarginalERP: makeMarginalERP,
makeDeltaERP: makeDeltaERP,
makeCategoricalERP: makeCategoricalERP,
makeMultiplexERP: makeMultiplexERP
makeMultiplexERP: makeMultiplexERP,
isErp: isErp,
isErpWithSupport: isErpWithSupport
};
14 changes: 11 additions & 3 deletions src/header.wppl
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,25 @@ var gamma = function(shape, scale) {
};

var deltaERP = function(v) {
return _top.makeDeltaERP(v);
return _top.makeCategoricalERP([1.0], [v]);
};

var categoricalERP = function(ps, vs) {
return _top.makeCategoricalERP(ps, vs);
var categoricalERP = function(ps, vs, extraParams) {
return _top.makeCategoricalERP(ps, vs, extraParams);
};

var multiplexERP = function(vs, erps) {
return _top.makeMultiplexERP(vs, erps);
};

var serializeERP = function(erp) {
return _top.serializeERP(erp);
};

var deserializeERP = function(JSONString) {
return _top.deserializeERP(JSONString);
};

// XRPs

var makeBetaBernoulli = function(pseudocounts) {
Expand Down
35 changes: 35 additions & 0 deletions src/headerUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,40 @@ module.exports = function(env) {
return k(s, cf);
}

// Stochastic caching for a wppl function.
// recompProb - if found in cache, recompute with this prob
// aggregator - when recomputing, aggregate(oldval, newval)
// **warning** avoid use on recursive functions
// **warning** aggregator should ideally be monoid because order can
// be weird when f is not deterministic
function stochasticCache(s, k, a, f, aggregator, recompProb) {
var c = {};
var cf = function(s, k, a) {
var args = Array.prototype.slice.call(arguments, 3);
var stringedArgs = JSON.stringify(args);
var foundInCache = stringedArgs in c;
var recomp = Math.random() < recompProb;
if (foundInCache && !recomp) { // return stored value
return k(s, c[stringedArgs]);
} else { // recompute
var newk = function(s, r) {
var prev = foundInCache ? c[stringedArgs] : null;
var nk = function(s, v) {
c[stringedArgs] = v;
return k(s, v);
};
if (foundInCache) { // aggregate with prev value
return aggregator.apply(this, [s, nk, a].concat([prev, r]))
} else { // just return current value
return nk(s, r);
}
};
return f.apply(this, [s, newk, a].concat(args));
}
};
return k(s, cf);
}

function apply(s, k, a, wpplFn, args) {
return wpplFn.apply(global, [s, k, a].concat(args));
}
Expand All @@ -58,6 +92,7 @@ module.exports = function(env) {
return {
display: display,
cache: cache,
stochasticCache: stochasticCache,
apply: apply,
_Fn: _Fn
};
Expand Down
25 changes: 18 additions & 7 deletions src/inference/incrementalmh.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,9 @@ module.exports = function(env) {
updateProperty(this, 'index', this.parent.nextChildIdx);
this.reachable = true;
// Check params for changes
for (var i = 0; i < params.length; i++)
{
if (params[i] !== this.params[i]) {
this.needsUpdate = true;
updateProperty(this, 'params', params);
break;
}
if (!paramsEqual(params, this.params)) {
this.needsUpdate = true;
updateProperty(this, 'params', params);
}
};

Expand Down Expand Up @@ -263,6 +259,21 @@ module.exports = function(env) {
return true;
}

function paramsEqual(p1, p2) {
if (p1 === p2) {
return true;
} else if (p1 === undefined || p2 === undefined) {
return false;
} else if (p1.length !== p2.length) {
return false;
} else {
for (var i = 0; i < p1.length; i++) {
if (p1[i] !== p2[i]) { return false; }
}
}
return true;
}

// Checks whether two function are equivalent
var fnEquivCache = {};
function fnsEqual(f1, f2) {
Expand Down

0 comments on commit c1f6ef4

Please sign in to comment.