Skip to content

Commit 980e351

Browse files
committed
add a warning when not converged + remove atol in tests + extra tests in slow convergence zones.
1 parent 7bc16eb commit 980e351

2 files changed

Lines changed: 54 additions & 32 deletions

File tree

ext/SpecialFunctionsChainRulesCoreExt.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ end
320320
# the auxiliary variable f, the continued-fraction coefficients a_n, b_n, and
321321
# their partial derivatives w.r.t. p (≡ a) and q (≡ b). See Boik & Robinson-Cox (1999).
322322

323-
@inline function _Kfun(logx::T, log1mx::T, p::T, q::T, logbetapq::T) where {T}
323+
function _Kfun(logx::T, log1mx::T, p::T, q::T, logbetapq::T) where {T}
324324
# K(x;p,q) = x^p (1-x)^{q-1} / (p * B(p,q)) computed in log-space for stability
325325
# logx = log(x), log1mx = log(1-x), precomputed
326326
return exp(p * logx + (q - 1) * log1mx - log(p) - logbetapq)
@@ -440,11 +440,9 @@ end
440440
return dan * Xpp + an * dXpp + dbn * Xp + bn * dXp
441441
end
442442

443-
function _beta_inc_grad(a::T, b::T, x::T) where {T<:Union{Float64, Float32}}
443+
function _beta_inc_grad(a::T, b::T, x::T; maxapp::Int=200, minapp::Int=3) where {T<:Union{Float64, Float32}}
444444

445445
# 0) Previously keyword arguments:
446-
maxapp=200
447-
minapp=3
448446
ϵ=eps(T)*T(1e4)
449447

450448
# Compute I_x(a,b) and partial derivatives (∂I/∂a, ∂I/∂b, ∂I/∂x)
@@ -500,7 +498,7 @@ function _beta_inc_grad(a::T, b::T, x::T) where {T<:Union{Float64, Float32}}
500498
p2q2 = p + 2q - 2 # p + 2*q - 2
501499
a1 = p * f * (q - 1) / (q * (p + 1)) # a₁ coefficient
502500
da1_dp = -a1 / (p + 1) # ∂a₁/∂p
503-
da1_dq = a1 / (q - 1) # ∂a₁/∂q
501+
da1_dq = pfq / (p + 1) # ∂a₁/∂q, including the removable q == 1 case
504502

505503
# Update continuants.
506504
An, an, Bn = _nextapp1(f, p, q)
@@ -539,6 +537,7 @@ function _beta_inc_grad(a::T, b::T, x::T) where {T<:Union{Float64, Float32}}
539537

540538
# 6) Main CF loop (n from 2): update continuants, scale, form current approximant Cn=A_n/B_n
541539
# and its derivatives to update I and ∂I/∂(p,q). Stop on relative convergence of all.
540+
converged = false
542541
for n=2:maxapp
543542

544543
# Update continuants.
@@ -593,7 +592,10 @@ function _beta_inc_grad(a::T, b::T, x::T) where {T<:Union{Float64, Float32}}
593592
rI = (Ixpqn - Ixpq) / denomI
594593
rp = (dI_dp - dI_dp_prev) / denomp
595594
rq = (dI_dq - dI_dq_prev) / denomq
596-
-ϵ < rI < ϵ && -ϵ < rp < ϵ && -ϵ < rq < ϵ && break
595+
if -ϵ < rI < ϵ && -ϵ < rp < ϵ && -ϵ < rq < ϵ
596+
converged = true
597+
break
598+
end
597599
end
598600
Ixpq = Ixpqn
599601
dI_dp_prev = dI_dp
@@ -614,6 +616,8 @@ function _beta_inc_grad(a::T, b::T, x::T) where {T<:Union{Float64, Float32}}
614616
dBp_dq = dBn_dq
615617
end
616618

619+
converged || @warn "_beta_inc_grad reached maxapp approximants before convergence" a b x maxapp minapp
620+
617621
# 7) Undo tail-swap if applied; ∂I/∂x is the pdf at original (a,b,x)
618622
if swap
619623
return -dI_dq, -dI_dp, dx
@@ -648,4 +652,4 @@ ChainRulesCore.@scalar_rule(
648652
(dIa * inv_dIx, dIb * inv_dIx, -inv_dIx),
649653
)
650654

651-
end # module
655+
end # module

test/chainrules.jl

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -185,31 +185,31 @@
185185
# Direct derivative checks without FD: ∂I/∂x equals beta pdf
186186
pdf = x^(a - 1) * (1 - x)^(b - 1) / beta(a, b)
187187
_, Δx = frule((NoTangent(), 0.0, 0.0, 1.0), beta_inc, a, b, x)
188-
@test isapprox(Δx[1], pdf; rtol=1e-12, atol=1e-12)
188+
@test isapprox(Δx[1], pdf; rtol=1e-12)
189189

