Skip to content

Fix missing signs factor in bernoulli_logit_lpmf partials above the cutoff - #3370

Closed
sims1253 wants to merge 2 commits into
stan-dev:developfrom
sims1253:bernoulli-logit-partials-sign
Closed

Fix missing signs factor in bernoulli_logit_lpmf partials above the cutoff#3370
sims1253 wants to merge 2 commits into
stan-dev:developfrom
sims1253:bernoulli-logit-partials-sign

Conversation

@sims1253

@sims1253 sims1253 commented Aug 23, 2026

Copy link
Copy Markdown

bernoulli_logit_lpmf computes, per observation, with signs = 2n − 1 and ntheta = signs * theta:

exp_m_ntheta = exp(-ntheta);
logp = (ntheta > cutoff)
           .select(-exp_m_ntheta,
                   (ntheta < -cutoff).select(ntheta, -log1p(exp_m_ntheta)));

edge<0>(ops_partials).partials_
    = (ntheta > cutoff)
          .select(
              -exp_m_ntheta,   // <-- bug
              (ntheta >= -cutoff)
                  .select(signs * exp_m_ntheta / (exp_m_ntheta + 1),
                          signs));

For ntheta > cutoff the value is −exp(−ntheta), so d(value)/d(ntheta) = +exp_m_ntheta, and the chain rule through ntheta = signs · theta (signs constant in theta) gives ∂lp/∂theta = signs · exp_m_ntheta. The upper partials branch returns −exp_m_ntheta — the derivative of the value without the chain-rule factor. It is correct only when signs = −1 (n = 0). For n = 1 with theta > 20 (and, symmetrically, n = 0 with theta < −20) the gradient of every such element has the wrong sign.

Eval

  • Added regression test cutoff_partials_sign (test/unit/math/prim/prob/bernoulli_logit_test.cpp): for both n = 1, theta = +25 and n = 0, theta = −25 (both put ntheta above the cutoff) it checks the autodiff gradient against (a) the analytic signs * exp(−ntheta) and (b) central finite differences of the double implementation, h = 1e-3 (small enough that both FD points stay inside the same branch, large enough that the ~1e-11-magnitude values subtract cleanly).
  • The test fails on unpatched code (the autodiff gradient comes back sign-flipped, off by exactly 2·exp(−ntheta)) and passes with the fix. Full test binary 6/6; the file's existing value-space cutoff test is unaffected — the bug is in partials only.

Checklist

  • Copyright holder: Maximilian Scholz
    The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
    - Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
    - Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

  • the basic tests are passing

    • unit tests pass (to run, use: ./runTests.py test/unit)
    • header checks pass, (make test-headers)
    • dependencies checks pass, (make test-math-dependencies)
    • docs build, (make doxygen)
    • code passes the built in C++ standards checks (make cpplint)
  • the code is written in idiomatic C++ and changes are documented in the doxygen

  • the new changes are tested

…cutoff

In the (ntheta > cutoff) branch the value is -exp(-ntheta), so the
derivative with respect to theta is signs * exp(-ntheta) with
signs = 2n - 1. The partials expression returned -exp_m_ntheta instead,
which is only correct for n = 0; for observations with n = 1 and
theta > 20 (and, symmetrically, n = 0 with theta < -20) the gradient
of every such element has the wrong sign. The per-element error is
2 * exp(-ntheta) (<= 4e-9 just above the cutoff), which is presumably
why it went unnoticed.

The same branch already applies signs correctly in the middle and
lower branches; this makes the upper branch consistent. Adds a
regression test checking the autodiff gradient against both the
analytic value and central finite differences of the double
implementation, staying inside the same branch; it fails on the
previous code.
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.

1 participant