Skip to content

DOC: Clarify jacobian parameter and overall docstring for Model.logp and similar#8185

Open
avm19 wants to merge 3 commits intopymc-devs:mainfrom
avm19:model_logp_doc
Open

DOC: Clarify jacobian parameter and overall docstring for Model.logp and similar#8185
avm19 wants to merge 3 commits intopymc-devs:mainfrom
avm19:model_logp_doc

Conversation

@avm19
Copy link
Copy Markdown

@avm19 avm19 commented Mar 10, 2026

Description

Improve docstrings for Model.logp() and related methods (dlogp(), d2logp(), compile_*()) and clarify what parameter jacobian does.

Related Issue

Checklist

Type of change

  • New feature / enhancement
  • Bug fix
  • Documentation
  • Maintenance
  • Other (please specify):

@read-the-docs-community
Copy link
Copy Markdown

read-the-docs-community bot commented Mar 10, 2026

Documentation build overview

📚 pymc | 🛠️ Build #31852668 | 📁 Comparing 527ed74 against latest (6c48a0b)


🔍 Preview build

Show files changed (86 files in total): 📝 86 modified | ➕ 0 added | ➖ 0 deleted
File Status
genindex.html 📝 modified
glossary.html 📝 modified
py-modindex.html 📝 modified
api/distributions/transforms.html 📝 modified
api/generated/pymc.icdf.html 📝 modified
api/generated/pymc.logcdf.html 📝 modified
api/generated/pymc.logp.html 📝 modified
api/generated/pymc.vectorize_over_posterior.html 📝 modified
_modules/pymc/model/core.html 📝 modified
api/distributions/classmethods/pymc.Censored.dist.html 📝 modified
api/distributions/classmethods/pymc.Simulator.dist.html 📝 modified
api/distributions/classmethods/pymc.Truncated.dist.html 📝 modified
api/model/generated/pymc.model.core.Model.html 📝 modified
api/distributions/generated/classmethods/pymc.AR.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.AsymmetricLaplace.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Bernoulli.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Beta.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.BetaBinomial.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Binomial.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.CAR.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Categorical.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Cauchy.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Continuous.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.DiracDelta.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Dirichlet.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.DirichletMultinomial.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Discrete.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.DiscreteUniform.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.DiscreteWeibull.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Distribution.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.EulerMaruyama.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.ExGaussian.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Exponential.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Flat.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.GARCH11.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Gamma.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Geometric.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Gumbel.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.HalfCauchy.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.HalfFlat.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.HalfNormal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.HalfStudentT.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.HyperGeometric.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.ICAR.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Interpolated.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.InverseGamma.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.KroneckerNormal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Kumaraswamy.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Laplace.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.LogNormal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Logistic.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.MatrixNormal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Mixture.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Moyal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Multinomial.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.MvNormal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.MvStudentT.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.NegativeBinomial.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Normal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Pareto.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Poisson.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.PolyaGamma.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Rice.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.SkewNormal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.SkewStudentT.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.StickBreakingWeights.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.StudentT.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Triangular.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.TruncatedNormal.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Uniform.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.VonMises.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Wald.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Weibull.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.Wishart.dist.html 📝 modified
api/distributions/generated/classmethods/pymc.ZeroSumNormal.dist.html 📝 modified
api/gp/generated/classmethods/pymc.gp.Marginal.marginal_likelihood.html 📝 modified
api/gp/generated/classmethods/pymc.gp.MarginalApprox.marginal_likelihood.html 📝 modified
api/gp/generated/classmethods/pymc.gp.MarginalKron.marginal_likelihood.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.compile_d2logp.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.compile_dlogp.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.compile_fn.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.compile_logp.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.create_value_var.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.d2logp.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.dlogp.html 📝 modified
api/model/generated/classmethods/pymc.model.core.Model.logp.html 📝 modified

@avm19 avm19 changed the title Model logp doc DOC: Clarify jacobian parameter and overall docstring for Model.logp and similar Mar 10, 2026
***************

.. currentmodule:: pymc.distributions.transforms
.. module:: pymc.distributions.transforms
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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Just to clarify. :module:: must be specified at least once (and probably only once) for Sphinx to create an "index entry". Without it, references :currentmodule::... and :mod:... would lead to nowhere. It so happens that module:: pymc.distributions.transforms was absent, even though currentmodule was present, and Sphinx does not check this.

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.

