-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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: Modifying base to play nicely with physical units #15394
Conversation
cc @Keno – who has some experience with units, as the author of SIUnits. |
I'm a bit worried that all number definitions would need to consider units explicitly. It seems wrong that we cannot handle it implicitly but since you have opened this PR you have probably had some issues. I'd like to see some of the specific issues that you have had with the existing definition in order to understand how long we can get in making the base code generic instead of explicitly handling units. (It wasn't obvious to me from the julia-users conversation.) You are mentioning |
@@ -62,21 +62,29 @@ complex(z::Complex) = z | |||
flipsign(x::Complex, y::Real) = ifelse(signbit(y), -x, x) | |||
|
|||
function show(io::IO, z::Complex) | |||
r, i = reim(z) | |||
if unitless(z) != z |
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.
The use case here is to print Complex{Unit}
differently? Why not just define a method show{T<:Unit}(io::IO, z::Complex{T}
?
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.
Yes, of course you're right, thanks for protecting me from myself...
My initial impression is that calling Also, a different point, but I'm not sure it makes sense to allow converting numbers to units. It seems you should need |
@JeffBezanson Thank you for taking a look. Some replies to your concerns:
|
@@ -124,16 +125,16 @@ function rat(x) | |||
a, c = f*a + c, a | |||
b, d = f*b + d, b | |||
max(abs(a),abs(b)) <= convert(Int,m) || return c, d | |||
oftype(x,a)/oftype(x,b) == x && break |
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.
Perhaps use oftype(x,a)/oftype(x,b) == x/oftype(x, 1)
instead of unitless
?
It'd be really great to collect some use-cases where we need the meaning of Edit: It'd be interesting to see what breaks if we change Dates to comply with the multiplicative-identity meaning of |
@@ -82,7 +82,8 @@ colon(a::Real, b::Real) = colon(promote(a,b)...) | |||
|
|||
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)) | |||
range(a::Real, len::Integer) = | |||
UnitRange{typeof(a)}(a, oftype(a, oftype(a, unitless(a)+len-1))) |
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.
Maybe UnitRange{typeof(a)}(a, a + oftype(a, len-1)))
?
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.
Yes, this is certainly better.
This is a really great PR, @ajkeller34. It's great to see you pushing Base in this direction. I think, though, that almost all of it can be accomplished without |
@mbauman – that's a really good point. I agree that making more operations work for quantities with units seems to be the less troublesome route here. I think that also dovetails with @JeffBezanson's comment about scaling. The scalability issue is that if we take the route of stripping units off before doing any computation then any code that wants to work with units needs to do this. If we take the route of making more core operations work with units, then anything that's coded with operations that work with units will automatically also work with units. So the hard part is thinking about how operations in the standard library should work with units – but once that hard work is done, everyone benefits. |
Exactly. In fact I remember making ranges work with dates at one point, which was done by carefully using generic constructs like |
Should probably be tested by introducing mock unit-like types and making sure operations work. |
The points raised here make a lot of sense. Just to give rationale, I went with @StefanKarpinski I have some tests in Unitful.jl (probably not enough) that cover when units are used. @mbauman Thanks for the nice words, which do matter for a first-time contributor! |
Agreed that it's great to having you working on this, @ajkeller34! Like you, I am aware that we still have a ways to go before all is well in unit-land. I also agree that unitful quantities are often a good test to see if we've gotten our abstractions right. In |
@ajkeller34: to elaborate on my testing proposal, I meant that we should have bare bones types that mimic relative and absolute unit quantities enough to make sure that the code in Base is doing what it should with respect to them. Otherwise changes will keep getting made accidentally in Base that will break Unitful. Unitful can do more thorough testing of actual unit functionality, of course. |
I've played around with the code a bit more, trying to get it to work without defining
|
I'd feel more comfortable deferring to @StefanKarpinski, but in looking at the code, I can't help but come to the conclusions that (1) the More speculatively, I wonder about replacing References and related work: |
Thank you @timholy for the comment and references. The following is not a reply regarding Let's consider the following unit-unsafe but straightforward method from This code is not unit-friendly because
This fixes the unit math, and initially I agreed that this would work fine. However, I think we are now conflating the unit type and the numeric type. In the unit-unsafe method, if we try Another example. Consider
At some point, if I was trying to avoid these kinds of issues with Can anyone address the points I raised? I hope I don't come across as overly defensive of my original idea, but how else can you treat the numeric and unit types separately when it is necessary or at least extremely convenient to do so? I don't think the scaling would be so bad... many methods in Base will work just fine with unitful quantities, without Thanks again for reading. |
Regarding ranges: I don't think that You should always need to specify three things for a unitful range. In this way, there's some mathematical constructions, and some things in Considering the second example, In the One important reason that I see to avoid stripping off units is that it's very easy to mistakenly conclude that |
Thinking about it a little more, in the rare cases like our current implementation of float ranges, you need something that gives a hint of the underlying representation. The operation that might be most natural in such cases is to be able to factorize a quantity into a unitless number and a unit. The caller would than have to take care to use both parts appropriately. |
What is the explicit definition of a UnitRange? If I may think of UnitRange as a StepRange with step size of 1, it's not too big of a stretch to me to think of it as having a step size of T(1), where T is the type parameter of the UnitRange. In this case the default step size is just one of the unit. Having unitful integers can be very helpful, e.g. when working with unit standards and exact conversions. 1inch is exactly 254//100 cm, etc. When you only allow floating point numbers it is harder to write code respecting exact conversions. |
@StefanKarpinski @Keno I am eager to work on this PR but I am waiting for response on some points I raised earlier. I guess I remain unconvinced that
I'm looking forward to when I can use units in Julia as part of my superconducting qubit measurement and design code, but I've been holding back until I know I have some support on this. I want to argue my case for why (I also recognize that tests are needed before this PR has even a chance of being merged.) |
First, thanks @ajkeller34 for pushing this, I think that making units more first-class is an awesome improvement. tl;dr - I'm not certain that we'll get to the point where whole packages will work with units without modification, but I think that making it easy for package authors to support units is huge (and we're pretty close). There are always going to be times when package authors need an escape hatch that converts between unitful and unitless values, so I think there's a use for In more depth: Having just gone through the process of designing a package that should work well with units (I used SIUnits.jl because it's 0.4 compatible), maybe I can give some more data points. Here are the places where I needed to take special care to handle unitful sampling rates and times:
So the cases I had to be unit-aware were either when I wanted some escape hatch into non-unit land (1-3), or when I needed to store a value in a type field (4). The former seems like a good use-case for This example seems to be a nice example of some code that I know works with or without units. I had to do some gymnastics to get the types correct for the Regarding when library code should strip units and proceed as if the given values were unitless, I think that it's usually indicative that maybe the operation you're doing isn't physically meaningful and it should be up to the user to handle unit stripping. It seems to me that you should be able to replace |
👍 to @ssfrr's final paragraph. @ajkeller34, I've certainly experienced similar frustrations in working with units, but I think the best answer is to get as far as you can without explicit unit-stripping and -adding functions. Then, file bugs against julia for those cases where you can't figure out a proper solution. This has by far the best chance of a delightful outcome: by pointing out problems in specific places, and giving everyone a chance to scratch their collective heads, either (1) some bright person will come up with brilliant solutions that don't require code-uglification, or (2) everyone will give up and agree with you. Either way, you win. What's not to like? 😄 |
Thanks @timholy, I'll think about some alternatives. One can get pretty far without explicit unit-stripping and -adding functions, but ranges are tricky. And yes, @ssfrr has the right idea: I was only using |
I also agree that the most likely approach to be fruitful with the non-straightforward cases is to consider one at a time. Also, I would like to question the use of
and not |
I agree with @tolvoh here. Unit conversion is compatible with the meaning of conversion, but unit change is not. These days I have been leaning towards simply disallowing UnitRange for unitful quantities, because as mentioned above it is not really meaningful. One might imagine having to figure out if UnitRange on the B type should use the B or the more common dB, and I think it would be good to avoid that. |
(Seems like there was some oddity with @TotalVerb's comment, I deleted a bunch of duplicates.) Right, |
Just to comment on the situation here for any casual observers: this is still very much something we want but essentially requires appropriate separation of |
x-ref #20268 |
I'm interested and personally invested in getting Julia to support physical units. There are some places in base that could be modified to suppress the numerous override warnings that appear when using my physical unit package for Julia 0.5.0-dev, Unitful.jl. Advertisements aside, I believe some changes to base are going to be necessary for any units package that fully supports Ranges (
UnitRange
,FloatRange
, etc.) to have a chance of integrating with Julia without emitting warnings. Here's a TL;DR version:one(x)
is not logically the same asoftype(x,1)
when working with quantities that have units (one(x)
is defined as the multiplicative identity, which should not have units!), and sometimes we need to strip units for certain numerical operations and comparisons. This is especially obvious inrange.jl
.Encouraged by @StefanKarpinski in this julia-users thread, here are some changes to base I'd appreciate having comments on. The change to
complex.jl
is not as important as the changes tonumber.jl
andrange.jl
. I built Julia using a few-day-old version of master and find no test regressions with my modifications. I could use some help with benchmarking, however. This is my first PR for Julia and while I have read the guidelines for contributions, please advise if I should be doing something differently.Thanks!