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

More inverses? #12

Closed
aplavin opened this issue Feb 11, 2022 · 17 comments
Closed

More inverses? #12

aplavin opened this issue Feb 11, 2022 · 17 comments

Comments

@aplavin
Copy link
Contributor

aplavin commented Feb 11, 2022

Do you consider inverses to two-argument Julia Base functions in scope of this package? For example:

inverse(f::Base.Fix1{typeof(+)}) = Base.Fix2(-, f.x)
inverse(f::Base.Fix1{typeof(^)}) = Base.Fix1(log, f.x)
# and so on

Originally, this came up in JuliaObjects/Accessors.jl#46, but can be useful elsewhere as well.

@oschulz
Copy link
Collaborator

oschulz commented Feb 11, 2022

I think this would fit in InverseFunctions, in principle. How many of these do you think there would be?

@oschulz
Copy link
Collaborator

oschulz commented Feb 11, 2022

I had a closer look at JuliaObjects/Accessors.jl#46 - I think the Fix1/Fix2 inverse defined there would fit nicely in InverseFunctions. And deg2rad and so on we should definitely add as well.

We should change our current definition square(x) = x^2 to const square = Base.Fix2(^,2) while we're at it.

@devmotion any objections?

@devmotion
Copy link
Member

We should change our current definition square(x) = x^2 to const square = Base.Fix2(^,2) while we're at it.

I'm not sure if this should be done. There are other possible choices such as abs2 or involving literal_pow (which x^2 is lowered to usually), so it's not clear to me that your suggestion should be preferred.

@oschulz
Copy link
Collaborator

oschulz commented Feb 11, 2022

I'm not sure if this should be done.

Oh, you're right - sorry, Base.Fix2(^,2) would be slow of course, since it couldn't be optimized as usual. But maybe we should make our square an alias of abs2 or so?

I any case we could certainly define things like

inverse(f::Base.Fix2{typeof(^)}) = Base.Fix2(^, inv(f.x))

right? The list of inverses proposed in JuliaObjects/Accessors.jl#46 is quite finite, I don't think they would bloat InverseFunctions too much (and can't be defined outside of it without type piracy, of course).

@sethaxen
Copy link

square and abs2 are not equivalent for non-real numbers.

@oschulz
Copy link
Collaborator

oschulz commented Feb 11, 2022

square and abs2 are not equivalent for non-real numbers.

True. Maybe best we keep square for now.

@devmotion
Copy link
Member

For real numbers, I think the arguably most natural choice would be

leftinverse(::typeof(sqrt)) = abs2

I am aware that it does not work in general (and I was sure someone would mention it 😄) but it would be nice to be able to use it for Real inputs. I mean, in general functions may depend on the input types (e.g. sqrt of negative real numbers throws an error whereas it works for complex numbers with negative real part), so why shouldn't we have a definition for the Real case specifically?

@oschulz
Copy link
Collaborator

oschulz commented Feb 11, 2022

leftinverse(::typeof(sqrt)) = abs2

Maybe we should continue that one on #10? :-)

@oschulz
Copy link
Collaborator

oschulz commented Feb 11, 2022

I like the symmetry of

inverse(f::Base.Fix1{typeof(^)}) = Base.Fix1(log, f.x))
inverse(f::Base.Fix2{typeof(^)}) = Base.Fix2(^, inv(f.x))

and so on for +, -, * and / we'd get from @aplavin's proposal.

@sethaxen
Copy link

I am aware that it does not work in general (and I was sure someone would mention it 😄) but it would be nice to be able to use it for Real inputs. I mean, in general functions may depend on the input types (e.g. sqrt of negative real numbers throws an error whereas it works for complex numbers with negative real part), so why shouldn't we have a definition for the Real case specifically?

This raises the point that it might not be sufficient to know the name of the function to define the inverse. One might also need to know the domain, as defined by the type of the input. Of course, some inputs may not store their constraints in their type, so the inverse may depend on the value itself.

@sethaxen
Copy link

