Skip to content

Model

Defined in: model.js:52

Bayesian probabilistic model: a DAG of random variables (priors), observed likelihoods, generic Model#potential log-density terms, and named Model#deterministic transforms, exposing the joint log-probability and its gradient for the MCMC samplers.

Constructors

Constructor

new Model(name?): Model

Defined in: model.js:63

Accepts either a positional name or a single options object { name }.

Parameters

name?

any = 'model'

Model name, or an options object { name }

Returns

Model

Examples

new Model('linear_regression')
new Model({ name: 'linear_regression' })

Properties

_transformCache

_transformCache: Map<any, any>

Defined in: model.js:271


compiledTerms

compiledTerms: Map<any, any>

Defined in: model.js:75


deterministics

deterministics: Map<any, any>

Defined in: model.js:72


name

name: any

Defined in: model.js:67


observedTerms

observedTerms: Map<any, any>

Defined in: model.js:79


observedVars

observedVars: Map<any, any>

Defined in: model.js:69


potentialGrads

potentialGrads: Map<any, any>

Defined in: model.js:71


potentials

potentials: Map<any, any>

Defined in: model.js:70


variables

variables: Map<any, any>

Defined in: model.js:68

Methods

_potentialSum()

_potentialSum(params): number

Defined in: model.js:291

Sum of all potential terms at the given parameter values.

Parameters

params

any

Returns

number


addVariable()

addVariable(name, distribution, observed?): Distribution

Defined in: model.js:269

Add a random variable to the model

Parameters

name

string

Name of the variable

distribution

Distribution

Distribution of the variable

observed?

any = null

Observed data (optional)

Returns

Distribution

The distribution


autoPotential()

autoPotential(name, fn, options?): Model

Defined in: model.js:187

Register a potential written in @tangent.to/grad ops, differentiated exactly by reverse-mode autodiff.

The same term as Model#potential, but fn builds its log-density from grad’s ops instead of plain arithmetic, and returns that expression rather than a number. No gradient is derived by hand and none is approximated:

import { add, mul, sub, div, log, square, sum, matmul } from '@tangent.to/grad';
model.autoPotential('y', (v) => {
const z = div(sub(yData, matmul(X, v.beta)), v.sigma);
return sub(mul(-0.5, sum(square(z))), mul(yData.length, log(v.sigma)));
});

add and mul take any number of operands, which is what keeps a mean with several terms readable. JavaScript cannot overload +, so this is as close as the language gets to PyMC’s mu0 + tau * z + gamma:

const mu = add(v.mu0, mul(tau, matmul(Z, v.z)), matmul(C, v.cyc));

Against the finite-difference fallback on a 21-parameter regression with 300 observations: one likelihood evaluation per gradient instead of 2·P, NUTS 7.7× faster end to end, and the same posterior. The gradient matches a hand-derived closed form to ~1e-13, where central differences are off by ~2e-7.

The value and gradient share one evaluation, so the sampler’s value-and-gradient path sweeps the data once rather than twice.

The tape is COMPILED by default: built once and replayed at each new set of parameters, rather than reconstructed per call. That is worth roughly 6x on a real model, and it is safe here because of the contract above. fn builds an expression out of grad’s ops, so its graph is fixed by the way it is written; and a sampler holds every parameter’s shape constant for the length of a run, so nothing can change underneath the plan.

Stepping outside that contract breaks the assumption, and the two ways to do it both take deliberate effort: branching on a parameter’s numeric value by reaching into .data, so different draws take different paths through fn, or closing over data that is mutated while the sampler runs. Neither is an expression built from ops, which is why the default is what it is. Pass { compile: false } if you need one of them.

Parameters

name

string

Identifier for the term

fn

(params) => any

Builds the log-density as a grad expression; receives the free variables as grad Vars keyed by name

options?
compile?

boolean

reuse the tape across calls

Returns

Model

this


computeDeterministics()

computeDeterministics(trace): any

Defined in: model.js:715

Parameters

trace

any

Returns

any


deterministic()

deterministic(name, fn): Model

Defined in: model.js:257

Register a named deterministic transform of the parameters for recording in the trace (computed post-hoc from posterior draws). Deterministics do NOT affect the log-probability - use Model#potential for likelihood or factor terms.

Parameters

name

string

Identifier for the transform

fn

(params) => number | any[]

The transform

Returns

