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)
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
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
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