Or put another way, different methods of the same function may require different inverses, and those inverses may not be different methods of the same function.

@aplavin
Copy link
Contributor Author

aplavin commented Feb 11, 2022

In the context of Accessors.jl, the #9 version probably makes most sense.

sqrt(4) == 2
sqrt(-4)  # throws
(@set sqrt(x) = 2) == 4
(@set sqrt(x) = -2)  # better if throws as well

But this loses the nice symmetry, and requires defining separate function for these inverses to perform argument checks.

@oschulz
Copy link
Collaborator

oschulz commented Feb 11, 2022

This raises the point that it might not be sufficient to know the name of the function to define the inverse. One might also need to know the domain, as defined by the type of the input.

That's a whole different level, though. :-)

@devmotion
Copy link
Member

I like the symmetry of

inverse(f::Base.Fix1{typeof(^)}) = Base.Fix1(log, f.x))
inverse(f::Base.Fix2{typeof(^)}) = Base.Fix2(^, inv(f.x))

The main problem is that this is incorrect and problematic in the same sense as sqrt and square. Even more problematic, even leftinverse and rightinverse would not fix the problem here:

If one considers the maximum support on the real line in all cases, then f = Base.Fix2(^, 2) is a left inverse of g = Base.Fix2(^, 1/2) (f(g(x)) = x for all x >= 0) but not a right inverse (g(f(-1)) != -1). However, this also shows that f = Base.Fix(^, 1/2) is a right inverse of g = Base.Fix(^, 2) but not a left inverse. So neither of the following definitions

inverse(f::Base.Fix2{typeof(^)}) = Base.Fix2(^, inv(f.x)) # incorrect e.g. if `f.x = 2` or `f.x = 1/2`
leftinverse(f::Base.Fix2{typeof(^)}) = Base.Fix2(^, inv(f.x)) # incorrect e.g. if `f.x = 2`
rightinverse(f::Base.Fix2{typeof(^)}) = Base.Fix2(^, inv(f.x)) # incorrect e.g. if `f.x = 1/2`

would be correct in general.

@aplavin
Copy link
Contributor Author

aplavin commented Feb 12, 2022

f = Base.Fix2(^, 2) is a left inverse of g = Base.Fix2(^, 1/2) (f(g(x)) = x for all x >= 0) but not a right inverse (g(f(-1)) != -1)

What definition of the right inverse do you use here?
For g:X->Y the right inverse is f:Y->X so that for all y in Y we get g(f(y)) = y. The -1 example is not in Y, and as I understand the right inverse definition doesn't "care" what happens there.

Still I agree it may be cleaner to throw an exception in f when the argument is outside of the range of the corresponding g: see my previous comment. This requires defining a few separate functions to only serve as inverses, but maybe there is nothing really wrong with it...

@devmotion
Copy link
Member

Ah yes, sorry, I messed it up. In general, it is highly dependent on the choice of domain and codomain though.

g: [0, Inf) -> R, x |-> x^(1/2) is a right inverse of f: R -> [0, Inf), x |-> x^2 since f(g(x)) = x for all x in [0, Inf). But g is not a left inverse of f since g(f(-1)) = 1 != -1.

Similarly, g: R -> [0, Inf), x |-> x^2 is a left inverse of f: [0, Inf) -> R, x |-> x^(1/2) since g(f(x)) = x for all x in [0, Inf). But g is not a right inverse of f since f(g(-1)) = 1 != -1.

We can consider other domains and codomains as well. Eg., the inverse of f: [0, Inf) -> [0, Inf), x |-> x^(1/2) is g: [0, Inf) -> [0, Inf), x |-> x^2, and vice versa.

@oschulz
Copy link
Collaborator

oschulz commented Feb 12, 2022

The main problem is that this is incorrect and problematic in the same sense as sqrt and square

Hmhm, we may need to do something in the direction of #8. Having invertibleat (or another solution) would allow us to have such inverses (like inverse(f::Base.Fix1{typeof(^)})) - they do make sense in general, after all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants