interface Module#
This module defines a friendly interface for creating models in the Pangolin IR.
Example
Take the following model:
x ~ normal(0,1)
y[i] ~ exponential(d[i])
z[i] ~ normal(x, y[i])
In Pangolin, you can declare this model like so:
>>> from pangolin import interface as pi
>>>
>>> x = pi.normal(0,1)
>>> y = pi.vmap(pi.exponential)(pi.constant([2,3,4]))
>>> z = pi.vmap(pi.normal, [None, 0])(x, y)
This produces the following internal representation.
>>> pi.print_upstream(z)
shape | statement
----- | ---------
() | a = 0
() | b = 1
() | c ~ normal(a,b)
(3,) | d = [2 3 4]
(3,) | e ~ vmap(exponential, [0], 3)(d)
(3,) | f ~ vmap(normal, [None, 0], 3)(c,e)
Reference card#
The entire interface is based on methods that create InfixRV objects (or that create functions that create InfixRV objects).
Here are all the functions:
To do… |
Use… |
|---|---|
Constants |
|
Arithmetic |
|
Trigonometry |
|
Other scalar functions |
|
Linear algebra |
|
Other multivariate functions |
|
Scalar distributions |
|
Multivariate distributions |
|
Control flow |
|
Indexing |
|
Transformed densities |
Auto-casting#
Most functions take RVLike arguments, meaning either an RV or something that can
be cast to a NumPy array. In the latter case, it is implicitly cast to a constant RV.
For example, categorical takes an RVLike argument, so instead of tediously writin
this:
>>> probs_list = [0.1, 0.2, 0.7]
>>> probs_array = np.array(probs_list)
>>> probs_rv = constant(probs_array)
>>> x = categorical(probs_rv)
You can simply write:
>>> x = categorical([0.1, 0.2, 0.7])
Broadcasting#
Do you love broadcasting? Do you hate broadcasting? Are you lukewarm about broadcasting?
Well, good news. In this interface, you can configure how broadcasting works. You can
use “simple” broadcasting, you can use full NumPy-style broadcasting, or
you can turn broadcasting off completely. See Broadcasting for details.
API docs#
- pangolin.interface.constant(value)[source]#
Create a constant RV
- Parameters:
value (ArrayLike) – value for the constant. Should be a numpy (or JAX) array or something castable to that, e.g. int / float / list of list of ints/floats.
- Returns:
out – RV with Constant Op
- Return type:
Examples
>>> constant(7) InfixRV(Constant(7)) >>> constant([0,1,2]) InfixRV(Constant([0,1,2]))
- pangolin.interface.vmap(f, in_axes=0, axis_size=None)[source]#
Vectorizing map. Create a function which maps
fover argument axes.This function matches exactly the interface of jax.vmap, although it doesn’t provide some of the optional arguments
jax.vmapdoes.- Parameters:
f (Callable[[*Args], PyTree[InfixRV]]) – The function to vmap. Should take a pytree of
RVas inputs and return a pytree ofRVas outputs.in_axes (Any) – An int, None, or pytree with roots that are int or None. Specifies which axis of each RV should be mapped (if int) or that no axis shuld be mapped (if None). Can be a pytree matching the structure of all arguments to
f. Or, can be a pytree that is a prefix to the pytree representing all arguments. By default, in_axes is zero, meaning all RVs are mapped over the first axis.axis_size (int | None) – An integer indicating the size of the axis to be mapped. This is optional unless all leaves of
in_axesareNone.
- Returns:
vec_f – batched/vectorized version of
fwith arguments matching those offwith extra axes at positions indicated byin_axesand a return value that corresponds to that offbut with an extra axis in the first position.- Return type:
Callable[[*Args], PyTree[InfixRV]]
Examples
Here’s the simplest possible example.
>>> def fun(a): ... return exp(a) >>> A = constant([0,1,2]) >>> vmap(fun)(A) InfixRV(VMap(Exp(), [0], 3), InfixRV(Constant([0,1,2])))
Multiple inputs are OK.
>>> def fun(a,b): ... return a*b >>> A = constant([1,2,3]) >>> B = constant([4,5,6]) >>> vmap(fun)(A, B) InfixRV(VMap(Mul(), [0, 0], 3), InfixRV(Constant([1,2,3])), InfixRV(Constant([4,5,6])))
Unmapped inputs are OK.
>>> def fun(a,b): ... return a*b >>> A = constant([1,2,3]) >>> vmap(fun, [0, None])(A, constant(7)) InfixRV(VMap(Mul(), [0, None], 3), InfixRV(Constant([1,2,3])), InfixRV(Constant(7)))
Multiple outputs are OK.
>>> def fun(a): ... return [exp(a), log(a)] >>> [out1, out2] = vmap(fun)(A) >>> out1 InfixRV(VMap(Exp(), [0], 3), InfixRV(Constant([1,2,3]))) >>> out2 InfixRV(VMap(Log(), [0], 3), InfixRV(Constant([1,2,3])))
Pytree inputs and pytree in_axes are OK
>>> def fun(x): ... return x['cat']*x['dog'] >>> x = {'cat': A, 'dog': constant(3)} >>> in_axes = {'cat': 0, 'dog': None} >>> vmap(fun, in_axes)(x) InfixRV(VMap(Mul(), [0, None], 3), InfixRV(Constant([1,2,3])), InfixRV(Constant(3)))
Pytree outputs are OK
>>> def fun(a, b): ... return {"add": a+b, "mul": a*b} >>> vmap(fun)(A, B) {'add': InfixRV(VMap(Add(), [0, 0], 3), InfixRV(Constant([1,2,3])), InfixRV(Constant([4,5,6]))), 'mul': InfixRV(VMap(Mul(), [0, 0], 3), InfixRV(Constant([1,2,3])), InfixRV(Constant([4,5,6])))}
Pytree in_axis prefixes are OK
>>> def fun(x): ... [a, (b,c)] = x ... return (a*b)+c >>> x = [A, (constant(7), constant(8))] >>> in_axes1 = [0, (None, None)] # axes for each leaf >>> in_axes2 = [0, None] # single None for (b,c) tuple! >>> out1 = vmap(fun, in_axes1)(x) >>> out2 = vmap(fun, in_axes2)(x) >>> out1.op == out2.op True >>> print_upstream(out1) shape | statement ----- | --------- (3,) | a = [1 2 3] () | b = 7 (3,) | c = vmap(mul, [0, None], 3)(a,b) () | d = 8 (3,) | e = vmap(add, [0, None], 3)(c,d) >>> print_upstream(out2) shape | statement ----- | --------- (3,) | a = [1 2 3] () | b = 7 (3,) | c = vmap(mul, [0, None], 3)(a,b) () | d = 8 (3,) | e = vmap(add, [0, None], 3)(c,d)
See also
- pangolin.interface.broadcast(scalar_fun)[source]#
- Parameters:
scalar_fun – Function accepting and returning all-scalar arguments. Can have keyword arguments, but cannot have keyword-only arguments.
- Returns:
“broadcast” version of
scalar_funthat accepts and returns array-shaped random variables according to the current broadcasting rules.
Examples
Take a simple function with scalar input and output
>>> fun = lambda a: InfixRV(ir.Exponential(), a)
If you just have a scalar input, this function works fine in all three broadcasting modes
>>> with override(broadcasting='off'): ... broadcast(fun)(3) InfixRV(Exponential(), InfixRV(Constant(3))) >>> with override(broadcasting='simple'): ... broadcast(fun)(3) InfixRV(Exponential(), InfixRV(Constant(3))) >>> with override(broadcasting='numpy'): ... broadcast(fun)(3) InfixRV(Exponential(), InfixRV(Constant(3)))
If you have a vector input, this only works with simple or numpy broadcasting
>>> with override(broadcasting='off'): ... broadcast(fun)([3,4]) Traceback (most recent call last): ... ValueError: Non-scalar argument: [3 4] >>> with override(broadcasting='simple'): ... broadcast(fun)([3,4]) InfixRV(VMap(Exponential(), [0], 2), InfixRV(Constant([3,4]))) >>> with override(broadcasting='numpy'): ... broadcast(fun)([3,4]) InfixRV(VMap(Exponential(), [0], 2), InfixRV(Constant([3,4])))
Now take this function with one vector and one matrix argument.
>>> fun = lambda a, b: InfixRV(ir.Normal(), a, b) >>> mean = [1,2,3] >>> scale = [[1,2,3],[4,5,6]]
This will only work with numpy-style broadcasting
>>> with override(broadcasting='off'): ... broadcast(fun)(mean, scale) Traceback (most recent call last): ... ValueError: Non-scalar argument: [1 2 3] >>> with override(broadcasting='simple'): ... broadcast(fun)(mean, scale) Traceback (most recent call last): ... ValueError: Can't broadcast non-matching shapes (2, 3) and (3,) >>> with override(broadcasting='numpy'): ... z = broadcast(fun)(mean, scale) >>> print(z) vmap(vmap(normal, [0, 0], 3), [None, 0], 2)([1 2 3], [[1 2 3] [4 5 6]])
- pangolin.interface.composite(fun)[source]#
Turn a function into a new function that returns a single Composite RV. Typically this would be used to create autoregressive distributions via the
autoregressivefunction, rather than called directly by the user.- Parameters:
fun (Callable) – Function that takes any number of RVs or pytrees of RVs and returns a single RV
- Return type:
Examples
>>> def f(stuff): ... x = stuff['cat'] ... y = stuff['dog'] ... a = x+y ... return normal(a, y) >>> composite(f) <function composite.<locals>.myfun at ...> >>> x = makerv(1) >>> y = makerv(2) >>> z = composite(f)({'cat':x, 'dog': y}) >>> z.op Composite(2, (Add(), Normal()), [[0, 1], [2, 1]]) >>> z.parents == (x, y) True
>>> # Example where x is not explicitly passed >>> def f(stuff): ... y = stuff['dog'] ... a = x+y ... return normal(a, y) >>> z = composite(f)({'dog':y}) >>> z.op Composite(2, (Add(), Normal()), [[1, 0], [2, 0]]) >>> z.parents == (y, x) True
- type pangolin.interface.Autoregressable = Callable[[InfixRV], InfixRV] | Callable[[InfixRV, PyTree[InfixRV]], InfixRV] | Callable[[InfixRV, PyTree[InfixRV], PyTree[InfixRV]], InfixRV] | Callable[[InfixRV, PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV]], InfixRV] | Callable[[InfixRV, PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV]], InfixRV] | Callable[[InfixRV, PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV]], InfixRV] | Callable[[InfixRV, PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV], PyTree[InfixRV]], InfixRV]#
- type pangolin.interface.Autoregressed = Callable[[...], InfixRV[Autoregressive[Composite]]]#
- pangolin.interface.autoregressive(fun, length=None, in_axes=0)[source]#
Given a function, create a function to generate an RV with an
AutoregressiveOp. Doingauto_fun = autoregressive(fun, 5) z = auto_fun(start)
is semantically like
carry = start values = [] for i in range(5): carry = fun(carry) values.append(carry) z = concatenate(values)
As a more complex example, doing
auto_fun = autoregressive(fun, 5, [0, None]) z = auto_fun(start, A, B)
is semantically like:
carry = start values = [] # length = 5 for i in range(5): carry = fun(carry, A[i], B) # A sliced on 0th axis, B unsliced values.append(carry) z = concatenate(values)
Even more generally,
in_axescan be any pytree of in-axes that is a tree prefix for the arguments. (See examples below)- Parameters:
fun (Autoregressable[O]) – Function to call repeatedly to define the distribution. Must take some argument
carryas the first input and return a singleRVwith the same shape ascarry. This function can only create a single randomRVwhich (if it exists) must be the return value. But it can create an arbitrary number of non-randomRVinternally. This function can take extra inputs, which can beRVLikeor pytrees ofRVLike. These can be mapped as determined Can optionally take extra inputs that will be mapped.length (None | int) – Length of autoregressive. Can be
Noneif any inputs are mapped along some axis.in_axes (PyTree[int | None]) – What axis to map each input other than
carryover (orNoneif non-mapped). As withvmap, can be a pytree ofRVcorresponding to the structure of all inputs other thancarry.
- Returns:
Function that takes a single init
RVLikeplus some number of pytrees ofRVLikewith mapped axes and produces a singleRV[Autoregressive]- Return type:
Examples
Distribution where
z[i] ~ normal(exp(z[i-1]), 1).>>> x = constant(3.3) >>> def fun(carry): ... return normal(exp(carry), 1) >>> z = autoregressive(fun, 5)(x) >>> isinstance(z.op, Autoregressive) True >>> z.op.base_op Composite(1, (Exp(), Constant(1), Normal()), [[0], [], [1, 2]]) >>> z.op.length 5 >>> z.op.in_axes () >>> z.parents == (x,) True
Distribution where
z[i] ~ exponential(z[i-1]*y[i])>>> x = constant(3.3) >>> y = constant([1,2,3]) >>> def fun(carry, yi): ... return exponential(carry * yi) >>> z = autoregressive(fun)(x,y) >>> isinstance(z.op, Autoregressive) True >>> z.op.base_op Composite(2, (Mul(), Exponential()), [[0, 1], [2]]) >>> z.op.length # note this was inferred! 3 >>> z.op.in_axes (0,) >>> z.parents == (x, y) True
You can also pass bare constants
>>> def fun(carry, yi): ... return exponential(carry * yi) >>> z = autoregressive(fun)(3.3, np.array([1,2,3])) >>> isinstance(z.op, Autoregressive) True >>> z.op.base_op Composite(2, (Mul(), Exponential()), [[0, 1], [2]]) >>> z.op.length 3 >>> z.op.in_axes (0,)
- pangolin.interface.autoregress(length=None, in_axes=0)[source]#
Simple decorator to create functions to create autoregressive RVs. The idea is that
autoregress(length, in_axes)(fun)
is exactly the same as
autoregressive(fun, length, in_axes)
This can be very convenient as a decorator.
- Parameters:
length (int | None) – the number of repetitions
in_axes (Any) – axis to map arguments other than
carryover
- Returns:
Decorator that takes a function that transforms a
RVLikeand some number of pytress ofRVLikeinto a singleRVand returns a function that transforms aRVLikeand some number of pytrees ofRVLikeinto a single RV with anir.Autoregressiveop.- Return type:
Callable[[Autoregressable], Autoregressed]
Examples
Here are two equivalent examples. Here’s
autoregressive:>>> x = constant(3.3) >>> def fun(carry): ... return normal(exp(carry), 1) >>> z1 = autoregressive(fun,5)(x)
And here’s
autoregress:>>> @autoregress(5) ... def fun(carry): ... return normal(exp(carry), 1) >>> z2 = fun(x)
>>> z1.op == z2.op True >>> z1.parents == z2.parents == (x,) True
See also
- pangolin.interface.index(var, *indices)[source]#
Index a RV Using fully-orthogonal indexing.
Note that this function is (intentionally) much simpler than indexing in NumPy or JAX or PyTorch in that it performs fully orthogonal indexing and that slices are treated exactly the same as 1D arrays.
(Similar to
oindexin NEP 21 .)- Parameters:
- Returns:
Random variable with shape equal to the shapes of all indices, concatenated.
- Return type:
Examples
>>> A = constant([[3,0,2],[4,4,4]]) >>> B = index(A, slice(None), [2,2]) >>> print_upstream(B) shape | statement ------ | --------- (2, 3) | a = [[3 0 2] [4 4 4]] (2,) | b = [0 1] (2,) | c = [2 2] (2, 2) | d = index(a,b,c) >>> C = index(A, 0, ...) >>> print_upstream(C) shape | statement ------ | --------- (2, 3) | a = [[3 0 2] [4 4 4]] () | b = 0 (3,) | c = [0 1 2] (3,) | d = index(a,b,c)
Technically, it’s legal (although pointless) to index a 0-D array
>>> A = constant(12.0) >>> B = index(A) >>> print_upstream(B) shape | statement ----- | --------- () | a = 12. () | b = index(a)
- pangolin.interface.vindex(var, *indices)[source]#
Index a RV Using “fully-vectorized” indexing.
This function treats
varas a scalar function of the indices and then applys the normal scalar broadcasting rules (as configured byconfig). Note that this behavior is (intentionally) much simpler than indexing in NumPy or JAX or PyTorch in that it performs fully orthogonal indexing and that slices are treated exactly the same as 1D arrays.(Named in honor of
vindexin NEP 21 .)- Parameters:
- Returns:
Random variable with shape equal to the shapes of all indices, concatenated.
- Return type:
Examples
Index a 2D array with two matching 2D arrays
>>> A = constant([[0.,0,0],[1,1,1],[2,2,2]]) >>> B = constant([[0,1,2],[2,1,0]]) >>> C = constant([[2,1,1],[0,0,0]]) >>> D = vindex(A, B, C) >>> print_upstream(D) shape | statement ------ | --------- (3, 3) | a = [[0. 0. 0.] [1. 1. 1.] [2. 2. 2.]] (2, 3) | b = [[0 1 2] [2 1 0]] (2, 3) | c = [[2 1 1] [0 0 0]] (2, 3) | d = vmap(vmap(index, [None, 0, 0], 3), [None, 0, 0], 2)(a,b,c)
With a square matrix, you can use slices to extract the diagonal
>>> A = constant([[1,2,3],[4,5,6],[7,8,9]]) >>> B = vindex(A,slice(None),slice(None)) >>> print_upstream(B) shape | statement ------ | --------- (3, 3) | a = [[1 2 3] [4 5 6] [7 8 9]] (3,) | b = [0 1 2] (3,) | c = [0 1 2] (3,) | d = vmap(index, [None, 0, 0], 3)(a,b,c)
You can also get the anti-diagonal
>>> C = vindex(A,slice(None),slice(None,None,-1)) >>> print_upstream(C) shape | statement ------ | --------- (3, 3) | a = [[1 2 3] [4 5 6] [7 8 9]] (3,) | b = [0 1 2] (3,) | c = [2 1 0] (3,) | d = vmap(index, [None, 0, 0], 3)(a,b,c)
- pangolin.interface.normal(mu, sigma)[source]#
Creates a Normal distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Normalorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> normal(0.1, 0.2) InfixRV(Normal(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.normal_prec(mu, tau)[source]#
Creates a NormalPrec distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.NormalPrecorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> normal_prec(0.1, 0.2) InfixRV(NormalPrec(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.lognormal(mu, sigma)[source]#
Creates a Lognormal distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Lognormalorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> lognormal(0.1, 0.2) InfixRV(Lognormal(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.cauchy(mu, sigma)[source]#
Creates a Cauchy distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Cauchyorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> cauchy(0.1, 0.2) InfixRV(Cauchy(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.bernoulli(theta)[source]#
Creates a Bernoulli distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
theta (RVLike) – probability (between 0 and 1)
- Returns:
Random variable with
z.opof typepangolin.ir.Bernoulliorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> bernoulli(0.1) InfixRV(Bernoulli(), InfixRV(Constant(0.1)))
- pangolin.interface.bernoulli_logit(theta)[source]#
Creates a BernoulliLogit distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
theta (RVLike) – logit of probability (unbounded)
- Returns:
Random variable with
z.opof typepangolin.ir.BernoulliLogitorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> bernoulli_logit(0.1) InfixRV(BernoulliLogit(), InfixRV(Constant(0.1)))
- pangolin.interface.binomial(N, theta)[source]#
Creates a Binomial distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Binomialorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> binomial(0.1, 0.2) InfixRV(Binomial(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.uniform(alpha, beta)[source]#
Creates a Uniform distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Uniformorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> uniform(0.1, 0.2) InfixRV(Uniform(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.beta(alpha, beta)[source]#
Creates a Beta distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Betaorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> beta(0.1, 0.2) InfixRV(Beta(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.beta_binomial(N, alpha, beta)[source]#
Creates a BetaBinomial distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.BetaBinomialorpangolin.ir.VMapif broadcasting is triggered and 3 parent(s).- Return type:
Examples
>>> beta_binomial(0.1, 0.2, 0.4) InfixRV(BetaBinomial(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)), InfixRV(Constant(0.4)))
Notes
This follows the (N,alpha,beta) convention of Stan (and Wikipedia). Some other systems (e.g. Numpyro) use alternate variable orderings. This is no problem for you as a user, since pangolin does the re-ordering for you based on the backend. But keep it in mind if translating a model from one system to another.
- pangolin.interface.exponential(beta)[source]#
Creates a Exponential distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
beta (RVLike) – rate / inverse scale
- Returns:
Random variable with
z.opof typepangolin.ir.Exponentialorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> exponential(0.1) InfixRV(Exponential(), InfixRV(Constant(0.1)))
- pangolin.interface.gamma(alpha, beta)[source]#
Creates a Gamma distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Gammaorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> gamma(0.1, 0.2) InfixRV(Gamma(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
Notes
This follows Stan in using the “shape/rate” parameterization, not the “shape/scale” parameterization.
- pangolin.interface.poisson(lambd)[source]#
Creates a Poisson distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
lambd (RVLike) – lambda
- Returns:
Random variable with
z.opof typepangolin.ir.Poissonorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> poisson(0.1) InfixRV(Poisson(), InfixRV(Constant(0.1)))
- pangolin.interface.student_t(nu, mu, sigma)[source]#
Creates a StudentT distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.StudentTorpangolin.ir.VMapif broadcasting is triggered and 3 parent(s).- Return type:
Examples
>>> student_t(0.1, 0.2, 0.4) InfixRV(StudentT(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)), InfixRV(Constant(0.4)))
Also, let’s check that we can mix positional and keyword arguments.
>>> student_t(nu=0.1, mu=0.2, sigma=0.4) InfixRV(StudentT(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)), InfixRV(Constant(0.4)))
>>> student_t(0.1, mu=0.2, sigma=0.4) InfixRV(StudentT(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)), InfixRV(Constant(0.4)))
>>> student_t(0.1, sigma=0.4, mu=0.2) InfixRV(StudentT(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)), InfixRV(Constant(0.4)))
>>> student_t(sigma=0.4, nu=0.1, mu=0.2) InfixRV(StudentT(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)), InfixRV(Constant(0.4)))
- pangolin.interface.add(a, b)[source]#
Creates a Add distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Addorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> add(0.1, 0.2) InfixRV(Add(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.sub(a, b)[source]#
Creates a Sub distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Suborpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> sub(0.1, 0.2) InfixRV(Sub(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.mul(a, b)[source]#
Creates a Mul distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Mulorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> mul(0.1, 0.2) InfixRV(Mul(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.div(a, b)[source]#
Creates a Div distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Divorpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> div(0.1, 0.2) InfixRV(Div(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.pow(a, b)[source]#
Creates a Pow distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
- Returns:
Random variable with
z.opof typepangolin.ir.Poworpangolin.ir.VMapif broadcasting is triggered and 2 parent(s).- Return type:
Examples
>>> pow(0.1, 0.2) InfixRV(Pow(), InfixRV(Constant(0.1)), InfixRV(Constant(0.2)))
- pangolin.interface.abs(a)[source]#
Creates a Abs distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Absorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> abs(0.1) InfixRV(Abs(), InfixRV(Constant(0.1)))
- pangolin.interface.arccos(a)[source]#
Creates a Arccos distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Arccosorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> arccos(0.1) InfixRV(Arccos(), InfixRV(Constant(0.1)))
- pangolin.interface.arccosh(a)[source]#
Creates a Arccosh distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Arccoshorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> arccosh(0.1) InfixRV(Arccosh(), InfixRV(Constant(0.1)))
- pangolin.interface.arcsin(a)[source]#
Creates a Arcsin distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Arcsinorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> arcsin(0.1) InfixRV(Arcsin(), InfixRV(Constant(0.1)))
- pangolin.interface.arcsinh(a)[source]#
Creates a Arcsinh distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Arcsinhorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> arcsinh(0.1) InfixRV(Arcsinh(), InfixRV(Constant(0.1)))
- pangolin.interface.arctan(a)[source]#
Creates a Arctan distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Arctanorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> arctan(0.1) InfixRV(Arctan(), InfixRV(Constant(0.1)))
- pangolin.interface.arctanh(a)[source]#
Creates a Arctanh distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Arctanhorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> arctanh(0.1) InfixRV(Arctanh(), InfixRV(Constant(0.1)))
- pangolin.interface.cos(a)[source]#
Creates a Cos distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Cosorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> cos(0.1) InfixRV(Cos(), InfixRV(Constant(0.1)))
- pangolin.interface.cosh(a)[source]#
Creates a Cosh distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Coshorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> cosh(0.1) InfixRV(Cosh(), InfixRV(Constant(0.1)))
- pangolin.interface.exp(a)[source]#
Creates a Exp distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Exporpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> exp(0.1) InfixRV(Exp(), InfixRV(Constant(0.1)))
- pangolin.interface.inv_logit(a)[source]#
Creates a InvLogit distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.InvLogitorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> inv_logit(0.1) InfixRV(InvLogit(), InfixRV(Constant(0.1)))
- pangolin.interface.log(a)[source]#
Creates a Log distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Logorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> log(0.1) InfixRV(Log(), InfixRV(Constant(0.1)))
- pangolin.interface.loggamma(a)[source]#
Creates a Loggamma distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Loggammaorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> loggamma(0.1) InfixRV(Loggamma(), InfixRV(Constant(0.1)))
Notes
Do we want
scipy.special.loggammaorscipy.special.gammaln?
- pangolin.interface.logit(a)[source]#
Creates a Logit distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Logitorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> logit(0.1) InfixRV(Logit(), InfixRV(Constant(0.1)))
- pangolin.interface.sin(a)[source]#
Creates a Sin distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Sinorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> sin(0.1) InfixRV(Sin(), InfixRV(Constant(0.1)))
- pangolin.interface.sinh(a)[source]#
Creates a Sinh distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Sinhorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> sinh(0.1) InfixRV(Sinh(), InfixRV(Constant(0.1)))
- pangolin.interface.step(a)[source]#
Creates a Step distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Steporpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> step(0.1) InfixRV(Step(), InfixRV(Constant(0.1)))
- pangolin.interface.tan(a)[source]#
Creates a Tan distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Tanorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> tan(0.1) InfixRV(Tan(), InfixRV(Constant(0.1)))
- pangolin.interface.tanh(a)[source]#
Creates a Tanh distributed RV.
All arguments must either be scalar or mutually broadcastable according to
config.broadcasting.- Parameters:
a (RVLike) – parent 0
- Returns:
Random variable with
z.opof typepangolin.ir.Tanhorpangolin.ir.VMapif broadcasting is triggered and 1 parent(s).- Return type:
Examples
>>> tanh(0.1) InfixRV(Tanh(), InfixRV(Constant(0.1)))
- pangolin.interface.multi_normal(mean, cov)[source]#
Create a multivariate normal distributed random variable. Call as
multi_normal(mean, cov)- Parameters:
- Return type:
- pangolin.interface.categorical(theta)[source]#
Create a categorical distributed
RVwherethetais a vector of non-negative reals that sums to one.- Parameters:
theta (RVLike) – positive event probabilities (should sum to one)
- Return type:
- pangolin.interface.multinomial(n, p)[source]#
Create a multinomial distributed random variable. Call as
multinomial(n,p)wherenis the number of repetitions andpis a vector of probabilities that sums to one.- Parameters:
- Return type:
- pangolin.interface.dirichlet(alpha)[source]#
Create a Dirichlet distributed random variable. Call as
dirichlet(alpha)wherealphais a 1-D vector of positive reals.
- pangolin.interface.matmul(a, b)[source]#
Matrix product of two arrays. The behavior follows that of numpy.matmul except that
aandbmust both be 1-D or 2-D arrays. In particular:If
aandbare both 1-D then this represents an inner-product.If
ais 1-D andbis 2-D then this represents vector/matrix multiplicationIf
ais 2-D andbis 1-D then this represents matrix/vector multiplicationIf
aandbare both 2-D then this represents matrix/matrix multiplication
- pangolin.interface.inv(a)[source]#
Take the inverse of a matrix. Input must be a 2-D square (invertible) array.
- pangolin.interface.cholesky(a)[source]#
Take the inverse of a matrix. Input must be a 2-D square (invertible) array.
- pangolin.interface.softmax(a)[source]#
Take softmax function. (TODO: conform to syntax of scipy.special.softmax
- pangolin.interface.diag(a)[source]#
Get the diagonal of a matrix. Input must be a 2-D square array. Does not construct diagonal matrices. (Use
diag_matrix)
- pangolin.interface.diag_matrix(a)[source]#
Get the diagonal of a matrix. Input must be a 1-D array. Does not extract diagonals. (Use
diag)- Parameters:
a (RVLike)
- Return type:
- pangolin.interface.fill_tril(params)[source]#
Take a 1D array, fill the lower-triangular entries of a matrix using Pangolin Ops. This does not create a special
Opbut rather reduces to indexing and multiplication.- Parameters:
params (ArrayLike | InfixRV) – 1D array of length
N*(N+1)/2- Returns:
2D Lower-triangular matrix with shape
(N,N)
Examples
>>> params = constant([1.1,2.2,3.3]) >>> out = fill_tril(params) >>> print_upstream(out) shape | statement ------ | --------- (3,) | a = [1.1 2.2 3.3] (2, 2) | b = [[0 0] [1 2]] (2, 2) | c = index(a,b) (2, 2) | d = [[1. 0.] [1. 1.]] (2, 2) | e = vmap(vmap(mul, [0, 0], 2), [0, 0], 2)(c,d)
- pangolin.interface.extract_tril(L)[source]#
Given a square 2D array, return lower-triangular entries as a 1D array. This does not create a special
Opbut instead reduces to multiplication.- Parameters:
L (ArrayLike | InfixRV) – 2D square array with shape
(N,N)- Returns:
1D array of length
N*(N+1)/2
Examples
>>> L = constant([[1.1,0],[2.2,3.3]]) >>> l = extract_tril(L) >>> print_upstream(l) shape | statement ------ | --------- (2, 2) | a = [[1.1 0. ] [2.2 3.3]] (3,) | b = [0 1 1] (3,) | c = [0 0 1] (3,) | d = vmap(index, [None, 0, 0], 3)(a,b,c)
- pangolin.interface.print_upstream(*vars, **named_vars)[source]#
Prints all upstream variables in a friendly readable format.
- Parameters:
Examples
>>> r = RV(Constant(0.5)) >>> s = RV(Bernoulli(), r) >>> t = RV(Constant(2)) >>> u = RV(Normal(), s, t) >>> v = RV(Constant([75, 50, 99])) >>> print_upstream([u, v]) # use autogenerated names shape | statement ----- | --------- () | a = 0.5 () | b ~ bernoulli(a) () | c = 2 () | d ~ normal(b,c) (3,) | e = [75 50 99] >>> print_upstream({'dog':[u], 'kitty':(v,)}) # any pytree is OK shape | statement ----- | --------- () | a = 0.5 () | b ~ bernoulli(a) () | c = 2 () | d ~ normal(b,c) (3,) | e = [75 50 99] >>> print_upstream(dog=u, kitty=v) shape | statement ----- | --------- () | a = 0.5 () | b ~ bernoulli(a) () | c = 2 () | dog ~ normal(b,c) (3,) | kitty = [75 50 99] >>> print_upstream(r=r, s=s, t=t, u=u, v=v) # control all names shape | statement ----- | --------- () | r = 0.5 () | s ~ bernoulli(r) () | t = 2 () | u ~ normal(s,t) (3,) | v = [75 50 99]
- pangolin.interface.RVLike#
A type class indicating either:
An
RVA NumPy array
Something that can be transformed into a NumPy array, such as a float or a list of lists of floats.
Many functions in this interface take
RVLikearguments. If the argument to the function is not anRV, then anInfixRVwith aConstantop is automatically created. So, for example,cos(2.5)is equivalent tocos(InfixRV(pangolin.ir.Constant(2.5))).Note that JAX arrays will typecheck as valid instances of
RVLike(which is probably good) but so will strings (which is probably bad).alias of
ArrayLike|InfixRV
- class pangolin.interface.Transform(forward, inverse, log_det_jac, n_biject_args=0)[source]#
Bases:
objectCreate a
Transformobject. ATransformcan be used to wrap a function that produces a singleRVwith a randomOpand turn it into a function the produces a singleRVwith typeir.Transformed.Mathematically, the idea is that if
P(X)is some density andY=T(X)is a diffeomorphism, thenP(Y=y) = P(X=T⁻¹(y)) × |det ∇T⁻¹(y)|. The goal here is that if you have some function that will produce the random variableX, this transform can be as a wrapper to createYinstead.Three functions must be provided as inputs:
y = forward(x, b1, ..., bN)performs the forward transformation. This takes a singleRVLikexalong with some number ofRVLikeparametersb1, ..., bNand produces a single randomInfixRVyas output. Any number of intermediateInfixRVcan be created internally, but these must all be non-random. Must be a diffeomorphism with respect toxfor any fixed values ofb1, ..., bN.x = inverse(y, b1, ..., bN)performs the inverse transformation, with similar properties asforward. Must be an inverse in the sense thatinverse(forward(x,*args),*args)==x.log_jac_det(x, y, b1, ..., bN)computes the log-determinant Jacobianlog|det ∇forward(x,*args)| == -log|det ∇backwards(y,*args)|. Bothxandyare provided since it may be more convenient to use one rather than the other.
When a transform is called, by default it returns a wrapped function that produces only a transformed random variable (
y). However, if the keyword-only argumentorigisTrue, then the wrapped function also returns a deterministic version of the original variable. That is, it returnsxthat is a deterministic function ofy.- Parameters:
- Returns:
Wrapper function that takes a function
fun(*parents) -> InfixRV[Op]and returns a functionwrapped(*args,*parents) -> InfixRV[Wrapped[Op,Bijector]]. (The bijector is constructed automatically.) The finalOp(Wrapped[Op,Bijector]) is always random.
Examples
Create an inverse-exponential distributed random variable:
>>> reciprocal = Transform(lambda x: 1/x, lambda y: 1/y, lambda x, y: -2*log(abs(x))) >>> print(exponential(1.5)) exponential(1.5) >>> print(reciprocal(exponential)(1.5)) transformed(exponential, bijector(composite(1, [1, div], [[], [1, 0]]), composite(1, [1, div], [[], [1, 0]]), composite(2, [abs, log, -2, mul], [[0], [2], [], [4, 3]])))(1.5)
Create an inverse-exponential-distributed random variable. But also return a deterministic random variable where the inverse is un-done.
>>> reciprocal = Transform(lambda x: 1/x, lambda y: 1/y, lambda x, y: -2*log(abs(x))) >>> y = reciprocal(exponential)(1.5) >>> x = reciprocal.inverse(y) >>> x.op Div() >>> y in x.parents True
Do the same as the above in a single call.
>>> reciprocal = Transform(lambda x: 1/x, lambda y: 1/y, lambda x, y: -2*log(abs(x))) >>> y, x = reciprocal.apply_and_invert(exponential)(1.5) >>> x.op Div() >>> y in x.parents True
Create a random variable distributed like
x*4.4forx ~ normal(3.3, 3.3)>>> myfun = lambda a: normal(a,a) >>> scale = Transform(lambda x, b: x*b, lambda y, b: y/b, lambda x, y, b: log(b), 1) >>> x = myfun(3.3) >>> print(x) normal(3.3, 3.3) >>> y = scale(myfun, 4.4)(3.3) >>> print(y) transformed(normal, bijector(mul, div, composite(3, [log], [[2]])), 1)(4.4, 3.3, 3.3)
Create a lognormal distribution parameterized in terms of the precision of the original normal.
>>> exp_tform = Transform(exp, log, lambda x,y: y) >>> def normal_precision(mean, precision): ... return normal(mean, 1/precision ** 0.5) >>> x = exp_tform(normal_precision)(1.1, 5.5) >>> print(x) transformed(normal, bijector(exp, log, composite(2, [identity], [[1]])))(1.1, div(1, pow(5.5, 0.5)))
See also:
tforms- apply_and_invert(fun, *biject_args)[source]#
- Parameters:
- Returns:
Wrapped version of
funthat creates a tuple containing (1) A randomInfixRVwith aTransformedOp. (2) A deterministicInfixRVthat depends on the first one that undoes the original transform.
- class pangolin.interface.tforms[source]#
Bases:
objectA namespace containing a bunch of pre-baked
Transforminstances for common transforms of distributions.Examples
Create a lognormal,
y = exp(x), x ~ normal(0.5,1.5)>>> y = tforms.exp(normal)(0.5,1.5) >>> print(y) transformed(normal, bijector(exp, log, composite(2, [log], [[1]])))(0.5, 1.5)
Create
y = log(x), x ~ exponential(1.5), i.e. an unconstrained version of an exponential distribution:>>> y = tforms.exp.reverse(exponential)(1.5) >>> print(y) transformed(exponential, bijector(log, exp, composite(2, [log, -1, mul], [[0], [], [2, 3]])))(1.5)
Create
y = logit(x), x ~ beta(2.2, 2.2), i.e. an unconstrained version of a beta distribution with one parameter.>>> y = tforms.logit(lambda u: beta(u,u))(2.2)
Create
y = scaled_logit(x, 3.5, 5.5), x ~ uniform(3.5, 5.5)i.e. an unconstrained version of a uniform distribution.>>> y = tforms.scaled_logit(uniform, 3.5, 5.5)(3.0, 5.0)
- exp = Transform(<function exp>, <function log>, <function tforms.<lambda>>)#
A
Transforminstance that applies the exp bijectory = exp(x). Commonly used to transform from reals to positive reals.
- log = Transform(<function log>, <function exp>, <function Transform.reverse.<locals>.<lambda>>)#
- logit = Transform(<function logit>, <function inv_logit>, <function tforms.<lambda>>)#
A
Transforminstance that applies the logit bijectory = logit(x). Commonly used to transform from [0,1] to reals.
- inv_logit = Transform(<function inv_logit>, <function logit>, <function Transform.reverse.<locals>.<lambda>>)#
A
Transforminstance that applies the inverse logit.
- scaled_logit = Transform(<function tforms.<lambda>>, <function tforms.<lambda>>, <function tforms.<lambda>>)#
A
Transforminstance that applies the scaled logity = logit((y-a)/(a-b). Commonly used to transform from [a,b] to reals.
- cholesky = Transform(<function tforms.<lambda>>, <function tforms.<lambda>>, <function _cholesky_log_det_jac>)#
A
Transforminstance that applies a Cholesky decomposition. Commonly used to transform from symmetric positive definite matrices into triangular matrices.
- fill_tril = Transform(<function fill_tril>, <function extract_tril>, <function tforms.<lambda>>)#
A
Transforminstance that fills a lower-triangular matrix from a vector. Used to transform from real vectors to lower-triangular matrices.
- extract_tril = Transform(<function extract_tril>, <function fill_tril>, <function Transform.reverse.<locals>.<lambda>>)#
A
Transforminstance that extracts the lower-triangular part of a matrix. Commonly used to transform from triangular lower-triangular matrices to real vectors.
- exp_diagonal = Transform(<function _exp_diagonal>, <function _log_diagonal>, <function _exp_diagonal_log_det_jac>)#
A
Transforminstance that exponentiates the diagonal of a matrix. Commonly used to transform real lower-triangular matrices into Cholesky factors.
- log_diagonal = Transform(<function _log_diagonal>, <function _exp_diagonal>, <function Transform.reverse.<locals>.<lambda>>)#
A
Transforminstance that takes the logarithm of the diagonal of a matrix. Commonly used to transform real lower-triangular matrices into Cholesky factors.
- unconstrain_spd = Transform(<function compose_transforms.<locals>.composed_forward>, <function compose_transforms.<locals>.composed_inverse>, <function compose_transforms.<locals>._log_det_forward>)#
A
Transforminstance that transforms a symmetric positive definite into the space of unconstrained reals. Accomplished by (1) taking a Cholesky decomposition (2) taking the logarithm of the diagonal (3) extracting the lower-triangular entries.
- pangolin.interface.compose_transforms(transforms, log_det_direction='forward')[source]#
Composes a sequence of Transform objects into a single Transform.
The resulting Transform applies
y = Tn(...T2(T1(x))...).yieldIf the individual transforms have arguments, these are in linear order. E.g. if you have
T1(x,a)andT2(y,b)thencompose_transforms([T1,T2])(x,a,b)appliesz = T2(T1(x,a),b).
- class pangolin.interface.InfixRV(op, *parents)[source]#
An Infix RV is exactly like a standard
pangolin.ir.RVexcept it supports infix operations.This is a generic type, so you may write
InfixRV[OpClass]as a type hint.- Parameters:
op (O) – The Op defining this class.
*parents (InfixRV)
Examples
>>> a = InfixRV(Constant(2)) >>> b = InfixRV(Constant(3)) >>> a + b InfixRV(Add(), InfixRV(Constant(2)), InfixRV(Constant(3))) >>> a**b InfixRV(Pow(), InfixRV(Constant(2)), InfixRV(Constant(3))) >>> -a InfixRV(Mul(), InfixRV(Constant(2)), InfixRV(Constant(-1)))
See also
- __getitem__(idx)[source]#
You can index an
RVwith the[]operator, e.g. asA[B,C].Note that indexing with this interface is different (and simpler) than NumPy or JAX:
First, indexing is by default fully-orthogonal. This is done to avoid the utter insanity that is NumPy indexing with broadcasting, basic indexing, advanced indexing, and combinations of basic and advanced indexing. In this interface, if
A,B, andCare RVs, thenA[B,C].shape == A.shape + B.shape, and similarly ifBorCare int / list of (list of) int / numpy array / slice.Second, all axes must be indexed. For example, if
Ais a RV with 3 axes, thenA[2]will trigger an exception. The idea of this is to make code more legible and self-enforcing. Instead you must writeA[2, :, :]orA[2, ...].Examples
>>> # Basic indexing >>> A = constant([9,8,7,6,5,4]) >>> B = A[2] >>> B.op Index() >>> B.parents[0] == A True >>> B.parents[1] InfixRV(Constant(2))
>>> # indexing with a slice >>> B = A[1::2] >>> B.op Index() >>> B.parents[0] == A True >>> B.parents[1] InfixRV(Constant([1,3,5]))
>>> # indexing with a combination of constants and slices >>> A = constant([[3,4,5],[6,7,8]]) >>> B = A[[1,0],::2] >>> B.op Index() >>> B.parents[0] == A True >>> B.parents[1] InfixRV(Constant([1,0])) >>> B.parents[2] InfixRV(Constant([0,2]))
- Parameters:
idx (_IdxType | tuple[_IdxType, ...])
- property parent_ops#
Just a shortcut for
tuple(p.op for p in self.parents). Intended mostly for testing.
- property vindex#
Do vectorized indexing. That is, treat the RV as a scalar function from (scalar) indices to (scalar) output. Then, broadcast the indices according to standard broadcasting rules.
Technically
rv.vindexreturns a proxy object that can be indexed, so thatrv.vindex[a,b,c]is equivalent tovindex(rv, a, b, c). Slices are converted to corresponding 1D arrays. Unless an ellipsis is given, the number of arguments must exactly match the number of dimensions of the array.Examples
>>> A = constant([[0,1,2],[3,4,5]]) >>> B = A.vindex[[2,2,2,0,0,0],[0,1,2,0,1,2]] >>> print_upstream(B) shape | statement ------ | --------- (2, 3) | a = [[0 1 2] [3 4 5]] (6,) | b = [2 2 ... 0 0] (6,) | c = [0 1 ... 1 2] (6,) | d = vmap(index, [None, 0, 0], 6)(a,b,c)
- class pangolin.interface.Broadcasting(*values)[source]#
Broadcasting behavior for scalar functions.
Used to change broadcasting behavior via
config.>>> config.broadcasting = Broadcasting.OFF # no broadcasting >>> config.broadcasting = Broadcasting.SIMPLE # simple broadcasting (default) >>> config.broadcasting = Broadcasting.NUMPY # numpy-style broadcasting
Broadcasting applies to “all-scalar” functions where all inputs and outputs are scalar. If broadcasting is set to OFF, all inputs must actually be scalar, and the output is scalar. If broadcasting is set to SIMPLE, then inputs can be non-scalar, but all non-scalar inputs must have exactly the same shape, which is the shape of the output If broadcasting is set to NUMPY, then inputs are broadcast against each other NumPy-style and the resulting shape is the shape of the output.
Suppose
f(a,b,c)is a scalar function. Then here is how broadcasting behaves:Broadcasting mode
OFF
SIMPLE
NUMPY
a.shapeb.shapec.shapef(a,b,c).shape()()()()()()(5,)()()n/a
(5,)(5,)(5,)(5,)()n/a
(5,)(5,)(5,)(5,)(5,)n/a
(5,)(5,)(5,9)()()n/a
(5,9)(5,9)(5,9)(5,9)()n/a
(5,9)(5,9)(5,9)(5,9)(5,9)n/a
(5,9)(5,9)(9,)(5,9)()n/a
n/a
(5,9)(5,)(6,)()n/a
n/a
n/a
This interface does not (currently) support automatic broadcasting for non-scalar functions.
- OFF#
No broadcasting at all. Scalar functions only accept scalar arguments.
- SIMPLE#
Simple broadcasting only. Arguments can be any shape, but non-scalar must have exactly the same shape.
- NUMPY#
NumPy-style broadcasting. The only limitation is that broadcasting of singleton dimensions against non-singleton dimensions is not currently supported.
Examples
No broadcasting
>>> from pangolin import interface as pi >>> pi.config.broadcasting = pi.Broadcasting.OFF >>> pi.student_t(0,1,1).shape () >>> pi.student_t(0,np.ones(5),1).shape Traceback (most recent call last): ... ValueError: Non-scalar argument: [1. 1. 1. 1. 1.]
Simple broadcasting
>>> pi.config.broadcasting = pi.Broadcasting.SIMPLE >>> pi.student_t(0,1,1).shape () >>> pi.student_t(0,np.ones(5),1).shape (5,) >>> pi.student_t(0,np.ones((5,3)),1).shape (5, 3) >>> pi.student_t(0,np.ones((5,3)),np.ones((5,3))).shape (5, 3) >>> pi.student_t(0,np.ones(3),np.ones((5,3))).shape Traceback (most recent call last): ... ValueError: Can't broadcast non-matching shapes (5, 3) and (3,)
NumPy broadcasting
>>> pi.config.broadcasting = pi.Broadcasting.NUMPY >>> pi.student_t(0,1,1).shape () >>> pi.student_t(0,np.ones(5),1).shape (5,) >>> pi.student_t(0,np.ones((5,3)),1).shape (5, 3) >>> pi.student_t(0,np.ones((5,3)),np.ones((5,3))).shape (5, 3) >>> pi.student_t(0,np.ones(3),np.ones((5,3))).shape (5, 3)
Restore to default
>>> pi.config.broadcasting = pi.Broadcasting.SIMPLE
You can also change this behavior temporarily in a code block by using the
overridecontext manager.>>> from pangolin import interface as pi >>> with pi.override(broadcasting="off"): ... x = pi.normal(np.zeros(3), np.ones((5,3))) Traceback (most recent call last): ... ValueError: Non-scalar argument: [0. 0. 0.] >>> with pi.override(broadcasting="numpy"): ... x = pi.normal(np.zeros(3), np.ones((5,3))) ... x.shape (5, 3)
- class pangolin.interface.Config(broadcasting=Broadcasting.SIMPLE)[source]#
Global configuration for Pangolin interface.
See also
- Parameters:
broadcasting (Broadcasting)
- pangolin.interface.config = Config(broadcasting=<Broadcasting.SIMPLE: 'simple'>)#
Singleton instance of
Configclass
- pangolin.interface.override(**kwargs)[source]#
Temporarily override config values.
A context manager that sets
configvalues for the duration of the block, then restores the original values on exit.- Parameters:
kwargs – Attribute names and their temporary values. See
configfor available options.- Raises:
AttributeError – If a key doesn’t match a config attribute.
ValueError – If a value is invalid for that config option.
Example
>>> from pangolin.interface import override, Broadcasting >>> config.broadcasting <Broadcasting.SIMPLE: 'simple'> >>> with override(broadcasting="off"): ... config.broadcasting <Broadcasting.OFF: 'off'> >>> config.broadcasting <Broadcasting.SIMPLE: 'simple'>
- pangolin.interface.Autoregressable = Autoregressable#
Type alias.
Type aliases are created through the type statement:
type Alias = int
In this example, Alias and int will be treated equivalently by static type checkers.
At runtime, Alias is an instance of TypeAliasType. The __name__ attribute holds the name of the type alias. The value of the type alias is stored in the __value__ attribute. It is evaluated lazily, so the value is computed only if the attribute is accessed.
Type aliases can also be generic:
type ListOrSet[T] = list[T] | set[T]
In this case, the type parameters of the alias are stored in the __type_params__ attribute.
See PEP 695 for more information.
- pangolin.interface.Autoregressed = Autoregressed#
Type alias.
Type aliases are created through the type statement:
type Alias = int
In this example, Alias and int will be treated equivalently by static type checkers.
At runtime, Alias is an instance of TypeAliasType. The __name__ attribute holds the name of the type alias. The value of the type alias is stored in the __value__ attribute. It is evaluated lazily, so the value is computed only if the attribute is accessed.
Type aliases can also be generic:
type ListOrSet[T] = list[T] | set[T]
In this case, the type parameters of the alias are stored in the __type_params__ attribute.
See PEP 695 for more information.