-
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
left/right inverses? #10
Comments
Good point, let's keep this issue as a reminder. |
I agree, this would be useful to have. |
Also relates to #8, e.g. for |
right-inverses could also be helpful components (building blocks) in pullback definitions, see example in JuliaObjects/Accessors.jl#46 ( |
@oschulz I see |
@ParadaCarleton , so far we haven't really had a use case for left/right inverses, but I guess now there is one with TableTransforms then. I think there's consensus that in principle we should add left/right inverses. Would you like to put a proposal together, maybe as a draft PR - just bare-bone initially, so we can discuss the API? |
Noticed interesting convergence, though arriving from quite a different direction. In In the general case, construct(Gaussian, area => 1, fwhm => 2)
construct(Complex, abs => 3, angle => 0.5) However, invertible functions and basic types like (named)tuples are supported automatically. This means julia> rinverse(f) = x -> construct(f => x) is a right inverse for supported functions. # just uses inverse() inside
julia> rinverse(log10)(3)
1000.0
# uses inverse() + creates namedtuple
julia> rinverse(@o log10(_.a))(3)
(a = 1000.0,) I don't really have common usecases for right inverses to non-fully-invertible mathematical functions, so this It can be seen from these examples that |
Oh, this looks elegant @aplavin ! I tried running |
Works beautifully @aplavin ! :-) |
I've been thinking, if we offer left/right-inverses, should we allow room in the API and namespace for non-function inverses, and a more general package "InverseElements.jl" (or simply "Inverses.jl")? Our current So we could have a package But what about |
It does work - my bad, the perils of typing on the phone on a train. :-) |
We would actually have at lease one nice internal use case: We could use expand Let's say we do it all in "InverseFunctions.jl" (no extra package "Inverse[Elements].jl"). "InverseFunctions" can mean "functions the generate inverses" just as well as "inverses of functions", after all. Then we could have a generic implementation inverse(ff::Base.Fix1) = Base.Fix1(ff.op, left_inverse(ff.op, ff.x))
inverse(ff::Base.Fix2) = Base.Fix2(ff.op, right_inverse(ff.op, ff.x)) And with left/right-inverses for specific binary operations left_inverse(::typeof(-), x::Real) = x
right_inverse(::typeof(-), x::Real) = -x we'd automatically get julia> f = Base.Fix1(-, 5); inverse(f)(f(9))
9
julia> f = Base.Fix2(-, 5); inverse(f)(f(9))
9 We still might want to specialize |
Not necessarily, at least not for numbers - if the inverse is used more than once, then precomputing Certainly wouldn't hurt to define
Unfortunately not - So yes, we'd still need quite a few explicit |
What’s the current right way to handle this? I’m looking to use something like this in SymbolicRegression. FWIW I like the |
Left/right- inverses of a specific inverse case involving |
Gotcha. Just to check, is the following the right idea? inverse(f::Base.Fix1{typeof(+)}) = Base.Fix2(-, f.x) I'm starting out with an internal method that forwards to InverseFunctions to avoid piracy and since for my use-case it's useful to have non-exact inverses (like |
I guess there's some ambiguity here... not sure if there's a better way inverse(f::Base.Fix1{typeof(+)}) = Base.Fix1(+, -f.x) Some are quite nice though: inverse(f::Base.Fix1{typeof(-)}) = f
inverse(f::Base.Fix1{typeof(/)}) = f |
For posterity, here's what I put into SR.jl. Note that I label these as # (f.x + _) => (_ - f.x)
approx_inverse(f::Base.Fix1{typeof(+)}) = Base.Fix2(-, f.x)
# (_ + f.x) => (_ - f.x)
approx_inverse(f::Base.Fix2{typeof(+)}) = Base.Fix2(-, f.x)
# (f.x * _) => (_ / f.x)
approx_inverse(f::Base.Fix1{typeof(*)}) = Base.Fix2(/, f.x)
# (_ * f.x) => (_ / f.x)
approx_inverse(f::Base.Fix2{typeof(*)}) = Base.Fix2(/, f.x)
# (f.x - _) => (f.x - _)
approx_inverse(f::Base.Fix1{typeof(-)}) = f
# (_ - f.x) => (_ + f.x)
approx_inverse(f::Base.Fix2{typeof(-)}) = Base.Fix2(+, f.x)
# (f.x / _) => (f.x / _)
approx_inverse(f::Base.Fix1{typeof(/)}) = f
# (_ / f.x) => (_ * f.x)
approx_inverse(f::Base.Fix2{typeof(/)}) = Base.Fix2(*, f.x)
# (f.x ^ _) => log(f.x, _)
approx_inverse(f::Base.Fix1{typeof(^)}) = Base.Fix1(log, f.x)
# (_ ^ f.x) => _ ^ (1/f.x)
approx_inverse(f::Base.Fix2{typeof(^)}) = Base.Fix2(^, inv(f.x)) |
D’oh!! I need sleep… |
@MilesCranmer , if you don't specifically need |
Just adding a note here (we don't have to dig in right now), that it would be great to also support
left_inverse
andright_inverse
, since this comes up so oftenThe text was updated successfully, but these errors were encountered: