JuliaMath / InverseFunctions.jl

Interface for function inversion in Julia
Other
29 stars 8 forks source link

Checking for existence of inverse #13

Closed oschulz closed 2 years ago

oschulz commented 2 years ago

I have a use case that needs to determine if a function has an inverse or not in a type-stable fashion (try/catch won't do). I think such a functionality would be useful in general.

I see two options:

a) Adding a has_inverse(f) function that returns a Singleton. This would be a breaking change, functions that support inverse would now be required to support has_inverse as well.

b) A default implementation inverse(f) = nothing (or missing?). This would be simpler (especially if we add left_inverse and right_inverse as well later on) and non-breaking. On the other hand, code that depends on functions having an inverse would fail in a less obvious way, the root cause would be harder to track (users would get a MethodError: objects of type Missing are not callable instead of a MethodError: no method matching inverse(::typeof(f))). On the other hand, code that uses inverses could check if the inverse is not nothing to ensure the exception points to the right place, e.g via something(inverse(f)).

An example for approach b) would be ChainRulesCore (rrule(f, args...) returns nothing by default).

@devmotion what do you think?

devmotion commented 2 years ago

An alternative would be to define something like

struct NoInverse{F}
    f::F
end

(f::NoInverse)(x) = error("inverse of ", f.f, " is not defined")

inverse(f) = NoInverse(f)
oschulz commented 2 years ago

Oh, this is neat!

devmotion commented 2 years ago

It would be similar to the default NullParameters in SciML (https://github.com/SciML/SciMLBase.jl/blob/9e5ba6f10a0d347d48ba4d2bae4a3c610a589bc1/src/problems/problem_utils.jl#L139-L141) which IIRC was Yingbo's idea at JuliaCon 2019.

oschulz commented 2 years ago

Do we want inverse(f::NoInverse) = f.f or inverse(f::NoInverse) = error(...)?

See #14.

aplavin commented 2 years ago

Still, how to check if a function is invertible? inverse(...) isa NoInverse doesn't really solve this:

julia> inverse(sin ∘ inv)
inv ∘ NoInverse{typeof(sin)}(sin)

julia> inverse(sin ∘ inv) isa NoInverse
false
devmotion commented 2 years ago

IMO that's a bug and generally one should check for NoInverse. I think the example should just return NoInverse{typeof(sin \circ inv)}(sin \circ inv) here. I guess this can be fixed by checking if the inverse of any of the functions in ComposedFunction is NoInverse.

oschulz commented 2 years ago

I guess this can be fixed by checking if the inverse of any of the functions in ComposedFunction is NoInverse.

I agree, the result should be NoInverse{typeof{Composed{...}} (so that it can still be inverted back to the original composed function).

jw3126 commented 2 years ago

This is a very elegant and nice mechanic!

oschulz commented 2 years ago

Thanks @jw3126 !