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

RFC: tests for dimensional correctness of Base #20484

Merged
merged 12 commits into from
Feb 11, 2017

Conversation

stevengj
Copy link
Member

@stevengj stevengj commented Feb 7, 2017

This PR (currently very incomplete) adds a test suite dimensionful.jl that implements a small dimensionful number type Furlong that can be used to test the dimensional correctness of Base functions.

I already caught an bug in range, and there are probably lots more problems.

cc @timholy

@stevengj stevengj added the test This change adds or pertains to unit tests label Feb 7, 2017
@timholy
Copy link
Sponsor Member

timholy commented Feb 7, 2017

CC @ajkeller34

@ajkeller34
Copy link
Contributor

ajkeller34 commented Feb 7, 2017

I know some of the LU factorization code is dimensionally inconsistent, and I'd love for Unitful to work better with linear algebra. See Unitful #46. I actually have a local branch of Unitful where I was playing around with how far I could get without changing Base, and I think I got stuck somewhere, so I will try to recall what the problem was.

Edit: Of course, I recognize some of this may not be trivial, and it may be better to do some prototyping in a package, which is sort of what I had in mind originally. I just thought I would mention it while the topic is being discussed.

@andreasnoack
Copy link
Member

@ajkeller34 We discussed this long time ago in #7623. As such, the algorithm used in generic_lufact! can handle units but it fails because it tries to store the unitless L in the input matrix. In the tradition of LAPACK, all of our dense linear algebra reuses input memory to store the result and that is not possible for tightly types unitful matrices. If you convert the element type of matrix to an abstract type then the factorization succeeds

julia> a = [1.0u"N" 2.0u"N"
             3.0u"N" 1.0u"N"]
2×2 Array{Quantity{Float64, Dimensions:{𝐋 𝐌 𝐓^-2}, Units:{N}},2}:
 1.0 N  2.0 N
 3.0 N  1.0 N

julia> lufact!(Matrix{Number}(a), Val{false})
Base.LinAlg.LU{Number,Array{Number,2}}(Number[1.0 N 2.0 N; 3.0 -5.0 N],[1,2],0)

but this is not really satisfying. We could try experimenting with widening of the type as the factorization progresses similarly to broadcast!.

@timholy
Copy link
Sponsor Member

timholy commented Feb 7, 2017

For Cholesky factorization there's a good argument that L should have sqrt(units(A)), but I can't really see that's true of LU factorization. To me it seems perfectly fine to have lufact(A) fail for unitful quantities, but to support lufact!(dest, A) where dest is a suitably-preallocated object.

Copy link
Contributor

@GlenHertz GlenHertz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a few typos

Base.abs{p,T}(x::Furlong{p,T}) = Furlong{p,T}(abs(x.val))

for f in (:isfinite, :isnan, :isreal)
@eval Base.$f(x::Furlong) = isfinite(x.val)
Copy link
Contributor

@GlenHertz GlenHertz Feb 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be @eval Base.$f(x::Furlong) = $f(x.val)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

@eval Base.$f(x::Furlong) = isfinite(x.val)
end
for f in (:real,:imag,:complex)
@eval Base.$f{p,T}(x::Furlong{p,T}) = Furlong{p}(f(x.val))
Copy link
Contributor

@GlenHertz GlenHertz Feb 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be @eval Base.$f{p,T}(x::Furlong{p,T}) = Furlong{p}($f(x.val))?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.

@stevengj
Copy link
Member Author

stevengj commented Feb 8, 2017

Hmm, found another problem: the StepRange{T,S} iteration code assumes that start::T + step::S can be converted to type T, but this is not guaranteed by the constructor. In particular, we define colon{T}(start::T, step, stop::T) = StepRange(start, step, stop), but it seems like this should be:

function colon{T}(start::T, step, stop::T)
    T′ = typeof(start+step)
    StepRange(convert(T′,start), step, convert(T′,stop))
end

I'm not really sure why StepRange allows the step to be of a different type than the endpoints at all. @StefanKarpinski, can you comment on this?

@stevengj
Copy link
Member Author

stevengj commented Feb 8, 2017

Hmm, for dimensionful quantities it looks like it would be nice to have isnegative(x) to use instead of x < 0. Of course, you can define <(x::Furlong, y::Real) to work if y==0 and throw an error otherwise, but it is a bit ugly.

@JeffBezanson
Copy link
Sponsor Member

