From e8d075a7bf80837c74671e76272e67354a91e462 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sat, 16 Nov 2024 02:35:49 -0500 Subject: [PATCH 01/13] add logabstanh --- src/basicfuns.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index e013adf..faf144b 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -149,6 +149,31 @@ end """ $(SIGNATURES) +Return `log(abs(tanh(x)))`, carefully evaluated without intermediate calculation of `tanh(x)`. + +The implementation ensures `logabstanh(-x) = logabstanh(x)`. +""" +function logabstanh(x::Real) + return log1p(-2/((exp(2abs(x))+1))) +end +function logabstanh(x::Float32) + abs_x = abs(x) + if abs_x < 0.0625f0 + return log(abs_x) - x*x*(1f0/3) + end + return log1p(-2/((exp(2abs_x)+1))) +end +function logabstanh(x::Float64) + abs_x = abs(x) + if abs_x < 0x1p-5 + return log(abs_x) + evalpoly(x*x, (0, -1/3, 7/90, -62/2835)) + end + return log1p(-2/((exp(2abs_x)+1))) +end + +""" +$(SIGNATURES) + Return `log(1+x^2)` evaluated carefully for `abs(x)` very small or very large. """ log1psq(x::Real) = log1p(abs2(x)) From 528545d222bdc033282b1006755f1da3837d7521 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sat, 16 Nov 2024 02:40:26 -0500 Subject: [PATCH 02/13] export --- src/LogExpFunctions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LogExpFunctions.jl b/src/LogExpFunctions.jl index 739e005..bc5fefd 100644 --- a/src/LogExpFunctions.jl +++ b/src/LogExpFunctions.jl @@ -8,7 +8,7 @@ import LinearAlgebra export xlogx, xlogy, xlog1py, xexpx, xexpy, logistic, logit, log1psq, log1pexp, log1mexp, log2mexp, logexpm1, softplus, invsoftplus, log1pmx, logmxp1, logaddexp, logsubexp, logsumexp, logsumexp!, softmax, - softmax!, logcosh, logabssinh, cloglog, cexpexp, + softmax!, logcosh, logabssinh, logabstanh, cloglog, cexpexp, loglogistic, logitexp, log1mlogistic, logit1mexp # expm1(::Float16) is not defined in older Julia versions, From 5cf00585a60bd62801271376ef7685a8d4093e03 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sat, 16 Nov 2024 09:36:03 -0500 Subject: [PATCH 03/13] Update src/basicfuns.jl Co-authored-by: David Widmann --- src/basicfuns.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index faf144b..47c8943 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -154,7 +154,7 @@ Return `log(abs(tanh(x)))`, carefully evaluated without intermediate calculation The implementation ensures `logabstanh(-x) = logabstanh(x)`. """ function logabstanh(x::Real) - return log1p(-2/((exp(2abs(x))+1))) + return log1p(-2/(exp(2*abs(x))+1)) end function logabstanh(x::Float32) abs_x = abs(x) From 95639d1029bbca72210a2e8af9b6a12c2a308bce Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sat, 16 Nov 2024 09:36:11 -0500 Subject: [PATCH 04/13] Update src/basicfuns.jl Co-authored-by: David Widmann --- src/basicfuns.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index 47c8943..689ae22 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -159,7 +159,7 @@ end function logabstanh(x::Float32) abs_x = abs(x) if abs_x < 0.0625f0 - return log(abs_x) - x*x*(1f0/3) + return log(abs_x) - x^2/3 end return log1p(-2/((exp(2abs_x)+1))) end From 3364d2035360fe4dbba4ff044f57c0d952c2c632 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sat, 16 Nov 2024 10:24:52 -0500 Subject: [PATCH 05/13] Update src/basicfuns.jl Co-authored-by: David Widmann --- src/basicfuns.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index 689ae22..9c5bf34 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -161,7 +161,7 @@ function logabstanh(x::Float32) if abs_x < 0.0625f0 return log(abs_x) - x^2/3 end - return log1p(-2/((exp(2abs_x)+1))) + return log1p(-2/(exp(2*abs_x)+1)) end function logabstanh(x::Float64) abs_x = abs(x) From 0b6d9f9c11a47f6aebce1188f5642bf819c679b5 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Sat, 16 Nov 2024 10:24:57 -0500 Subject: [PATCH 06/13] Update src/basicfuns.jl Co-authored-by: David Widmann --- src/basicfuns.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index 9c5bf34..e2828f1 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -168,7 +168,7 @@ function logabstanh(x::Float64) if abs_x < 0x1p-5 return log(abs_x) + evalpoly(x*x, (0, -1/3, 7/90, -62/2835)) end - return log1p(-2/((exp(2abs_x)+1))) + return log1p(-2/(exp(2*abs_x)+1)) end """ From 938ef4530c8f738ab1d8fed4fdccb414a451fe60 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 17 Aug 2025 21:12:16 +0200 Subject: [PATCH 07/13] include `logabstanh` doc string into the docs --- docs/src/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/index.md b/docs/src/index.md index 3c0a7f3..9454e0c 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -16,6 +16,7 @@ logistic logit logcosh logabssinh +logabstanh log1psq log1pexp softplus From b318133ebc2ddc35e3a5f2e0af65b5b6f1ac94e0 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 17 Aug 2025 21:18:30 +0200 Subject: [PATCH 08/13] add ChainRules.jl rule for `logabstanh` --- ext/LogExpFunctionsChainRulesCoreExt.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/LogExpFunctionsChainRulesCoreExt.jl b/ext/LogExpFunctionsChainRulesCoreExt.jl index 397603a..28fe8fe 100644 --- a/ext/LogExpFunctionsChainRulesCoreExt.jl +++ b/ext/LogExpFunctionsChainRulesCoreExt.jl @@ -118,6 +118,7 @@ ChainRulesCore.@scalar_rule(logistic(x::Real), (Ω * (1 - Ω),)) ChainRulesCore.@scalar_rule(logit(x::Real), (inv(x * (1 - x)),)) ChainRulesCore.@scalar_rule(logcosh(x::Real), tanh(x)) ChainRulesCore.@scalar_rule(logabssinh(x::Real), coth(x)) +ChainRulesCore.@scalar_rule(logabstanh(x::Real), inv(cosh(x) * sinh(x))) ChainRulesCore.@scalar_rule(log1psq(x::Real), (2 * x / (1 + x^2),)) ChainRulesCore.@scalar_rule(log1pexp(x::Real), (logistic(x),)) ChainRulesCore.@scalar_rule(log1mexp(x::Real), (-exp(x - Ω),)) From 4c17c3b3e13a99979e97355ce60b5b1eeee838a4 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 17 Aug 2025 21:19:46 +0200 Subject: [PATCH 09/13] test ChainRules.jl rule for `logabstanh` --- test/chainrules.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/chainrules.jl b/test/chainrules.jl index f844d0b..c19ae93 100644 --- a/test/chainrules.jl +++ b/test/chainrules.jl @@ -64,6 +64,8 @@ test_rrule(logcosh, x) test_frule(logabssinh, x) test_rrule(logabssinh, x) + test_frule(logabstanh, x) + test_rrule(logabstanh, x) end @testset "log1pexp" begin From 0207a15ade23528ef15356f0bdd2b56c0eba8657 Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 17 Aug 2025 21:44:23 +0200 Subject: [PATCH 10/13] test `logabstanh`: basicfuns --- test/basicfuns.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/basicfuns.jl b/test/basicfuns.jl index c16e2d1..cc5bccd 100644 --- a/test/basicfuns.jl +++ b/test/basicfuns.jl @@ -93,7 +93,7 @@ end end end -@testset "logcosh and logabssinh" begin +@testset "logcosh and logabssinh and logabstanh" begin for x in (randn(), randn(Float32)) @test @inferred(logcosh(x)) isa typeof(x) @test logcosh(x) ≈ log(cosh(x)) @@ -101,21 +101,28 @@ end @test @inferred(logabssinh(x)) isa typeof(x) @test logabssinh(x) ≈ log(abs(sinh(x))) @test logabssinh(-x) == logabssinh(x) + @test @inferred(logabstanh(x)) isa typeof(x) + @test logabstanh(x) ≈ log(abs(tanh(x))) + @test logabstanh(-x) == logabstanh(x) end # special values for x in (-Inf, Inf, -Inf32, Inf32) @test @inferred(logcosh(x)) === oftype(x, Inf) @test @inferred(logabssinh(x)) === oftype(x, Inf) + @test @inferred(logabstanh(x)) === -oftype(x, 0) end for x in (NaN, NaN32) @test @inferred(logcosh(x)) === x @test @inferred(logabssinh(x)) === x + @test @inferred(logabstanh(x)) === x end - @testset "accuracy of `logcosh`" begin + @testset "accuracy" begin for t in (Float16, Float32, Float64) - @test ulp_error_maximum(logcosh, range(start = t(-3), stop = t(3), length = 1000)) < 3 + ran = range(start = t(-3), stop = t(3), length = 1000) + @test ulp_error_maximum(logcosh, ran) < 3 + @test ulp_error_maximum(logabstanh, ran) < 3 end end end From fc6617d04aee16a81f072bfe2d7a589496626e2d Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 17 Aug 2025 21:29:47 +0200 Subject: [PATCH 11/13] avoid excessive promises in the `logabstanh` doc string --- src/basicfuns.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index 51901bf..500e173 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -218,7 +218,7 @@ end """ $(SIGNATURES) -Return `log(abs(tanh(x)))`, carefully evaluated without intermediate calculation of `tanh(x)`. +Return `log(abs(tanh(x)))`, evaluated carefully. The implementation ensures `logabstanh(-x) = logabstanh(x)`. """ From 39127b6b4ef9fa4cb38b0f5d4136ebba64fb41fd Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 17 Aug 2025 21:30:39 +0200 Subject: [PATCH 12/13] delete the precision-specific methods They can be added back in a future PR as an optimization. --- src/basicfuns.jl | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index 500e173..aeb7656 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -225,20 +225,6 @@ The implementation ensures `logabstanh(-x) = logabstanh(x)`. function logabstanh(x::Real) return log1p(-2/(exp(2*abs(x))+1)) end -function logabstanh(x::Float32) - abs_x = abs(x) - if abs_x < 0.0625f0 - return log(abs_x) - x^2/3 - end - return log1p(-2/(exp(2*abs_x)+1)) -end -function logabstanh(x::Float64) - abs_x = abs(x) - if abs_x < 0x1p-5 - return log(abs_x) + evalpoly(x*x, (0, -1/3, 7/90, -62/2835)) - end - return log1p(-2/(exp(2*abs_x)+1)) -end """ $(SIGNATURES) From 00e2d63176b083278586c75dcfba91f6be97feba Mon Sep 17 00:00:00 2001 From: Neven Sajko Date: Sun, 17 Aug 2025 21:33:39 +0200 Subject: [PATCH 13/13] help `logabstanh` to be accurate near zero --- src/basicfuns.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/basicfuns.jl b/src/basicfuns.jl index aeb7656..bd20aeb 100644 --- a/src/basicfuns.jl +++ b/src/basicfuns.jl @@ -223,7 +223,12 @@ Return `log(abs(tanh(x)))`, evaluated carefully. The implementation ensures `logabstanh(-x) = logabstanh(x)`. """ function logabstanh(x::Real) - return log1p(-2/(exp(2*abs(x))+1)) + a = abs(x) + if 8*a < 3 + log(tanh(a)) + else + log1p(-2/(exp(2*a)+1)) + end end """