-
Notifications
You must be signed in to change notification settings - Fork 8
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
Partial Inverses #39
Partial Inverses #39
Conversation
@@ -2,9 +2,6 @@ name = "InverseFunctions" | |||
uuid = "3587e190-3f89-42d0-90ee-14403ec27112" | |||
version = "0.1.12" | |||
|
|||
[deps] | |||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be removed. Can you revert this?
export r_inv, l_inv, retraction, coretraction | ||
|
||
""" | ||
r_inv(function) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use a more descriptive name and also be consistent with inverse
. I suggest
r_inv(function) | |
right_inverse(function) |
|
||
""" | ||
r_inv(function) | ||
retraction(function) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need an alias? I prefer a simple API and would suggest defining only right_inverse
.
retraction(function) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, right_inverse
and left_inverse
would be best, I think.
r_inv(args...; kwargs...) = inverse(args...; kwargs...) | ||
|
||
""" | ||
l_inv(function) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same hee, I think we should use only the name
l_inv(function) | |
left_inverse(function) |
|
||
""" | ||
l_inv(function) | ||
coretraction(function) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coretraction(function) |
let trigfuns = ("sin", "cos", "tan", "sec", "csc", "cot") | ||
# regular, degrees, hyperbolic | ||
funcs = (trigfuns..., (trigfuns .* "d")..., (trigfuns .* "h")...) | ||
invfuncs = "a" .* funcs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invfuncs = "a" .* funcs | |
invfunc = "a" * func |
# regular, degrees, hyperbolic | ||
funcs = (trigfuns..., (trigfuns .* "d")..., (trigfuns .* "h")...) | ||
invfuncs = "a" .* funcs | ||
funcs, invfuncs = Symbol.(funcs), Symbol.(invfuncs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
funcs, invfuncs = Symbol.(funcs), Symbol.(invfuncs) |
funcs = (trigfuns..., (trigfuns .* "d")..., (trigfuns .* "h")...) | ||
invfuncs = "a" .* funcs | ||
funcs, invfuncs = Symbol.(funcs), Symbol.(invfuncs) | ||
for (func, invfunc) in zip(funcs, invfuncs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (func, invfunc) in zip(funcs, invfuncs) |
invfuncs = "a" .* funcs | ||
funcs, invfuncs = Symbol.(funcs), Symbol.(invfuncs) | ||
for (func, invfunc) in zip(funcs, invfuncs) | ||
@eval l_inv(::typeof($func)) = $invfunc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eval l_inv(::typeof($func)) = $invfunc | |
@eval left_inverse(::typeof($func)) = $invfunc |
funcs, invfuncs = Symbol.(funcs), Symbol.(invfuncs) | ||
for (func, invfunc) in zip(funcs, invfuncs) | ||
@eval l_inv(::typeof($func)) = $invfunc | ||
@eval r_inv(::typeof($invfunc)) = $func |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eval r_inv(::typeof($invfunc)) = $func | |
@eval right_inverse(::typeof($invfunc)) = $func |
@oschulz thinking about this more, I don't know if this is really a useful interface. If we consistently apply the rule that functions must be invertible on the whole real number line to have an TL;DR: If we want to be strict with math terminology, we'd have to break any code relying on this package and make So, here's three proposals for what a better interface could look like:
|
Personally I lean towards 3, and providing some way to let users opt-in to stricter behavior. |
I have to admit I'm not that happy with 1, 2 or 3, since the break the current approach, which is user-friendly and has worked very well in practice so far. I would argue that the situation is not quite as problematic as it might seem - the question is just on which domains inverses should be applicable, I think. The domain of The situation is different with |
From what I can tell, this is what I'm suggesting as option 1. This is the least-breaking option that leaves The reason we haven't hit any problems in practice is because implicitly, we've mostly decided that what
If that's the case, we can say the same for
They aren't, since
That's also why the inverse isn't unique--the behavior of I think what I've learned from all of this is that mathematicians have a bad habit of saying "invertible" when what they really mean is injective or one-to-one (i.e. left invertible) 😅 |
Your comment is based on the assumption that domain and co-domain should be R. But as @oschulz said, these are not necessarily the most natural (co-)domains and the notion of inverse functions is not restricted to these choices. Just as one example, Wikipedia also mentions that invertibility depends on the choice of the domain and co-domain (the article even discusses the squaring function and the fact that it is invertible if one restricts the domain to non-negative real numbers: https://en.wikipedia.org/wiki/Inverse_function#Squaring_and_square_root_functions). For some choices, only left or right inverses exist but for other choices both of them do exist. |
My comment is based on the assumption that the domain/codomain rule should behave consistently. Proposal 1 does not assume the domain and codomain should be R; it allows I think it makes sense to say " |
I agree with @devmotion. I also strongly disagree with the statement
and so would the developers of Bijectors and similar packages, I think. Functions map between sets - exp maps from the set of reals to the set of positive reals. The "whole real line" isn't a special thing, mathematically. So exp is absolutely a bijection, the codomain of exp is just not the whole set of real numbers.
The situation is very different for asin and friends. Yes, if their domains are restricted appropriately they can be bijective. But Let's take a more complex example: using BAT, ValueShapes, Distributions, InverseFunctions
mu = BAT.StandardMvUniform(4)
nu = HierarchicalDistribution(
NamedTupleDist(
a = Dirichlet([1,2,3]),
b = Exponential()
)
) do v
NamedTupleDist(
c = Uniform(v.a[1], v.a[1] + 5)
)
end
x = rand(mu)
f = BAT.DistributionTransform(nu, mu)
y = f(x) Let's say Here, nothing lives on the whole real line. The domain of We have currently no way of expressing such domains explicitly. Even it we did have a (probably very daunting) system for that, I don't see that could ever be automatically propagated through a composition What we can and do require is that domains are specified implicitly: In the example above, we have The above is not an esoteric example, we do use such constructions in practice reqularly. And in such applications we do use Not let's take a very simple example -
|
Thanks, that's roughly what I needed to know when writing the tests. (I'm trying to do this with property-based testing, which requires defining these kinds of properties rigorously.) One potential problem with this: what if |
Yes, that's a tricky area. I think so far we've required that |
I also totally support keeping the strict julia> using IntervalSets, InverseFunctions, Accessors, DataPipes
julia> function preimage(f, image::Interval)
f⁻¹ = inverse(f)
eps = f⁻¹.(endpoints(image))
preimg = @set endpoints(image) = eps
if eps[2] >= eps[1]
preimg
else
@p begin
preimg
@modify(reverse, endpoints(__))
@modify(reverse, closedendpoints(__))
end
end
end (not packaged anywhere, just copypaste where needed). It automatically works for all invertible functions, and throws for non-invertible ones. Getting an exception makes it clear that a specific If we took the stance that " Of course, it doesn't mean that left/right inverse aren't useful, they just need to be explicitly requested. |
@aplavin for your Maybe we should think about a function traits package that provides |
Indeed, you are totally right, it requires continuity. It's just that I always used it with functions continous on the interval of interest, and there's no way to check for that anyway now.
I'm all for such a package in principle, not sure how eager others will be with defining these traits. Also |
Yes, |
I'm closing this for now, until the discussion in #10 has converged. |
Adds
l_inv
andr_inv
for left/right inverses.