Using the module directive here is probably fine, I am not sure how relevant it is though. Is pymc.distributions.transforms as a module something relevant to users we'll want to reference? If so we need to switch, otherwise using currentmodule is perfectly fine but using module won't hurt either.


Full context:

.. currentmodule:: is basically syntactic sugar, its only role is defining the module name to prepend all the autodoc/autosummary entries in that file. That is, we can use circular or ordered in the autosummary directive below instead of needing to specify pymc.distributions.transforms.circular...

.. module:: does the same but also generates a target that can be referenced from other places using the :mod:`pymc.distributions.transforms` role; .. currentmodule:: does not reference the module directive with the same name nor needs it to exist to work properly though.

The module directive can also take a :synopsis: to manually add the module docstring. In our case however, using this optional argument wouldn't make much sense, it would be much better to use .. automodule:: instead which defines a module directive pulling the docstring directly from the module in question (like we do for classes or functions).

Sphinx does not check this because module/automodule should indeed be used at most once, but it isn't really necessary to use it at least once, that depends on how the package maintainers decide to define and document the public API.

Copy link
Copy Markdown
Author

@avm19 avm19 Mar 24, 2026

Choose a reason for hiding this comment

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

Using the module directive here is probably fine, I am not sure how relevant it is though.

Without it references did not work, which is why I made the change.

P.S. I don't remember if Sphinx threw an error or just no hyperlink without the module. I don't know much about this, but I guess you were not using automodule because this module is accompanied by a lot of text, which was placed in transform.rst (better have it there, than a huge module-level docstring). Some other entries, e.g. shape_utils.rst, also contain text in addition to Sphinx directives.

Is pymc.distributions.transforms as a module something relevant to users we'll want to reference?

Yes. The module page explains how PyMC deals with transformations, and I reference it as a good source for further details in a method's docstring.

Random variables or potential terms whose contribution to logp is to be included.
If None, use all basic (free or observed) variables and potentials defined in the model.
jacobian : bool, default True
If True, add Jacobian contributions associated with automatic variable transformations,
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.

Needs some working.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Anything specific?

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.

Include jacobian correction term for transformed variables ?

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 don't think we should explain why, specially because the why is context specific. Maybe you don't want it because you want to do optimization on the constrained space, using unconstrained variables.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It is the goal of this PR to give a concise and clear description of what jacobian=True does, so this bit is central. I am trying to share my perspective of an outsider to make the docstrings friendlier to users who are not used to PyMC parlance.

I don't think we should explain why

Here I am explaining not so much "why", as "what". The phrase "Jacobian correction term" might sound precise and unquestionable to people working on PyMC or specialising in Bayesian modelling, but to someone coming from a slightly different field it is very ambiguous and uninformative. Which Jacobian? Why correction? There are so many Jacobians, it is not clear which one it is about. Any gradient, such as Model.dlogp, is also a Jacobian (neglect the differential geometry subtleties of co- and contra-variance), and one would naturally think that the Jacobian refers to dlogp, because d2logp is called a Hessian in the same file. Further, what is meant by "correction"? Who made the error that needs to be corrected? Or is it like the prediction-correction optimisation methods used in engineering? They use first and second derivatives, so sounds about right... Frankly, I never saw the change-of-variables Jacobian determinant being referred to as "correction", so it is the last connection I would make.

I am trying to explain what jacobian does in a way that does not allow misinterpretations. I am not try trying to convince the reader why they should use it.

I thought about using "which" here:

jacobian : bool, default True
If True, add Jacobian contributions associated with automatic variable transformations,
so that which make the result is the true density of transformed random variables.
See :py:mod:pymc.distributions.transforms for details.

but then it is not clear if "which" refers to "transformations" (incorrect) or to "contributions" (correct). This is why I chose "so that" as a conjunction -- it refers to "add", i.e. the purpose of the parameter jacobian.

Copy link
Copy Markdown
Author

@avm19 avm19 Mar 25, 2026

Choose a reason for hiding this comment

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

They may not be automatic, the user can override behavior with transform=x when defining a variable.

