From dd6e408ed9f33e517329f5715f2e8496f1f0e9ad Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Wed, 28 Sep 2022 14:08:14 +0200 Subject: [PATCH] Add setinverse (#25) Co-authored-by: Chad Scherrer Co-authored-by: David Widmann --- docs/src/api.md | 4 ++- src/InverseFunctions.jl | 1 + src/setinverse.jl | 54 +++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + test/test_setinverse.jl | 15 ++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/setinverse.jl create mode 100644 test/test_setinverse.jl diff --git a/docs/src/api.md b/docs/src/api.md index fecb4b7..e34de90 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -5,6 +5,7 @@ ```@docs inverse NoInverse +setinverse ``` ## Test utility @@ -13,8 +14,9 @@ NoInverse InverseFunctions.test_inverse ``` -## Additional functions +## Additional functionality ```@docs InverseFunctions.square +InverseFunctions.FunctionWithInverse ``` diff --git a/src/InverseFunctions.jl b/src/InverseFunctions.jl index c22608e..0c5438b 100644 --- a/src/InverseFunctions.jl +++ b/src/InverseFunctions.jl @@ -10,6 +10,7 @@ using Test include("functions.jl") include("inverse.jl") +include("setinverse.jl") include("test.jl") end # module diff --git a/src/setinverse.jl b/src/setinverse.jl new file mode 100644 index 0000000..b027c53 --- /dev/null +++ b/src/setinverse.jl @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index b076cff..58ee692 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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!( diff --git a/test/test_setinverse.jl b/test/test_setinverse.jl new file mode 100644 index 0000000..50b97ef --- /dev/null +++ b/test/test_setinverse.jl @@ -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