Model

this


getFreeVariableNames()

getFreeVariableNames(): string[]

Defined in: model.js:601

Get list of unobserved variable names

Returns

string[]

Variable names


getVariable()

getVariable(name): Distribution

Defined in: model.js:286

Get a variable from the model

Parameters

name

string

Name of the variable

Returns

Distribution

The distribution


gradientsOnly()

gradientsOnly(params): any

Defined in: model.js:377

Gradient of the joint log-probability WITHOUT its value — exactly logProbAndGradient(params).gradients, skipping the potential-value pass.

Samplers’ leapfrog steps only consume the gradient, but for a model with an analytic potential gradient, computing the discarded value costs a full extra pass over the data at every leapfrog step. This method omits it; the returned gradients are identical.

Parameters

params

any

Parameter values as {name: number|Array} pairs

Returns

any

{name: number|Array} map of gradients


gradientsOnlyUnconstrained()

gradientsOnlyUnconstrained(uparams): any

Defined in: model.js:509

Gradient only, in unconstrained space — the leapfrog hot path.

Parameters

uparams

any

Returns

any

{name: number|Array}


hasConstrainedVariables()

hasConstrainedVariables(): boolean

Defined in: model.js:420

Does any free variable have a bounded support?

When nothing is constrained the unconstrained space IS the constrained one and every transform is the identity, so a sampler can skip the mapping entirely and behave exactly as it did before this existed.

Returns

boolean


logProb()

logProb(params): number

Defined in: model.js:304

Compute the log probability of the model given parameter values

Parameters

params

any

Parameter values as {name: number|Array} pairs

Returns

number

Log probability (scalar)


logProbAndGradient()

logProbAndGradient(params): object

Defined in: model.js:338

Compute the log probability and its gradient with respect to parameters.

Prior terms are differentiated analytically (proba dlogpdf); potential terms by central finite differences with step h = 1e-6 * max(1, |x|) per scalar component.

Parameters

params

any

Parameter values as {name: number|Array} pairs

Returns

object

The scalar log probability and a {name: number|Array} map of gradients, one per parameter

gradients

gradients: any

logProb

logProb: number


logProbAndGradientUnconstrained()

logProbAndGradientUnconstrained(uparams): object

Defined in: model.js:477

Joint log-probability and gradient in UNCONSTRAINED space.

The change of variables adds Σ log|dx/du| to the log-density, which is what keeps the posterior invariant: without it the sampler would explore the transformed density, not the one you wrote. The gradient is chained through the same derivative, plus the d/du of that Jacobian term.

For a lower-bounded parameter x = a + eᵘ the Jacobian term is just u, so its derivative is 1 — the “+1” below. For a doubly-bounded one it is 1 − 2σ(u).

Parameters

uparams

any

{name: number|Array}, unconstrained

Returns

object

both in unconstrained terms

gradients

gradients: any

logProb

logProb: number


observe()

observe(name, factory, observed, options?): Model

Defined in: model.js:226

Declare an observed random variable: the likelihood, derived from a distribution instead of written out.

factory receives the free variables as grad Vars and returns a distribution whose parameters are expressions in them. The term added to the model is that distribution’s logDensity at observed, differentiated exactly and compiled, so this is Model#autoPotential with the density supplied by the distribution rather than by you. What that removes from a model is everything a PyMC user never writes: the kernel, the -n log sigma, the normalizing constant.

const { add, mul } = mc.ops;
model.addVariable('a', new Normal(0, 5));
model.addVariable('b', new Normal(0, 5));
model.addVariable('sigma', new HalfNormal(2));
model.observe('y', (v) => new Normal(add(v.a, mul(v.b, xData)), v.sigma), yData);

The seven built-in distributions can be observed. A user-defined one cannot be differentiated and is refused here; write its term with autoPotential.

Parameters

name

string

Identifier for the term

factory

(v) => Distribution

Builds the observation distribution from the free variables

observed

number | any[]

The data

options?

any = {}

As for Model#autoPotential

Returns

Model

this


potential()

potential(name, fn, gradFn?): Model

Defined in: model.js:124

Register a generic log-density term (a “potential” / factor) contributing to the joint log-probability. fn(params) receives the current free-variable values as plain numbers (or arrays) keyed by name and must return a number or an array of log-density values (which are summed into the total).

