-
Notifications
You must be signed in to change notification settings - Fork 9
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
Add exact rules for irrational numbers #14
base: main
Are you sure you want to change the base?
Conversation
Pull Request Test Coverage Report for Build 2239524464Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
Codecov Report
@@ Coverage Diff @@
## main JuliaMath/Tau.jl#14 +/- ##
===========================================
+ Coverage 0 100.00% +100.00%
===========================================
Files 0 1 +1
Lines 0 28 +28
===========================================
+ Hits 0 28 +28
Continue to review full report at Codecov.
|
src/rules.jl
Outdated
(sqrt2, 2.0), | ||
(sqrt3, 3.0), |
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'm not sure these should be (sqrt2, 2.0), (sqrt3, 3.0)
or (sqrt2, 2), (sqrt3, 3)
. I was referring to sin(π)
.
julia> versioninfo()
Julia Version 1.9.0-DEV.428
Commit f6a95348fe (2022-04-21 11:53 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: 16 × AMD Ryzen 7 2700X Eight-Core Processor
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-13.0.1 (ORCJIT, znver1)
Threads: 1 on 16 virtual cores
julia> sin(π)
0.0
julia> cos(π)
-1.0
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 they should be Float64
, as explained in my other comment.
I'm in favour of adding such improvements in principle. However, I think we should be consistent with Julia base and non-breaking, ie all shortcuts should return values of the same type as currently. Thus generally they should return |
For example, julia> log(ℯ)
1
This package is before |
It would be OK to add breaking changes here but IMO it's not good to be inconsistent with Julia base. In the sin(pi) PR triage made it clear that the trigonometric shortcuts should return |
In that PR(JuliaLang/julia#42595), the discussion On the current master julia> sin(quartπ)^2
0.4999999999999999 With this PR julia> sin(quartπ)^2
0.5 I have read the following comment in JuliaLang/julia#35823 (comment).
We don't need to care about breaking changes here, so I still think the return values can be @jishnub @JeffreySarnoff Do you have any thoughts on this? |
src/rules.jl
Outdated
@@ -0,0 +1,64 @@ | |||
## Square | |||
SQUARE_PAIRS = ( |
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'd put these variables in a let
block (or do for (a,b) in (...)
), otherwise you're bloating the compiled cache with stuff which isn't needed at runtime
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.
Thanks for the review! I'll update the code later.
I don't see why we can't improve accuracy while keeping the same return types as base (and as currently in this package). Using sin(::typeof(quartpi)) = Float64(invsqrt2) would keep consistent return types while speeding up calculations and potentially improving accuracy (at least it's definitely not worse). Such changes would be much less controversial and easily justifiable. Hence even if we would want to change the behaviour at some point, they would be a simple non-breaking first step. IMO it would be very unfortunate to introduce inconsistencies such as julia> sin(pi) isa Float64
true
julia> sin(quartpi) isa Irrational
true Apart from the inconsistencies that we would introduce, I also wonder more generally wether it's worth returning Irrationals. As @JeffreySarnoff pointed out in the other PR it is unclear where to stop (and as the commented out line in this PR shows some definitions are not even possible without introducing type piracy). In most downstream calculations at some point irrationals are promoted to floating point numbers anyway, and hence downstream packages already now have to take care of these promotions (and e.g. usually avoid multiplying them with integers) to ensure that the output is of the desired type and no undesired promotions occur. Hence loss of accuracy usually should not be an issue. And if your computation contains a term such as |
Mathematically, elementary functions almost never return an integer value. To introduce a second return type would force other people's code to work with Unions as returned types (whether they know it or not), and this is disadvantageous. |
Thanks for the detailed description! I still have some questions.
As you may know, if we use this definition, then the julia> Float64(invsqrt2)^2
0.5000000000000001 And the speed is not relevant here: julia> f(x) = sin(x)^2 # with this PR
f (generic function with 1 method)
julia> @code_llvm f(quartπ)
; @ REPL[27]:1 within `f`
define double @julia_f_292() #0 {
top:
ret double 5.000000e-01 julia> f(x) = sin(x)^2 # On the current master
f (generic function with 1 method)
julia> @code_llvm f(quartπ)
; @ REPL[2]:1 within `f`
define double @julia_f_147() #0 {
top:
ret double 0x3FDFFFFFFFFFFFFE
}
Both julia> typeof(π) == typeof(quartπ)
false As a different example, the power operator julia> 2^2
4
julia> 2^(-2)
0.25 Both inputs are integer, but the output types are different. This is not problematic because the type-instability doesn't exist after compiling.
I was also having the same concerns. I thought "return
I agree with that, but I think the objection can be adapted to
Is there any practical case to produce |
I meant, if we evaluated
It is much the same case as this
|
I see, but the function |
IIUC, the issue is with something like julia> sin.(Irrational[π, quartπ])
2-element Vector{Real}:
1.2246467991473532e-16
invsqrt2 = 0.7071067811865... vs. something like ulia> sin.(Irrational[π, ℯ])
2-element Vector{Float64}:
1.2246467991473532e-16
0.41078129050290885 |
I think the example is similar to the following, so julia> abs2.(Real[1.,1//2])
2-element Vector{Real}:
1.0
1//4
julia> abs2.([1.,1//2])
2-element Vector{Float64}:
1.0
0.25 If we use julia> isconcretetype(eltype(Irrational[π, quartπ]))
false
julia> isconcretetype(eltype(Real[1.,1//2]))
false
julia> isconcretetype(eltype([π, quartπ]))
true
julia> isconcretetype(eltype([1.,1//2]))
true Note that we already have the following property because of julia> log.(Irrational[π, ℯ])
2-element Vector{Real}:
1.1447298858494002
1 (I still could not agree that the definition of |
@hyrodium we may be looking at slightly different effects, my concern with providing Integers when the result is exact and integral while providing floats for most results is that the following does not allow the same downstream optimizations as would be available were only floats provided. Some specializations and simplifications with the flow within other users' functions which utilize this and pass along the results to other methods certainly could become unavailable.
|
Surely it is type stable but my point was that it is inconsistent - with the behaviour in base but also within this PR. As pointed out by others, this is problematic since it makes it more difficult to reason about the return type, both for the compiler and downstream developers. To reiterate, I think it would be good to avoid these problems and just consistently ensure |
"Exact result for exact input" doesn't imply type-instable, and the |
Is this comment on the compiler true? I'm not much familiar with the inside of the Julia compiler, but I think if we have type-stable code, that will not be problematic. For the downstream developers, we can just add more documentation. If someone needs
I'm okay to separate this PR into [add rules] and [fix return type], but, I couldn't agree that the Julia base enforces
julia> sin(quartπ)
invsqrt2 = 0.7071067811865...
julia> f(x,a) = sin(x) + a
f (generic function with 1 method)
julia> f(3.0f0, 4.0f0)
4.14112f0
julia> f(quartπ, 4.0f0)
4.7071066f0 In the above example, |
@hyrodium At this point in the discussion it is safe to say that your position and reasoning are well understood by the thread participants. You ask "Is this comment on the compiler true?" It is true and well understood by the people who work with performance sensitive coding. In addition, it may increase precompilation time, as either each case needs to be followed into calling code or optimization paths dropped. I understand your preference for mathematical "honesty", and respect it. Nonetheless, your proposal would lead people to avoid using our work -- including me. |
@JeffreySarnoff Thanks for summarizing the threads so far!
Sorry to bother you, but I have one last question. Is there any performance benchmark for that? on the current julia> using IrrationalConstants
julia> t=time(); inv(inv(sin(quartπ)))^2; println(time()-t)
0.04355502128601074 with this PR julia> using IrrationalConstants
julia> t=time(); inv(inv(sin(quartπ)))^2; println(time()-t)
0.000640869140625 Note that I could not compare the performance with on the current julia> using IrrationalConstants
julia> @time inv(inv(sin(quartπ)))^2
0.000001 seconds
0.4999999999999999 with this PR julia> using IrrationalConstants
julia> @time inv(inv(sin(quartπ)))^2
0.000001 seconds
0.5 I'm using the following environment. julia> versioninfo()
Julia Version 1.7.2
Commit bf53498635 (2022-02-06 15:21 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: AMD Ryzen 7 2700X Eight-Core Processor
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-12.0.1 (ORCJIT, znver1) Sorry if the method for measuring compile time is not correct. Since Julia uses a JIT compilation, I thought it was normal not to worry about compilation time unless the time is significantly longer.
I'm not a maintainer of this repository, so it's okay to close this PR. Then, I will create a package |
As @giordano mentioned, there are some problems with the implementation in the PR, regardless of the discussion above. So it would be good to fix these before comparing run or compilation times. One could use
I would be happy if you could fix the implementation issue and change it to a non-breaking PR. But I understand if you don't want to spend time on this if you have a different goal in mind. In this case I might open a PR when I find the time for it. The main issue with a package such as IrrationalConstantRules.jl is that it will contain most/only type piracy (as you already mention) which affects even packages that only use it as a indirect dependency. This will severely limit its adoption in the Julia ecosystem, I assume. (Out of curiosity, what's the type piracy in ColorTypes?) |
Thanks! I measured the time. On commit julia> @time using IrrationalConstants
0.029152 seconds (25.73 k allocations: 2.263 MiB, 34.32% compilation time) On commit julia> @time using IrrationalConstants
0.027626 seconds (25.73 k allocations: 2.264 MiB, 33.53% compilation time) It seems the difference in compilation times can be ignored.
I also updated around multiplication inverse, it now returns julia> π * invπ
1.0 This behavior is different from #7 (comment).
I understand that.
julia> using ColorTypes
julia> RGB(0.0, 0.1, 0.2)
RGB{Float64}(0.0,0.1,0.2)
julia> RGB(0.0, 0.1, 0.2) + RGB(0.4, 0.1, 0.0)
ERROR: MethodError: no method matching +(::RGB{Float64}, ::RGB{Float64})
Math on colors is deliberately undefined in ColorTypes, but see the ColorVectorSpace package.
Closest candidates are:
+(::Any, ::Any, ::Any, ::Any...) at ~/julia/julia-1.7.2/share/julia/base/operators.jl:655
Stacktrace:
[1] top-level scope
@ REPL[3]:1
julia> using ColorVectorSpace
julia> RGB(0.0, 0.1, 0.2) + RGB(0.4, 0.1, 0.0)
RGB{Float64}(0.4,0.2,0.2) The (What I was trying to do in |
Do we really need to fix tests in Julia 1.0? (x-ref: JuliaMath/Tau.jl#15) julia> versioninfo()
Julia Version 1.0.5
Commit 3af96bcefc (2019-09-09 19:06 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: AMD Ryzen 7 2700X Eight-Core Processor
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.0 (ORCJIT, znver1)
julia> inv(π)
ERROR: MethodError: no method matching Irrational{:π}(::Int64)
Closest candidates are:
Irrational{:π}(::T<:Number) where T<:Number at boot.jl:725
Irrational{:π}() where sym at irrationals.jl:18
Irrational{:π}(::Complex) where T<:Real at complex.jl:37
...
Stacktrace:
[1] convert(::Type{Irrational{:π}}, ::Int64) at ./number.jl:7
[2] one(::Type{Irrational{:π}}) at ./number.jl:297
[3] one(::Irrational{:π}) at ./number.jl:298
[4] inv(::Irrational{:π}) at ./number.jl:220
[5] top-level scope at none:0
The test failure is because of |
I see no compelling need to support any version earlier than |
That doesn't matter for us though as we don't define it? Since it's just a specific test that fails we could just not run it on Julia versions that do not support it. I don't think there's anything problematic in the package that we can control or fix but this is just a test problem. |
If we skip the tests for Julia 1.0, that means the version of Julia is not well-supported in this package, I guess. Specified code for the unsupported version of Julia will be painful for me even if that was just a test problem.... sorry. |
I thought one could just skip the tests that fail because some method in base is not in Julia 1.0. It's still tested properly in all other Julia versions so I don't think the package is not tested properly. However, here the fix seems to be even simpler: we can just load Compat in the tests which defines That is also the fix for your example above: julia> inv(π)
ERROR: MethodError: no method matching Irrational{:π}(::Int64)
Closest candidates are:
Irrational{:π}(::T<:Number) where T<:Number at boot.jl:725
Irrational{:π}() where sym at irrationals.jl:18
Irrational{:π}(::Complex) where T<:Real at complex.jl:37
...
Stacktrace:
[1] convert(::Type{Irrational{:π}}, ::Int64) at ./number.jl:7
[2] one(::Type{Irrational{:π}}) at ./number.jl:297
[3] one(::Irrational{:π}) at ./number.jl:298
[4] inv(::Irrational{:π}) at ./number.jl:220
[5] top-level scope at none:0
julia> using Compat
julia> inv(π)
0.3183098861837907 |
I didn't know the |
Just created |
I just noticed current main branch julia> @time_imports using IrrationalConstants
9.6 ms IrrationalConstants with this PR julia> @time_imports using IrrationalConstants
31.5 ms IrrationalConstants I think we can ignore these differences because 30ms is small. |
(sqrtπ, invsqrtπ), | ||
) | ||
|
||
function test_with_function(f, a::Irrational) |
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.
function test_with_function(f, a::Irrational) | |
function test_with_function(f, a::AbstractIrrational) |
MethodError: no method matching (::var"#test_with_function#4")(::typeof(sin), ::IrrationalConstants.Log4π)
Closest candidates are:
(::var"#test_with_function#4")(::Any, ::Irrational)
This PR fixes #20, and is an alternative to JuliaMath/Tau.jl#7.
On current master
With this PR