190190
# Symmetry check: ∂I/∂a(a,b,x) = -∂I/∂b(b,a,1-x)
191191
_, Δa = frule((NoTangent(), 1.0, 0.0, 0.0), beta_inc, a, b, x)
192192
_, Δb_sw = frule((NoTangent(), 0.0, 1.0, 0.0), beta_inc, b, a, 1 - x)
193-
@test isapprox(Δa[1], -Δb_sw[1]; rtol=1e-10, atol=1e-12)
193+
@test isapprox(Δa[1], -Δb_sw[1]; rtol=1e-10)
194194

195195
# Composition identity f(g(p)) = p: forward-mode differential equals 1 for dp, 0 for da,db
196196
p = first(beta_inc(a, b, x))
197197
x_inv, _ = beta_inc_inv(a, b, p)
198198
# Check primal composition
199199
p_roundtrip = first(beta_inc(a, b, x_inv))
200-
@test isapprox(p_roundtrip, p; rtol=1e-12, atol=1e-12)
200+
@test isapprox(p_roundtrip, p; rtol=1e-12)
201201
# Forward through g then f: dp
202202
_, Δx_inv_dp = frule((NoTangent(), 0.0, 0.0, 1.0), beta_inc_inv, a, b, p)
203203
_, Δp_from_dp = frule((NoTangent(), 0.0, 0.0, Δx_inv_dp[1]), beta_inc, a, b, x_inv)
204-
@test isapprox(Δp_from_dp[1], 1.0; rtol=1e-9, atol=1e-12)
204+
@test isapprox(Δp_from_dp[1], 1.0; rtol=1e-9)
205205
# Forward da
206206
_, Δx_inv_da = frule((NoTangent(), 1.0, 0.0, 0.0), beta_inc_inv, a, b, p)
207207
_, Δp_from_da = frule((NoTangent(), 1.0, 0.0, Δx_inv_da[1]), beta_inc, a, b, x_inv)
208-
@test isapprox(Δp_from_da[1], 0.0; rtol=1e-9, atol=1e-12)
208+
@test iszero(Δp_from_da[1])
209209
# Forward db
210210
_, Δx_inv_db = frule((NoTangent(), 0.0, 1.0, 0.0), beta_inc_inv, a, b, p)
211211
_, Δp_from_db = frule((NoTangent(), 0.0, 1.0, Δx_inv_db[1]), beta_inc, a, b, x_inv)
212-
@test isapprox(Δp_from_db[1], 0.0; rtol=1e-9, atol=1e-12)
212+
@test isapprox(Δp_from_db[1], 0.0; rtol=1e-9)
213213

214214
# Reverse-mode chain for composition: pullback through f then g
215215
# Pullback of f at (a,b,x_inv)
@@ -221,9 +221,9 @@
221221
ā_total = āf + āg
222222
b̄_total = b̄f + b̄g
223223
p̄_total = p̄g
224-
@test isapprox(ā_total, 0.0; rtol=1e-10, atol=1e-12)
225-
@test isapprox(b̄_total, 0.0; rtol=1e-10, atol=1e-12)
226-
@test isapprox(p̄_total, 1.0; rtol=1e-9, atol=1e-12)
224+
@test isapprox(ā_total, 0.0; rtol=1e-10)
225+
@test isapprox(b̄_total, 0.0; rtol=1e-10)
226+
@test isapprox(p̄_total, 1.0; rtol=1e-9)
227227
end
228228

229229
@testset "incomplete beta: basic test_frule/test_rrule" begin
@@ -267,11 +267,29 @@
267267
# Float32 promotion sanity (lightweight)
268268
a32 = 1.5f0; b32 = 2.25f0; x32 = 0.3f0
269269
# Finite-difference checks for Float32 are noisier; use looser tolerances
270-
test_frule(beta_inc, a32, b32, x32; rtol=5e-4, atol=1e-6)
271-
test_rrule(beta_inc, a32, b32, x32; rtol=5e-4, atol=1e-6)
270+
test_frule(beta_inc, a32, b32, x32; rtol=5e-4)
271+
test_rrule(beta_inc, a32, b32, x32; rtol=5e-4)
272272
p32 = first(beta_inc(a32, b32, x32))
273-
test_frule(beta_inc_inv, a32, b32, p32; rtol=5e-4, atol=1e-6)
274-
test_rrule(beta_inc_inv, a32, b32, p32; rtol=5e-4, atol=1e-6)
273+
test_frule(beta_inc_inv, a32, b32, p32; rtol=5e-4)
274+
test_rrule(beta_inc_inv, a32, b32, p32; rtol=5e-4)
275+
276+
@testset "_beta_inc_grad sensitive cases" begin
277+
ext = Base.get_extension(SpecialFunctions, :SpecialFunctionsChainRulesCoreExt)
278+
@test ext !== nothing
279+
280+
for (a, b, x) in (
281+
(2.0, 1.0, 0.25), # internal q == 1 without tail swap
282+
(1.0, 2.0, 0.75), # internal q == 1 after tail swap
283+
(1e-6, 2.0, 0.01), # tiny/imbalanced left-tail shape
284+
(2.0, 1e-6, 0.99), # tiny/imbalanced right-tail shape
285+
(1000.0, 1000.0, 0.5), # large central shape
286+
)
287+
@test all(isfinite, ext._beta_inc_grad(a, b, x))
288+
end
289+
290+
grad = @test_logs (:warn, "_beta_inc_grad reached maxapp approximants before convergence") ext._beta_inc_grad(1e6, 1e6, 0.5)
291+
@test all(isfinite, grad)
292+
end
275293
end
276294