They are still automatic in the sense that you don't have to change variables in the model definition. Maybe it would be correct to say that all these transformations are automatic, which includes both the default transformations and the user-specified transformations? Here is a relevant quote:

If a random variable has a ``default_transform`` and an additional transform
is provided through the ``transform`` parameter, PyMC will automatically
create an instance of the :class:`Chain` transform that applies the
user-provided transform on top of the default one.

true thing still, doesn't sound quite right.

Oh, I misunderstood your previous message, I thought you were referring to somewhat redundant "If True ...". Let me think if there are ways to re-phrase "true density".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure about the true thing still, doesn't sound quite right

You are right, "true density" doesn't sound right, neither does "proper", and I haven't found anything better. How about:

jacobian : bool, default True
If True, add Jacobian contributions associated with automatic variable transformations,
so that the result is the log-probability density of transformed random variables.
See :py:mod:pymc.distributions.transforms for details.

That is, if I convinced you that user-specified and default transforms are all "automatic". Otherwise, remove the word "automatic".

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.

Agreed that "true" is not right, it makes a subjective judgement where none exists. You can see an example here of a case where applying it leads to the "false" logp. It applies a change of variables correction implied by the model's transformation, if one exists. I would write a docstring that says something like that:

jacobian: bool, default True
     If True, the change-of-variable correction implied by each random variable's transformation, if applicable. This correction accounts for the distortion of probability mass induced by the application of a non-linear function to a random variable. See :py:mod:pymc.distributions.transforms for details. 

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You can see an example here of a case where applying it leads to the "false" logp.

After a cursory reading, I tend to think that the confusion in there was between $\ln p_{X_t, Y_t, Z_t, ...}(x_t,y_t,z_t,...)$ and $\ln p_{X,Y,Z,...}(x(x_t),y(y_t),z(z_t),...)$. Both are functions of transformed variables, both are "true" logps. But the former is the log density of transformed RVs, and the latter is the log density of the original RVs. To sample transformed variables one would use $p_{X_t, Y_t, Z_t}(...)$, and to optimise $p_{X,Y,Z}(...)$ one would use $p_{X,Y,Z}(...)$. I alluded to this in the conversation and in my suggested version. The "true" was a bad choice (it may also suggest ground truth vs approximation). But I think "density of transformed random variables" is a good part, which pushes the reader in the right direction.

Maybe we should also add that with jacobian=False the result is "density of untransformed variables"? (I am like 95% sure, but I can check the maths and get a toy example)

Copy link
Copy Markdown
Member

@ricardoV94 ricardoV94 Mar 26, 2026

Choose a reason for hiding this comment

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

(I tend to think of transformed/untransformed as unconstrained/constrained, but that's giving an interpretation of why the transform was applied)

Maybe we should also add that with jacobian=False the result is "density of untransformed variables"? (I am like 95% sure, but I can check the maths and get a toy example)

I would maybe try to emphasize this function always takes a point in transformed space and "untransforms" it to evaluate the logprob function of each random variable in it's "natural"/original space. What's optional is whether we add the terms corresponding to this transformation.

This is not a specific suggestion, just sharing how I think mechanistically about it.

jacobian: bool = True,
) -> Variable:
"""Gradient of the models log-probability w.r.t. ``vars``.
"""Gradient of the joint log-probability density of the model.
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.

density is a strong word, model may be discrete or a mix

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Technically, a probability mass function is a probability density (i.e. a Radon-Nikodym derivative) with respect to the counting measure, in contrast to a "regular" PDF of an absolutely continuous variable, which is wrt the standard Lebesgue-Borel measure; and so the mixed case should also be a density wrt a certain product measure.

I agree that people might find this confusing, and some may even assume that discrete variables are marginalised out or not allowed at all in this context. On the other hand, I used the word "density" because Jacobians arise only in (absolutely) continuous variables; if it were not a density, there would be no Jacobians.

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 do constant use the term log-probability though. Yes jacobian corrections don't exist for discrete variables, I don't think that's going to be what makes it confusing to understand jacobian kwarg here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Actually, the word "density" was there before me in compile_logp, compile_dlogp, compile_d2logp only. I only put it everywhere else for consistency.

"""Compiled log probability density gradient function.

Should I make it "log-probability" (hyphenated, noun) and remove "density" from everywhere (except jacobian parameter where "density" is relevant)?

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants