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 4 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
57 changes: 57 additions & 0 deletions src/setinverse.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# This file is a part of InverseFunctions.jl, licensed under the MIT License (MIT).


"""
struct FunctionWithInverse{F,InvF}::Function
oschulz marked this conversation as resolved.
Show resolved Hide resolved

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) = FunctionWithInverse(f.invf, f.f)
oschulz marked this conversation as resolved.
Show resolved Hide resolved


"""
setinverse(f, invf)::Function
oschulz marked this conversation as resolved.
Show resolved Hide resolved

Returns a function that behaves like `f` and uses `invf` it implement its
inverse.
oschulz marked this conversation as resolved.
Show resolved Hide resolved

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. for only for a limited argument
oschulz marked this conversation as resolved.
Show resolved Hide resolved
range that is guaranteed by the use case but not in general.

For example, `asin` not is a valid inverse of `sin` for arbitrary arguments
oschulz marked this conversation as resolved.
Show resolved Hide resolved
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
```
"""
function setinverse end
export setinverse

setinverse(f, invf) = FunctionWithInverse(f, invf)
setinverse(f::FunctionWithInverse, invf) = FunctionWithInverse(f.f, invf)
setinverse(f, invf::FunctionWithInverse) = FunctionWithInverse(f, invf.f)
setinverse(f::FunctionWithInverse, invf::FunctionWithInverse) = FunctionWithInverse(f.f, invf.f)
oschulz marked this conversation as resolved.
Show resolved Hide resolved
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