This is the general mechanism for likelihoods whose parameters are arbitrary deterministic functions of the latent variables and data - the deterministic expression is computed inside fn with ordinary JavaScript math:

model.potential('y', (v) =>
new Normal(xData.map((x) => v.slope * x + v.intercept), v.sigma).logProb(yData));

Gradients of potentials are estimated by central finite differences by default; priors added with Model#addVariable get analytic gradients. For a large data term this finite-difference cost (2·(#free params) extra evaluations of fn per gradient) dominates NUTS/HMC — and, more seriously, finite-difference error costs the leapfrog integrator its symplectic property, degrading the acceptance rate. Two ways to avoid it:

Model#autoPotential writes the term in @tangent.to/grad ops and differentiates it exactly, with no derivation by hand. Prefer it.

Otherwise pass an explicit gradFn returning the analytic gradient:

model.potential('y', (v) => new Normal(mu(v), v.sigma).logProb(yData),
(v) => ({ slope: dSlope(v), intercept: dIntercept(v), sigma: dSigma(v) }));

gradFn(params) must return an object mapping each free-variable name to the partial derivative of THIS term’s log-density with respect to it (a number, or an array for a vector-valued variable). It is added to the analytic prior gradients; omit an entry whose partial is zero.

Parameters

name

string

Identifier for the term

fn

(params) => number | number[]

Returns log-density value(s)

gradFn?

(params) => any

Optional analytic gradient of fn

Returns

Model

this


predictPosterior()

predictPosterior(trace, predictFn, nSamples?): any[]

Defined in: model.js:747

Posterior predictive sampling Generate predictions by sampling from the posterior

Parameters

trace

any

Trace object from MCMC sampling

predictFn

Function

Function that takes params and returns predictions

nSamples?

number = null

Number of posterior samples to use (null = use all)

Returns

any[]

Array of predictions from each posterior sample


predictPosteriorSummary()

predictPosteriorSummary(trace, predictFn, credibleInterval?): any

Defined in: model.js:776

Compute posterior predictive mean and credible intervals

Parameters

trace

any

Trace object from MCMC sampling

predictFn

Function

Function that takes params and returns predictions

credibleInterval?

number = 0.95

Credible interval (e.g., 0.95 for 95%)

Returns

any

{mean, lower, upper} predictions


samplePrior()

samplePrior(nSamples?): any

Defined in: model.js:585

Sample from the prior distributions

Parameters

nSamples?

number = 1

Number of samples to generate

Returns

any

Samples as {name: Array} pairs


serializable()

serializable(): { ok: true; } | { ok: false; reason: string; }

Defined in: model.js:634

Can this model be written out as data and rebuilt elsewhere?

It can when every variable is one of mc’s own distributions, so it travels as a class name and parameters, and every potential is a compiled grad term, so it travels as a plan. A potential written over plain numbers is a function and cannot; a user-defined distribution cannot. The answer names the first thing that stands in the way, since it is what the user would have to change to get parallel chains.

Returns

{ ok: true; } | { ok: false; reason: string; }


summary()

summary(): string

Defined in: model.js:825

Create a summary of the model

Returns

string

Model summary


toConstrained()

toConstrained(uparams): any

Defined in: model.js:451

Map unconstrained values back into the model’s units.

Parameters

uparams

any

{name: number|Array}, unconstrained

Returns

any

the same shape, constrained


toJSON()

toJSON(at): any

Defined in: model.js:664

The model as data. Every variable as its distribution’s class name and parameters; every term as a compiled plan; deterministics left out, since they run on the trace afterwards and stay on the calling thread.

A plan exists only once its graph has been traced, which needs a point to trace at; at is that point, in the model’s own (constrained) parameterization, the same values a sampler is initialized with.

Parameters

at

any

a full set of parameter values

Returns

any

structured-clonable


toUnconstrained()

toUnconstrained(params): any

Defined in: model.js:434

Map constrained parameter values into the unconstrained space a gradient sampler should move through.

Parameters

params

any

{name: number|Array} in the model’s own units

Returns

any

the same shape, unconstrained


fromJSON()

static fromJSON(json): Model

Defined in: model.js:698

Rebuild a model from Model#toJSON’s output, on any thread.

The terms come back as compiled plans, bound to the shapes they were traced at. Deterministics do not travel; add them on the calling side and apply them to the trace when the chains return.

Parameters

json

any

Returns

Model