Skip to content

Add Base.zero and one #6

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

Merged
merged 2 commits into from
Jun 7, 2023
Merged

Add Base.zero and one #6

merged 2 commits into from
Jun 7, 2023

Conversation

MilesCranmer
Copy link
Member

No description provided.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 7, 2023

Benchmark Results

main 37b41a3... t[main]/t[37b41a3...]
creation/Quantity(x) 3.3 ± 0.1 ns 4 ± 0.1 ns 0.825
creation/Quantity(x, length=y) 3.7 ± 0.1 ns 3.7 ± 0.1 ns 1
time_to_load 0.0751 ± 0.0011 s 0.0756 ± 0.00076 s 0.994
with_numbers/*real 3.7 ± 0.1 ns 3.7 ± 0.1 ns 1
with_numbers/^int 14.8 ± 2.8 ns 14.8 ± 2.9 ns 1
with_numbers/^int * real 26.1 ± 1.4 ns 25.6 ± 1.3 ns 1.02
with_quantity//y 16.1 ± 0.1 ns 16.1 ± 0.1 ns 1
with_quantity/^y 0.931 ± 0.18 μs 0.931 ± 0.18 μs 1
with_self/dimension 1.6 ± 0.1 ns 1.6 ± 0.1 ns 1
with_self/inv 2.1 ± 0.1 ns 2.8 ± 0.1 ns 0.75
with_self/ustrip 1.6 ± 0.1 ns 1.6 ± 0.1 ns 1

Benchmark Plots

A plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR.
Go to "Actions"->"Benchmark a pull request"->[the most recent run]->"Artifacts" (at the bottom).

@MilesCranmer MilesCranmer merged commit 0c2938f into main Jun 7, 2023
@MilesCranmer MilesCranmer deleted the base-zero branch June 7, 2023 07:48
@mcabbott
Copy link

Are these right? I think zero should probably keep the dimension, and oneunit should too:

julia> x = Quantity(0.3, mass=1, length=0.5)
0.3 𝐋 ¹ᐟ² 𝐌 ¹

julia> x + zero(typeof(x))  # additive identity element
ERROR: DimensionError: 0.3 𝐋 ¹ᐟ² 𝐌 ¹ and 0.0  have incompatible dimensions
Stacktrace:
 [1] +(l::Quantity{Float64, DynamicQuantities.FixedRational{…}}, r::Quantity{Float64, DynamicQuantities.FixedRational{…}})
   @ DynamicQuantities ~/.julia/packages/DynamicQuantities/jl8WE/src/math.jl:19
 [2] top-level scope
   @ REPL[83]:1
Some type information was truncated. Use `show(err)` to see complete types.

julia> x * one(typeof(x))  # multiplicative identity for x
0.3 𝐋 ¹ᐟ² 𝐌 ¹

julia> x + oneunit(typeof(x))  # This differs from one for dimensionful quantities: one is dimensionless (a
  multiplicative identity) while oneunit is dimensionful (of the same type as x, or of type T).
ERROR: MethodError: no method matching Quantity{Float64, DynamicQuantities.FixedRational{Int32, 25200}}(::Quantity{Float64, DynamicQuantities.FixedRational{Int32, 25200}})

I also wonder whether one needs to wrap in a Quantity at all. Might it be simpler to return just the Float64?

julia> one(typeof(x))
1.0 

julia> typeof(ans)
Quantity{Float64, DynamicQuantities.FixedRational{Int32, 25200}}

@MilesCranmer
Copy link
Member Author

Are these right? I think zero should probably keep the dimension, and oneunit should too:

This is the intended behavior, although I'm happy to discuss whether it makes the most sense. I view it as adding a 2D array to a 3D array - even if the 2D array is all 0s, you would still want an error to be thrown.

Other unit packages have similar behavior. e.g., astropy:

from astropy import units as u

q = 10 * u.m # 10 meters
q2 = 10 * u.s # 10 seconds

q * 0 + q2
# ^ Raises UnitConversionsError

and Unitful.jl

julia> using Unitful

julia> 0.0u"m" + 10.0u"s"
ERROR: DimensionError: 0.0 m and 10.0 s are not dimensionally compatible.
julia> x + zero(typeof(x))  # additive identity element

This breaks because the Quantity does not store the dimensions in the type, but rather in the value. So zero(::Type{Quantity}) just creates a Dimensions with 0 power along each dimension. But maybe we could make a zero(::Quantity) and one(::Quantity) that do share the dimensions?

I also wonder whether one needs to wrap in a Quantity at all. Might it be simpler to return just the Float64?

This is what Unitful.jl does, but from my brief experience, it can get very frustrating when you request a type and are given something else, or if it randomly converts to a Float64 in the middle of a calculation because it happened to be dimensionless. In the Unitful->DynamicQuantities conversion I need to construct a FreeUnits by hand, because the standard constructors will eagerly reduce to Float64.

Note also that you can't add a Float64 to a Quantity right now. But I guess it might make more sense to allow it if that Quantity is dimensionless?

julia> 0.5 + Quantity(0.5)

(Right now this threads a MethodError, but I don't see a reason it shouldn't work. dimension(::Number) is actually already defined to give back a dimensionless Dimensions() object).

@mcabbott
Copy link

mcabbott commented Jun 12, 2023

Ah my complaint is not about the error, but about the zero. I agree that adding apples + oranges never makes sense. But perhaps I forgot about types & this is more complicated than I thought.

x + zero(x) == x should be true. For most numbers, zero(x) = zero(typeof(x)) is the only definition. But here, zero(::Quantity) is easy, while perhaps zero(::Type{Quantity}) cannot be defined? (If it returned anything it would have to be some special "weak zero" which infers units later?)

I do think 0.5 + Quantity(0.5) makes sense. More generally I wonder whether you want Quantity <: Real, in which case promotion ought to take care of many cases where you mix units & not-units. Although I see you allow Quantity([1,2,3], time=1) at the moment. I see on discourse some discussion of a QuantityArray but... well maybe this gets out-of-scope for this issue.

@MilesCranmer
Copy link
Member Author

Okay, I think I see what you mean. I'll put in a PR for this (and the :+(::Number, ::Quantity)) shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants