Skip to content

Add ChainRulesCore extension with frule/rrule for logmeanexp, logvarexp, logstdexp - #3

Merged
cossio merged 1 commit into
mainfrom
chainrules
Jul 29, 2026
Merged

Add ChainRulesCore extension with frule/rrule for logmeanexp, logvarexp, logstdexp#3
cossio merged 1 commit into
mainfrom
chainrules

Conversation

@cossio

@cossio cossio commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Adds ChainRules support via a ChainRulesCore package extension, following the pattern of JuliaStats/LogExpFunctions.jl#120.

Changes

  • New ext/LogStatFunctionsChainRulesCoreExt.jl defining frules and rrules for logmeanexp, logvarexp and logstdexp (with dims and corrected keyword support). The logvarexp/logstdexp rules also accept the logmean keyword, matching the primal signatures.
  • The logvarexp gradient uses ∂/∂xⱼ = 2 exp(xⱼ)(exp(xⱼ) − m) / Σᵢ(exp(xᵢ) − m)² with m = exp(logmean); the dependence of m on x cancels because Σᵢ(exp(xᵢ) − m) = 0, and corrected only shifts the result by a constant. logstdexp is half of that. Reverse rules return an InplaceableThunk with ProjectTo, as in the LogExpFunctions PR.
  • Project.toml: ChainRulesCore as a weak dependency with compat "1".
  • Tests: test/chainrules.jl checks frule/rrule against finite differences with ChainRulesTestUtils over vectors/matrices, dims ∈ (:, 1, 1:2, 2) and both corrected values (330 assertions).
  • Changelog entry and a short note in the manual.

Verification

  • Pkg.test() passes locally on Julia 1.12.6 (functions, chainrules, ExplicitImports, Aqua).
  • Runic 1.7 --check passes.
  • Docs build passes locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

Define forward- and reverse-mode ChainRules for logmeanexp, logvarexp
and logstdexp in a ChainRulesCore package extension, following the
pattern of JuliaStats/LogExpFunctions.jl#120. The logvarexp/logstdexp
rules accept the same logmean keyword as the primal functions.

Tested against finite differences with ChainRulesTestUtils across dims
and corrected combinations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj
@cossio
cossio marked this pull request as ready for review July 29, 2026 10:20
Copilot AI review requested due to automatic review settings July 29, 2026 10:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (fed55f7) to head (f7e85d7).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main        #3   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         2    +1     
  Lines           24        59   +35     
=========================================
+ Hits            24        59   +35     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cossio
cossio merged commit 0ffc002 into main Jul 29, 2026
9 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7e85d7f04

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/chainrules.jl

@testset "chainrules" begin
for x in (randn(10), randn(10, 8)), dims in (:, 1, 1:2, 2)
dims isa Colon || all(d ≤ ndims(x) for d in dims) || continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle scalar dims before iterating

When the loop reaches dims = 1 or dims = 2, this condition attempts to iterate an Int, which raises a MethodError in Julia before any derivative checks run. Normalize an integer dimension to a one-element tuple, or handle dims isa Integer separately, so the new test suite can complete.

Useful? React with 👍 / 👎.

Comment on lines +57 to +59
d = x .- logmean
e = expm1.(d)
return (2 .* exp.(d) .* e) ./ sum(abs2, e; dims)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep the variance gradient denominator numerically scaled

For nearly equal but representably distinct inputs, squaring e can underflow even though both the primal and its derivative are finite; for example, with Float64[-1e-200, 1e-200], sum(abs2, e) becomes zero while the expected log-variance gradient is on the order of 1e200, so these rules return Inf/NaN. Compute the ratio with log-domain arithmetic or scale e before forming the sum of squares.

Useful? React with 👍 / 👎.

Comment on lines +66 to +67
Δ -> Δ .+= Ω̄ .* ∂x,
ChainRulesCore.@thunk(project_x(Ω̄ .* ∂x)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Unthunk the incoming cotangent before broadcasting

When an upstream ChainRules pullback supplies Ω̄ as a supported Thunk, both branches capture that wrapper and later attempt Ω̄ .* ∂x; evaluating the returned tangent then fails because the thunk itself is not the cotangent array or scalar. Unthunk Ω̄ before using it, or explicitly compose the thunks so this rule works in reverse-mode chains that preserve lazy cotangents.

Useful? React with 👍 / 👎.

using LogStatFunctions: logmeanexp, logvarexp, logstdexp
import ChainRulesCore

function ChainRulesCore.frule((_, Δx), ::typeof(logmeanexp), x::AbstractArray{<:Real}; dims = :)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Qualify or implement the advertised complex-input rules

The package already supports complex arrays for logmeanexp, but every new rule is restricted to AbstractArray{<:Real} while the added documentation says all three functions have ChainRules derivatives without stating that limitation. Consequently, rrule(logmeanexp, complex_x) has no method from this extension and complex users do not receive the advertised AD support; either add the correctly conjugated complex rule or document the real-input restriction.

Useful? React with 👍 / 👎.

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.

2 participants