277295
@testset "4-arg beta_inc identities (y = 1 - x)" begin
@@ -288,40 +306,40 @@
288306
# Primal consistency: 4-arg matches 3-arg when y = 1 - x
289307
p3, q3 = beta_inc(a, b, x)
290308
p4, q4 = beta_inc(a, b, x, y)
291-
@test isapprox(p4, p3; rtol=1e-12, atol=1e-12)
292-
@test isapprox(q4, q3; rtol=1e-12, atol=1e-12)
309+
@test isapprox(p4, p3; rtol=1e-12)
310+
@test isapprox(q4, q3; rtol=1e-12)
293311

294312
# Analytical pdf
295313
pdf = x^(a - 1) * (1 - x)^(b - 1) / beta(a, b)
296314

297315
# Constrained x-variation: dx = 1, dy = -1 => dp = 2 * pdf, dq = -dp
298316
_, Δxy = frule((NoTangent(), 0.0, 0.0, 1.0, -1.0), beta_inc, a, b, x, y)
299-
@test isapprox(Δxy[1], 2 * pdf; rtol=1e-11, atol=1e-12)
300-
@test isapprox(Δxy[2], -Δxy[1]; rtol=1e-11, atol=1e-12)
317+
@test isapprox(Δxy[1], 2 * pdf; rtol=1e-11)
318+
@test isapprox(Δxy[2], -Δxy[1]; rtol=1e-11)
301319

302320
# Parameter derivatives should match 3-arg ones
303321
_, Δa3 = frule((NoTangent(), 1.0, 0.0, 0.0), beta_inc, a, b, x)
304322
_, Δb3 = frule((NoTangent(), 0.0, 1.0, 0.0), beta_inc, a, b, x)
305323
_, Δa4 = frule((NoTangent(), 1.0, 0.0, 0.0, 0.0), beta_inc, a, b, x, y)
306324
_, Δb4 = frule((NoTangent(), 0.0, 1.0, 0.0, 0.0), beta_inc, a, b, x, y)
307-
@test isapprox(Δa4[1], Δa3[1]; rtol=1e-11, atol=1e-12)
308-
@test isapprox(Δb4[1], Δb3[1]; rtol=1e-11, atol=1e-12)
325+
@test isapprox(Δa4[1], Δa3[1]; rtol=1e-11)
326+
@test isapprox(Δb4[1], Δb3[1]; rtol=1e-11)
309327

310328
# Reverse-mode: compare pullbacks for 3-arg vs constrained 4-arg
311329
_, pb3 = rrule(beta_inc, a, b, x)
312330
_, ā3, b̄3, x̄3 = pb3((1.0, 0.0))
313331
_, pb4 = rrule(beta_inc, a, b, x, y)
314332
_, ā4, b̄4, x̄4, ȳ4 = pb4((1.0, 0.0))
315-
@test isapprox(ā4, ā3; rtol=1e-11, atol=1e-12)
316-
@test isapprox(b̄4, b̄3; rtol=1e-11, atol=1e-12)
333+
@test isapprox(ā4, ā3; rtol=1e-11)
334+
@test isapprox(b̄4, b̄3; rtol=1e-11)
317335
# Unconstrained pullbacks should satisfy x̄4 ≈ x̄3 and ȳ4 ≈ -x̄3
318-
@test isapprox(x̄4, x̄3; rtol=1e-11, atol=1e-12)
319-
@test isapprox(ȳ4, -x̄3; rtol=1e-11, atol=1e-12)
336+
@test isapprox(x̄4, x̄3; rtol=1e-11)
337+
@test isapprox(ȳ4, -x̄3; rtol=1e-1)
320338
# Effective pullback along the constraint y = 1 - x equals 2*x̄3
321339
x̄_eff = x̄4 - ȳ4
322-
@test isapprox(x̄_eff, 2 * x̄3; rtol=1e-11, atol=1e-12)
340+
@test isapprox(x̄_eff, 2 * x̄3; rtol=1e-11)
323341
end
324342
end
325343

326344
end
327-
end
345+
end

0 commit comments

Comments
 (0)