That case handles ordinal types, e.g. 'a':2:'z'.

@TotalVerb
Copy link
Contributor

Another case is with Date and Period.

@stevengj
Copy link
Member Author

stevengj commented Feb 8, 2017

I fixed a couple more dimensional problems. My inclination would be to merge these tests and fixes now, assuming tests are green, and put additional fixes in subsequent PRs.

@timholy
Copy link
Sponsor Member

timholy commented Feb 8, 2017

Hmm, for dimensionful quantities it looks like it would be nice to have isnegative(x) to use instead of x < 0.

What's wrong with x < zero(x)? I'd rather not proliferate too many names...

Copy link
Sponsor Member

@timholy timholy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See issues regarding ranges. I wonder if it would be better to move the code that defines Furlong and its operations into TestHelpers?

base/range.jl Outdated
@@ -6,9 +6,9 @@ colon{T<:Real}(start::T, stop::T) = UnitRange{T}(start, stop)

range(a::Real, len::Integer) = UnitRange{typeof(a)}(a, oftype(a, a+len-1))

colon{T}(start::T, stop::T) = colon(start, oftype(stop-start, 1), stop)
colon{T}(start::T, stop::T) = colon(start, oneunit(stop-start), stop)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I didn't look at this earlier. I think the original version was correct, and that this version is a bug. This has been discussed elsewhere and there isn't a uniform opinion, but I would find the following worrisome:

xmm, ymm = 1000mm, 2000mm
xm, ym = convert(Meter, xmm), convert(Meter, ymm)
xmm == xm && ymm == ym => true
(xmm:ymm) == (xm:ym) => false

I think it's a bad idea to allow start:stop constructors for unitful quantities; one should be required to specify the step explicitly. The oftype version of this code forces that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay.

base/range.jl Outdated
@@ -46,7 +49,7 @@ _range{T,S}(::Any, ::Any, a::T, step::S, len::Integer) = StepRangeLen{typeof(a+0

# AbstractFloat specializations
colon{T<:AbstractFloat}(a::T, b::T) = colon(a, T(1), b)
range(a::AbstractFloat, len::Integer) = range(a, oftype(a, 1), len)
range(a::AbstractFloat, len::Integer) = range(a, oneunit(a), len)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

end
Base.sqrt{p,T}(x::Furlong{p,T}) = _div(sqrt(x.val), x, Val{2})

@test collect(Furlong(2):Furlong(10)) == collect(Furlong(2):Furlong(1):Furlong(10)) == Furlong.(2:10)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turn the first of these into a @test_throws

@StefanKarpinski
Copy link
Sponsor Member

What's wrong with x < zero(x)? I'd rather not proliferate too many names...

x has units and zero(x) does not.

@timholy
Copy link
Sponsor Member

timholy commented Feb 8, 2017

Why would the additive identity not have units?

julia> using Unitful

julia> using Unitful.m

julia> x = 1m
1 m

julia> zero(x)
0 m

julia> x + zero(x)
1 m

@StefanKarpinski
Copy link
Sponsor Member

Oh right, my mistake! 🥇

@stevengj
Copy link
Member Author

stevengj commented Feb 8, 2017

The only problem with x < zero(x) is that it may be inefficient for something like BigFloat, since zero(x) has to allocate a new object, and comparisons with Int64 are faster.

@stevengj
Copy link
Member Author

stevengj commented Feb 8, 2017

But probably if you are doing the sqrtm of a BigFloat matrix the comparison with zero is the least of your worries. I'll change it.

@stevengj stevengj changed the title WIP: tests for dimensional correctness of Base RFC: tests for dimensional correctness of Base Feb 8, 2017
@stevengj stevengj force-pushed the dimensionful branch 2 times, most recently from df10330 to 8871684 Compare February 10, 2017 02:39
@stevengj
Copy link
Member Author

stevengj commented Feb 10, 2017

Rebased. Good to squash/merge when green?

@stevengj
Copy link
Member Author

Rebased again.

@stevengj stevengj merged commit 43e2aad into JuliaLang:master Feb 11, 2017
@stevengj stevengj deleted the dimensionful branch February 11, 2017 23:05
@tkelman
Copy link
Contributor

tkelman commented Feb 14, 2017

this has caused the ambiguity test to start segfaulting. happens often on the buildbots, and it's reproducible locally with make testall1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
test This change adds or pertains to unit tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants