Skip to content
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 setinverse #25

Merged
merged 6 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
```@docs
inverse
NoInverse
setinverse
```

## Test utility
Expand All @@ -13,8 +14,9 @@ NoInverse
InverseFunctions.test_inverse
```

## Additional functions
## Additional functionality

```@docs
InverseFunctions.square
InverseFunctions.FunctionWithInverse
Copy link
Member

Choose a reason for hiding this comment

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

Since it's not exported and not intended to be constructed directly, I wonder if it should be omitted from the docs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thought people might want to have the option to dispatch on it, in low-level uses cases and so on, that's why I made it party of the official API.

Copy link
Member

Choose a reason for hiding this comment

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

I assumed that this was the motivation. I wonder, however, how often one would actually want to dispatch on FunctionWithInverse. I assume for many cases it would be simpler to just use unwrap_f (or something equivalent of the same name) and inverse - similar to the setinverse case in this PR.

Copy link
Collaborator Author

@oschulz oschulz Sep 25, 2022

Choose a reason for hiding this comment

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

I wonder, however, how often one would actually want to dispatch on FunctionWithInverse.

We may actually have occasion to do that in the pushforward measure in MeasureBase (in the context of JuliaMath/MeasureBase.jl#89, since PushforwardMeasure stores both forward and inverse function for performance reasons). Not sure yet - but it's an interesting option (we may want to extract forward and inverse from FunctionWithInverse). So there's at least one possible use case, so there may be more. And it doesn't cost us anything, I wouldn't expect FunctionWithInverse to change in breaking ways in the future.

Copy link
Member

Choose a reason for hiding this comment

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

since PushforwardMeasure stores both forward and inverse function for performance reasons

To obtain these you don't have to specialize on FunctionWithInverse though. You could just store _unwrap_f(f) and inverse(f) if an arbitrary f is provided.

```
1 change: 1 addition & 0 deletions src/InverseFunctions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using Test

include("functions.jl")
include("inverse.jl")
include("setinverse.jl")
include("test.jl")

end # module
54 changes: 54 additions & 0 deletions src/setinverse.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# This file is a part of InverseFunctions.jl, licensed under the MIT License (MIT).


"""
struct FunctionWithInverse{F,InvF} <: Function

A function with an inverse.

Do not construct directly, use [`setinverse(f, invf)`](@ref) instead.
"""
struct FunctionWithInverse{F,InvF} <: Function
f::F
invf::InvF
end


(f::FunctionWithInverse)(x) = f.f(x)

inverse(f::FunctionWithInverse) = setinverse(f.invf, f.f)


"""
setinverse(f, invf)

Return a function that behaves like `f` and uses `invf` as its inverse.

Useful in cases where no inverse is defined for `f` or to set an inverse that
is only valid within a given context, e.g. only for a limited argument
range that is guaranteed by the use case but not in general.

For example, `asin` is not a valid inverse of `sin` for arbitrary arguments
of `sin`, but can be a valid inverse if the use case guarantees that the
argument of `sin` will always be within `-π` and `π`:

```jldoctest
julia> foo = setinverse(sin, asin);

julia> x = π/3;

julia> foo(x) == sin(x)
true

julia> inverse(foo)(foo(x)) ≈ x
true

julia> inverse(foo) === setinverse(asin, sin)
true
```
"""
setinverse(f, invf) = FunctionWithInverse(_unwrap_f(f), _unwrap_f(invf))
export setinverse

_unwrap_f(f) = f
_unwrap_f(f::FunctionWithInverse) = f.f
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Documenter
Test.@testset "Package InverseFunctions" begin
include("test_functions.jl")
include("test_inverse.jl")
include("test_setinverse.jl")

# doctests
Documenter.DocMeta.setdocmeta!(
Expand Down
15 changes: 15 additions & 0 deletions test/test_setinverse.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file is a part of InverseFunctions.jl, licensed under the MIT License (MIT).

using Test
using InverseFunctions


@testset "setinverse" begin
@test @inferred(setinverse(sin, asin)) === InverseFunctions.FunctionWithInverse(sin, asin)
@test @inferred(setinverse(sin, setinverse(asin, sqrt))) === InverseFunctions.FunctionWithInverse(sin, asin)
@test @inferred(setinverse(setinverse(sin, sqrt), asin)) === InverseFunctions.FunctionWithInverse(sin, asin)
@test @inferred(setinverse(setinverse(sin, asin), setinverse(asin, sqrt))) === InverseFunctions.FunctionWithInverse(sin, asin)

InverseFunctions.test_inverse(setinverse(sin, asin), π/4)
InverseFunctions.test_inverse(setinverse(asin, sin), 0.5)
end