Skip to content

User-defined constrained parameterizations - #967

Open
bob-carpenter wants to merge 20 commits into
masterfrom
fun-distributions
Open

User-defined constrained parameterizations#967
bob-carpenter wants to merge 20 commits into
masterfrom
fun-distributions

Conversation

@bob-carpenter

Copy link
Copy Markdown
Member

This is a whole new chapter on user-defined constrained parameters with some challenging examples. I've tested that all the code actually works.

Submission Checklist

  • Builds locally
  • [n/a] New functions marked with <<{ since VERSION }>>
  • Declare copyright holder and open-source license: see below

Summary

Copyright and Licensing

Please list the copyright holder for the work you are submitting (this will be you or your assignee, such as a university or company):

Simons Foundation

By submitting this pull request, the copyright holder is agreeing to license the submitted work under the following licenses:

@bob-carpenter bob-carpenter changed the title Fun distributions User-defined constrained parameterizations Jul 17, 2026
@bob-carpenter bob-carpenter changed the title User-defined constrained parameterizations [WIP] User-defined constrained parameterizations Jul 17, 2026
@bob-carpenter

Copy link
Copy Markdown
Member Author

I renamed and tagged "WIP" because I want to go over and add a little more text background and also citations to the original threads that introduced these ideas.

@bob-carpenter bob-carpenter changed the title [WIP] User-defined constrained parameterizations User-defined constrained parameterizations Jul 21, 2026
@bob-carpenter

Copy link
Copy Markdown
Member Author

@WardBrian. This is now code complete, spell checked, and read over by me. Sorry, but I sort of got carried away, and as a result, it's a lot.

@WardBrian WardBrian left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fun read. A bunch of comments below

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to reassess https://mc-stan.org/docs/stan-users-guide/reparameterization.html#changes-of-variables and see if that should just be blended into this new chapter (or at least cross-referenced)

