-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add loglogistic
, logitexp
, log1mlogistic
and logit1mexp
#82
Add loglogistic
, logitexp
, log1mlogistic
and logit1mexp
#82
Conversation
This takes advantage of `LogExpFunctions`'s accurate implementations of `log1pexp` and `log1mexp`, combined with negation in the log-odds domain to provide more accurate and less expensive implementations of the function compositions.
src/basicfuns.jl
Outdated
|
||
Its inverse is the [`loglogistic`](@ref) function. | ||
""" | ||
logitexp(x::Real) = x - log1mexp(x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be done more carefully to avoid loss of precision when both terms are approximately equal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this can only be resolved by improving the ulp error of logexpm1
around log(2)
, I see no reason for this to gate merging of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could check if the argument is in a neighborhood of -log(2), and then just use a first-order approximation. My quick calculations suggest that the local derivative is 2 but someone should check this.
i.e. properly handle `Integer` and `Rational`
This enables the desired interface of `f(x::Real)`
src/basicfuns.jl
Outdated
loglogistic(x::Real) = _loglogistic(x) | ||
_loglogistic(x::Real) = -log1pexp(-x) | ||
_loglogistic(x::Union{Integer, Rational}) = _loglogistic(float(x)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure whether unsigned integers are worth any consideration given that you will run into problems with them in many (most?) calculations and definitions in JuliaStats, JuliaMath, etc. typically do not handle them in a special way - but if we care then it would be easier to just define
loglogistic(x::Real) = _loglogistic(x) | |
_loglogistic(x::Real) = -log1pexp(-x) | |
_loglogistic(x::Union{Integer, Rational}) = _loglogistic(float(x)) | |
loglogistic(x::Real) = -log1pexp(-float(x)) |
This would cover also cases such as e.g. unitful integer numbers that are not covered by Union{Integer, Rational}
and for floating points such as x::Float64
or x::Float32
it would be equivalent to -log1pexp(-x)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opted for the float
solution, as this is simplest and, furthermore, replicates the handling of integer and rational arguments in Julia Base
.
I'm still not sure whether unsigned integers are worth any consideration
SpecialFunctions.jl (another package I contributed to) converts most arguments via float
, hence, it handles both the unsigned integer cases as well as the signed integer (i.e. typemin(T<:Signed)
) edge case.
Use of float
to convert integers and rationals to a floating point type for use in special functions is common throughout Base
. Unless integers and rationals are converted, then a function interface defined as x::Real
cannot be claimed. On the other hand, if the intention is to guarantee reasonable results only for floating point numbers, then this should clearly be stated in the package description. However, I am of the opinion that this package should follow the behavior of Base
and SpecialFunctions
, whereby integers and rationals are converted to floating point numbers internally.
Unless conversion is enforced, this leads to some obviously incorrect results, e.g., logistic(typemin(Int)) == 1.0
, but, clearly, should be 0.0
.
ChainRulesCore.@scalar_rule(loglogistic(x::Real), (logistic(-x),)) | ||
ChainRulesCore.@scalar_rule(log1mlogistic(x::Real), (-logistic(x),)) | ||
ChainRulesCore.@scalar_rule(logitexp(x::Real), (inv(1 - exp(x)),)) | ||
ChainRulesCore.@scalar_rule(logit1mexp(x::Real), (-inv(1 - exp(x)),)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO these are not needed - AD handles the functions already fine since rules for log1pexp
etc. are defined.
ChainRulesCore.@scalar_rule(loglogistic(x::Real), (logistic(-x),)) | |
ChainRulesCore.@scalar_rule(log1mlogistic(x::Real), (-logistic(x),)) | |
ChainRulesCore.@scalar_rule(logitexp(x::Real), (inv(1 - exp(x)),)) | |
ChainRulesCore.@scalar_rule(logit1mexp(x::Real), (-inv(1 - exp(x)),)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As directed, these definitions have been removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking through the issues, #36 seems to indicate that these functions should support ChainRulesCore
forward and reverse rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's just not correct. These new functions are supported by ChainRulesCore-based AD systems just fine without any additional definitions. See https://juliadiff.org/ChainRulesCore.jl/stable/rule_author/which_functions_need_rules.html for some additional information on this matter.
As
please let me know of any additional changes which must be made for this PR to be merged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, as far as I can see all requests have been addressed. Thanks for the fine work!
May we clarify: include or exclude Last item to adjust, then can merge. |
It would be best to not let this bit rot. Moreover, it is easiest to make changes when it is in recent memory. @devmotion could you clarify with @tpapp whether to include or exclude ChainRulesCore forward/reverse rules? Once clarified, I can either revert the last commit or leave it in place. Should I then assume that there would be no further obstacles to merging? |
Feel free to remove them. Thanks for the ping! |
This reverts commit 1b0f914.
Reverted |
lim2 = T === Float16 ? -10.0 : -37.0 | ||
xs = T[Inf, -Inf, 0.0, lim1, lim2] | ||
for x in xs | ||
@test loglogistic(x) == log(logistic(x)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we test multiple types T
, I think it would be good to also check that - as desired - the results are of type T
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. Comprehensive return type tests have been added for the 4 functions included in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anything else, @devmotion?
This takes advantage of
LogExpFunctions
's accurate implementations oflog1pexp
andlog1mexp
, combined with negation in the log-odds domain to provide more accurate and less expensive implementations of the function compositions.As recommended here, I am submitting these.
Precedent for
loglogistic
andlog1mlogistic
is their existence in Stan; it is natural to include their inverses.