real log_sigma;
}
transformed parameters {
real<lower=0> sigma = exp(log_sigma);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth explicitly (re)-stating that the lower= syntax only performs checks in blocks other than parameters

Comment on lines +278 to +281
if (cols(x) != N + binom(N, 2)) {
reject("cols(x) = ", cols(x),
"; but N + binom(N, 2) = ", N + binom(N, 2));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

binom is not a function; it's choose (which is correctly used in the parameters block below)

functions {
matrix toeplitz_matrix(vector x, int M, int N) {
if (num_elements(x) != M + N - 1) {
reject("x must have M + N - 1 elements",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these rejects should be fatal_errors, since the sizes will never change there is no recovery possible

```stan
functions {
matrix symmetric_matrix(vector x, int N) {
matrix[N, N] y = lower_triangular_matrix(x, N);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
matrix[N, N] y = lower_triangular_matrix(x, N);
matrix[N, N] y = lower_triangular_matrix(x, N); // defined as above

Or any other indication that this is not a built-in and therefore this example is not fully self-contained

```

The transform itself, which takes a vector $v$ of size $\binom{N}{2}$,
creates a skew-symmetric matrix (the `skew_symmetric` function is defined

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
creates a skew-symmetric matrix (the `skew_symmetric` function is defined
creates a skew-symmetric matrix (the `skew_symmetric_matrix` function is defined

* @return The `N` x `N` rotation matrix for which `v` is the basis
*/
matrix special_orthogonal_jacobian(vector v, int N) {
matrix[N, N] S = skew_symmetric(v, N);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
matrix[N, N] S = skew_symmetric(v, N);
matrix[N, N] S = skew_symmetric_matrix(v, N);

Comment on lines +1412 to +1417
{
tuple(matrix[D, D], matrix[D, D]) phi_Gamma
= stationary_var1_jacobian(raw, D);
phi = phi_Gamma.1;
Gamma = phi_Gamma.2;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have tuple unpacking

Suggested change
{
tuple(matrix[D, D], matrix[D, D]) phi_Gamma
= stationary_var1_jacobian(raw, D);
phi = phi_Gamma.1;
Gamma = phi_Gamma.2;
}
(phi, Gamma) = stationary_var1_jacobian(raw, D);

Comment on lines +1424 to +1430
covariance `Gamma`. These need to be pulled out of the tuple. This
is one function returning a tuple because there is shared work. The
extra braces mean that `phi_Gamma` is no longer a top-level
`transformed parameters` variable and thus will not be saved in the
output (it would be redundant). The model could have been written
directly in terms of `phi_Gamma.1` and `phi_Gamma.2`, but it's easier
to follow as written. Note that `phi` is used in its transposed form

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which makes this explanation redundant


## Matrices matching known margins

Suppose $X$ is an $M \times N$ matrix $X$, the marginals of which are

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Suppose $X$ is an $M \times N$ matrix $X$, the marginals of which are
Suppose $X$ is an $M \times N$ matrix, the marginals of which are

@SteveBronder SteveBronder left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few small comments on the intro

Comment on lines +40 to +45
$$
p(\phi)
= p(\theta)
\cdot \left| \frac{\textrm{d}}{{\textrm d}\phi} \theta
\right|,
$$

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you define p before this equation?

the natural logarithm.

The underlying sampler works with the unconstrained variable $\phi$
that results from transforming the constrained parameter..

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
that results from transforming the constrained parameter..
that results from transforming the constrained parameter.

prior is uniform, but improper (i.e., the integral over its support
diverges).

The way this is done is by applying a change-of-variables correction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid the use of this.

Suggested change
The way this is done is by applying a change-of-variables correction.
Enforcing a uniform implicit prior on constrained parameters requires applying a change-of-variables correction.

existing constrained types and transforms, as that will let Stan
automatically work out the change-of-variables. The more general
approach is to code a user-defined transform function and either
adjust for then change-of-variables or not, depending on the intended

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
adjust for then change-of-variables or not, depending on the intended
adjust for the change-of-variables or not, depending on the intended


The underlying sampler works with the unconstrained variable $\phi$
that results from transforming the constrained parameter..
In order for the distribution of $\theta$ to be uniform over $(0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In order for the distribution of $\theta$ to be uniform over $(0,
In order for the distribution $p(\cdot)$ of $\theta$ to be uniform over $(0,

\begin{align}
p_\Phi(\phi) &= p_\Theta(\exp(\phi)) \cdot \left| \frac{\textrm{d}}{\textrm{d} \phi} \exp(\phi) \right|
\\[4pt]
&= p_\Theta(\exp(\phi)) \cdot \exp(\phi).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
&= p_\Theta(\exp(\phi)) \cdot \exp(\phi).
&= p_\Theta(\exp(\phi)) \cdot \exp(\phi)

= p(\theta)
\cdot \left| \frac{\textrm{d}}{{\textrm d}\phi} \theta
\right|,
$$

@SteveBronder SteveBronder Jul 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the substitution of $\phi$ for $f\left(\theta\right)$ imo I think this reads nicer as

$$ p\left(f\left(\theta\right)\right) = p(\theta) \cdot \left| \frac{\textrm{d f}}{{\textrm d}\theta} \theta \right|, $$

Though applying that through the line right after this I'm not totally understanding the equation. And what is $p_\Phi$?

$$ \begin{align} p_\Phi(\log\left(\theta\right)) &= p_\Theta\left(\exp\left(\log\left(\theta\right)\right)\right) \cdot \left| \frac{\textrm{d}}{\textrm{d} \log\left(\theta\right)} \exp\left(\log\left(\theta\right)\right) \right| \\ &= p_\Theta(\exp\left(\log\left(\theta\right)\right)) \cdot \exp\left(\log\left(\theta\right)\right) \end{align} $$

Wouldn't the last line reduce $\exp(\log(\cdot))$ so that the final line just becomes

$$ p_\Theta(\theta) \cdot \theta $$

Maybe I'm just not understanding something?

Comment on lines +145 to +152
be constant and thus should be included.


## Specialized matrices

There are dozens if not hundreds of specialized matrix forms in use,
some of the most popular of which will be described in this section.

@SteveBronder SteveBronder Jul 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of a 100 G-force of a transition from change of variables to bespoke matrices

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants