Skip to content

Commit 8520362

Browse files
authoredNov 12, 2023
Merge branch 'main' into safe-precompile
2 parents cc9694c + cd30223 commit 8520362

22 files changed

+1341
-517
lines changed
 

‎Project.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DynamicQuantities"
22
uuid = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
33
authors = ["MilesCranmer <miles.cranmer@gmail.com> and contributors"]
4-
version = "0.7.5"
4+
version = "0.8.2"
55

66
[deps]
77
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"

‎docs/make.jl‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using DynamicQuantities
2-
import DynamicQuantities.Units
2+
using DynamicQuantities.Units
3+
using DynamicQuantities: constructorof, with_type_parameters, dimension_names
34
using Documenter
45

56
DocMeta.setdocmeta!(DynamicQuantities, :DocTestSetup, :(using DynamicQuantities); recursive=true)

‎docs/src/examples.md‎

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ On your chemistry homework, you are faced with the following problem on the phot
1010
> The energy of the incident UV light is ``7.2 \cdot 10^{-19} \mathrm{J}`` per photon. Calculate the wavelength of the ejected electrons, in nanometers.
1111
1212
Let's solve this problem with `DynamicQuantities.jl`!
13+
1314
```jldoctest examples
1415
julia> using DynamicQuantities
1516
@@ -30,6 +31,356 @@ julia> λ = h / p # wavelength of ejected electrons
3031
julia> uconvert(us"nm", λ) # return answer in nanometers
3132
3.0294912478780556 nm
3233
```
34+
3335
Since units are automatically propagated, we can verify the dimension of our answer and all intermediates.
3436
Also, using `DynamicQuantities.Constants`, we were able to obtain the (dimensionful!) values of all necessary constants without typing them ourselves.
3537

38+
## 2. Projectile motion
39+
40+
Let's solve a simple projectile motion problem.
41+
First load the `DynamicQuantities` module:
42+
43+
```julia
44+
using DynamicQuantities
45+
```
46+
47+
Set up initial conditions as quantities:
48+
49+
```julia
50+
y0 = 10u"km"
51+
v0 = 250u"m/s"
52+
θ = deg2rad(60)
53+
g = 9.81u"m/s^2"
54+
```
55+
56+
Next, we use trig functions to calculate x and y components of initial velocity.
57+
`vx0` is the x component and
58+
`vy0` is the y component:
59+
60+
```julia
61+
vx0 = v0 * cos(θ)
62+
vy0 = v0 * sin(θ)
63+
```
64+
65+
Next, let's create a time vector from 0 seconds to 1.3 minutes.
66+
Note that these are the same dimension (time), so it's fine to treat
67+
them as dimensionally equivalent!
68+
69+
```julia
70+
t = range(0u"s", 1.3u"min", length=100)
71+
```
72+
73+
Next, use kinematic equations to calculate x and y as a function of time.
74+
`x(t)` is the x position at time t, and
75+
`y(t)` is the y position:
76+
77+
```julia
78+
x(t) = vx0*t
79+
y(t) = vy0*t - 0.5*g*t^2 + y0
80+
```
81+
82+
These are functions, so let's evaluate them:
83+
84+
```julia
85+
x_si = x.(t)
86+
y_si = y.(t)
87+
```
88+
89+
These are regular vectors of quantities
90+
with `Dimensions` for physical dimensions.
91+
92+
Next, let's plot the trajectory.
93+
First convert to km and strip units:
94+
95+
```julia
96+
x_km = ustrip.(uconvert(us"km").(x_si))
97+
y_km = ustrip.(uconvert(us"km").(y_si))
98+
```
99+
100+
Now, we plot:
101+
102+
```julia
103+
plot(x_km, y_km, label="Trajectory", xlabel="x [km]", ylabel="y [km]")
104+
```
105+
106+
## 3. Various Simple Examples
107+
108+
This section demonstrates miscellaneous examples of using `DynamicQuantities.jl`.
109+
110+
### Conversion
111+
112+
Convert a quantity to have a new type for the value:
113+
114+
```julia
115+
quantity = 1.5u"m"
116+
117+
convert_q = Quantity{Float32}(quantity)
118+
119+
println("Converted Quantity to Float32: ", convert_q)
120+
```
121+
122+
### Array basics
123+
124+
Create a `QuantityArray` (an array of quantities with
125+
the same dimension) by passing an array and a single quantity:
126+
127+
```julia
128+
x = QuantityArray(randn(32), u"km/s")
129+
```
130+
131+
or, by passing an array of individual quantities:
132+
133+
```julia
134+
y = randn(32)
135+
y_q = QuantityArray(y .* u"m * cd / s")
136+
```
137+
138+
We can take advantage of this being `<:AbstractArray`:
139+
140+
```julia
141+
println("Sum x: ", sum(x))
142+
```
143+
144+
We can also do things like setting a particular element:
145+
146+
```julia
147+
y_q[5] = Quantity(5, length=1, luminosity=1, time=-1)
148+
println("5th element of y_q: ", y_q[5])
149+
```
150+
151+
We can get back the original array with `ustrip`:
152+
153+
```julia
154+
println("Stripped y_q: ", ustrip(y_q))
155+
```
156+
157+
This `QuantityArray` is useful for broadcasting:
158+
159+
```julia
160+
f_square(v) = v^2 * 1.5 - v^2
161+
println("Applying function to y_q: ", sum(f_square.(y_q)))
162+
```
163+
164+
### Fill
165+
166+
We can also make `QuantityArray` using `fill`:
167+
168+
```julia
169+
filled_q = fill(u"m/s", 10)
170+
println("Filled QuantityArray: ", filled_q)
171+
```
172+
173+
`fill` works for 0 dimensional `QuantityArray`s as well:
174+
175+
```julia
176+
empty_q = fill(u"m/s", ())
177+
println("0 dimensional QuantityArray: ", empty_q)
178+
```
179+
180+
### Similar
181+
182+
Likewise, we can create a `QuantityArray` with the same properties as another `QuantityArray`:
183+
184+
```julia
185+
qa = QuantityArray(rand(3, 4), u"m")
186+
187+
new_qa = similar(qa)
188+
189+
println("Similar qa: ", new_qa)
190+
```
191+
192+
### Promotion
193+
194+
Promotion rules are defined for `QuantityArray`s:
195+
196+
```julia
197+
qarr1 = QuantityArray(randn(32), convert(Dimensions{Rational{Int32}}, dimension(u"km/s")))
198+
qarr2 = QuantityArray(randn(Float16, 32), convert(Dimensions{Rational{Int64}}, dimension(u"km/s")))
199+
```
200+
201+
See what type they promote to:
202+
203+
```julia
204+
println("Promoted type: ", typeof(promote(qarr1, qarr2)))
205+
```
206+
207+
### Array Concatenation
208+
209+
Likewise, we can take advantage of array concatenation,
210+
which will ensure we have the same dimensions:
211+
212+
```julia
213+
qarr1 = QuantityArray(randn(3) .* u"km/s")
214+
qarr2 = QuantityArray(randn(3) .* u"km/s")
215+
```
216+
217+
Concatenate them:
218+
219+
```julia
220+
concat_qarr = hcat(qarr1, qarr2)
221+
println("Concatenated QuantityArray: ", concat_qarr)
222+
```
223+
224+
### Symbolic Units
225+
226+
We can use arbitrary `AbstractQuantity` and `AbstractDimensions`
227+
in a `QuantityArray`, including `SymbolicDimensions`:
228+
229+
```julia
230+
z_ar = randn(32)
231+
z = QuantityArray(z_ar, us"Constants.M_sun * km/s")
232+
```
233+
234+
Expand to standard units:
235+
236+
```julia
237+
z_expanded = uexpand(z)
238+
println("Expanded z: ", z_expanded)
239+
```
240+
241+
242+
### GenericQuantity Construction
243+
244+
In addition to `Quantity`, we can also use `GenericQuantity`:
245+
246+
247+
```julia
248+
x = GenericQuantity(1.5)
249+
y = GenericQuantity(0.2u"km")
250+
println(y)
251+
```
252+
253+
This `GenericQuantity` is subtyped to `Any`,
254+
rather than `Number`, and thus can also store
255+
custom non-scalar types.
256+
257+
For example, we can work with `Coords`, and
258+
wrap it in a single `GenericQuantity` type:
259+
260+
```julia
261+
struct Coords
262+
x::Float64
263+
y::Float64
264+
end
265+
266+
# Define arithmetic operations on Coords
267+
Base.:+(a::Coords, b::Coords) = Coords(a.x + b.x, a.y + b.y)
268+
Base.:-(a::Coords, b::Coords) = Coords(a.x - b.x, a.y - b.y)
269+
Base.:*(a::Coords, b::Number) = Coords(a.x * b, a.y * b)
270+
Base.:*(a::Number, b::Coords) = Coords(a * b.x, a * b.y)
271+
Base.:/(a::Coords, b::Number) = Coords(a.x / b, a.y / b)
272+
```
273+
274+
We can then build a `GenericQuantity` out of this:
275+
276+
```julia
277+
coord1 = GenericQuantity(Coords(0.3, 0.9), length=1)
278+
coord2 = GenericQuantity(Coords(0.2, -0.1), length=1)
279+
```
280+
281+
and perform operations on these:
282+
283+
```julia
284+
coord1 + coord2 |> uconvert(us"cm")
285+
# (Coords(50.0, 80.0)) cm
286+
```
287+
288+
The nice part about this is it only stores a single Dimensions
289+
(or `SymbolicDimensions`) for the entire struct!
290+
291+
### GenericQuantity and Quantity Promotion
292+
293+
When we combine a `GenericQuantity` and a `Quantity`,
294+
the result is another `GenericQuantity`:
295+
296+
```julia
297+
x = GenericQuantity(1.5f0)
298+
y = Quantity(1.5, length=1)
299+
println("Promoted type of x and y: ", typeof(x * y))
300+
```
301+
302+
### Custom Dimensions
303+
304+
We can create custom dimensions by subtyping to
305+
`AbstractDimensions`:
306+
307+
```julia
308+
struct MyDimensions{R} <: AbstractDimensions{R}
309+
cookie::R
310+
milk::R
311+
end
312+
```
313+
314+
Many constructors and functions are defined on `AbstractDimensions`,
315+
so this can be used out-of-the-box.
316+
We can then use this in a `Quantity`, and all operations will work as expected:
317+
318+
```julia
319+
x = Quantity(1.5, MyDimensions(cookie=1, milk=-1))
320+
y = Quantity(2.0, MyDimensions(milk=1))
321+
322+
x * y
323+
```
324+
325+
which gives us `3.0 cookie` computed from a rate of `1.5 cookie milk⁻¹` multiplied
326+
by `2.0 milk`. Likewise, we can use these in a `QuantityArray`:
327+
328+
```julia
329+
x_qa = QuantityArray(randn(32), MyDimensions(cookie=1, milk=-1))
330+
331+
x_qa .^ 2
332+
```
333+
334+
### Custom Quantities
335+
336+
We can also create custom dimensions by subtyping
337+
to either `AbstractQuantity` (for `<:Number`) or
338+
`AbstractGenericQuantity` (for `<:Any`):
339+
340+
```julia
341+
struct MyQuantity{T,D} <: AbstractQuantity{T,D}
342+
value::T
343+
dimensions::D
344+
end
345+
```
346+
347+
Since `AbstractQuantity <: Number`, this will also be a number.
348+
Keep in mind that you must call these fields `value` and `dimensions`
349+
for `ustrip(...)` and `dimension(...)` to work. Otherwise, simply
350+
redefine those.
351+
352+
We can use this custom quantity just like we would use `Quantity`:
353+
354+
```julia
355+
q1 = MyQuantity(1.2, Dimensions(length=-2))
356+
# prints as `1.2 m⁻²`
357+
358+
q2 = MyQuantity(1.5, MyDimensions(cookie=1))
359+
# prints as `1.5 cookie`
360+
```
361+
362+
Including mathematical operations:
363+
364+
```julia
365+
q2 ^ 2
366+
# `2.25 cookie²`
367+
```
368+
369+
The main reason you would use a custom quantity is if you want
370+
to change built-in behavior, or maybe have special methods for
371+
different types of quantities.
372+
373+
Note that you can declare a method on `AbstractQuantity`, or
374+
`AbstractGenericQuantity` to allow their respective inputs.
375+
376+
**Note**: In general, you should probably
377+
specialize on `UnionAbstractQuantity` which is
378+
the union of these two abstract quantities, _as well as any other future abstract quantity types_,
379+
such as the planned `AbstractRealQuantity`.
380+
381+
```julia
382+
function my_func(x::UnionAbstractQuantity{T,D}) where {T,D}
383+
# value has type T and dimensions has type D
384+
return x / ustrip(x)
385+
end
386+
```

‎docs/src/types.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,28 @@ SymbolicDimensions
3030
```@docs
3131
QuantityArray
3232
```
33+
34+
## Generic quantities
35+
36+
Whereas `Quantity` is subtyped to `Number`,
37+
a more general type of quantity is `GenericQuantity`,
38+
which is subtyped to `Any`.
39+
40+
```@docs
41+
GenericQuantity
42+
AbstractGenericQuantity
43+
UnionAbstractQuantity
44+
DynamicQuantities.ABSTRACT_QUANTITY_TYPES
45+
```
46+
47+
## Custom behavior in abstract quantities
48+
49+
There are a few functions you may need to overload
50+
when subtyping `AbstractDimensions`, `AbstractQuantity`,
51+
or `AbstractGenericQuantity`.
52+
53+
```@docs
54+
constructorof
55+
with_type_parameters
56+
dimension_names
57+
```
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module DynamicQuantitiesLinearAlgebraExt
22

33
import LinearAlgebra: norm
4-
import DynamicQuantities: AbstractQuantity, ustrip, dimension, new_quantity
4+
import DynamicQuantities: UnionAbstractQuantity, ustrip, dimension, new_quantity
55

6-
norm(q::AbstractQuantity, p::Real=2) = new_quantity(typeof(q), norm(ustrip(q), p), dimension(q))
6+
norm(q::UnionAbstractQuantity, p::Real=2) = new_quantity(typeof(q), norm(ustrip(q), p), dimension(q))
77

88
end
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
module DynamicQuantitiesMeasurementsExt
22

3-
using DynamicQuantities: AbstractQuantity, new_quantity, dimension, ustrip, DimensionError
3+
using DynamicQuantities: UnionAbstractQuantity, new_quantity, dimension, ustrip, DimensionError
44
using Measurements: Measurements, measurement, value, uncertainty
55

6-
function Measurements.measurement(a::Q, b::Q) where {Q<:AbstractQuantity}
6+
function Measurements.measurement(a::Q, b::Q) where {Q<:UnionAbstractQuantity}
77
dimension(a) == dimension(b) || throw(DimensionError(a, b))
88
raw_measurement = measurement(ustrip(a), ustrip(b))
99
return new_quantity(Q, raw_measurement, dimension(a))
1010
end
11-
function Measurements.measurement(a::AbstractQuantity, b::AbstractQuantity)
11+
function Measurements.measurement(a::UnionAbstractQuantity, b::UnionAbstractQuantity)
1212
return measurement(promote(a, b)...)
1313
end
1414

15-
Measurements.value(q::Q) where {Q<:AbstractQuantity} = new_quantity(Q, value(ustrip(q)), dimension(q))
16-
Measurements.uncertainty(q::Q) where {Q<:AbstractQuantity} = new_quantity(Q, uncertainty(ustrip(q)), dimension(q))
15+
Measurements.value(q::Q) where {Q<:UnionAbstractQuantity} = new_quantity(Q, value(ustrip(q)), dimension(q))
16+
Measurements.uncertainty(q::Q) where {Q<:UnionAbstractQuantity} = new_quantity(Q, uncertainty(ustrip(q)), dimension(q))
1717

1818
end
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
module DynamicQuantitiesScientificTypesExt
22

3-
import DynamicQuantities: AbstractQuantity, ustrip
3+
import DynamicQuantities: UnionAbstractQuantity, ustrip
44
import ScientificTypes as ST
55
import ScientificTypes.ScientificTypesBase as STB
66

7-
STB.scitype(x::AbstractQuantity, C::ST.DefaultConvention) = STB.scitype(ustrip(x), C)
8-
STB.Scitype(::Type{<:AbstractQuantity{T}}, C::ST.DefaultConvention) where {T} = STB.Scitype(T, C)
7+
STB.scitype(x::UnionAbstractQuantity, C::ST.DefaultConvention) = STB.scitype(ustrip(x), C)
8+
STB.Scitype(::Type{<:UnionAbstractQuantity{T}}, C::ST.DefaultConvention) where {T} = STB.Scitype(T, C)
99

1010
end

‎ext/DynamicQuantitiesUnitfulExt.jl‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Base.convert(::Type{Unitful.Quantity}, x::DynamicQuantities.Quantity) =
2828
validate_upreferred()
2929
cumulator = DynamicQuantities.ustrip(x)
3030
dims = DynamicQuantities.dimension(x)
31+
if dims isa DynamicQuantities.SymbolicDimensions
32+
throw(ArgumentError("Conversion of a `DynamicQuantities.Quantity` to a `Unitful.Quantity` is not defined with dimensions of type `SymbolicDimensions`. Instead, you can first use the `uexpand` function to convert the dimensions to their base SI form of type `Dimensions`, then convert this quantity to a `Unitful.Quantity`."))
33+
end
3134
equiv = unitful_equivalences()
3235
for dim in keys(dims)
3336
value = dims[dim]

‎src/DynamicQuantities.jl‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module DynamicQuantities
22

33
export Units, Constants
4-
export AbstractQuantity, AbstractDimensions
5-
export Quantity, Dimensions, SymbolicDimensions, QuantityArray, DimensionError
4+
export AbstractDimensions, AbstractQuantity, AbstractGenericQuantity, UnionAbstractQuantity
5+
export Quantity, GenericQuantity, Dimensions, SymbolicDimensions, QuantityArray, DimensionError
66
export ustrip, dimension
77
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
88
export uparse, @u_str, sym_uparse, @us_str, uexpand, uconvert
99

10+
include("internal_utils.jl")
1011
include("fixed_rational.jl")
1112
include("types.jl")
1213
include("utils.jl")
@@ -16,6 +17,7 @@ include("units.jl")
1617
include("constants.jl")
1718
include("uparse.jl")
1819
include("symbolic_dimensions.jl")
20+
include("disambiguities.jl")
1921

2022
include("deprecated.jl")
2123
export expand_units

‎src/arrays.jl‎

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Compat: allequal
22

33
"""
4-
QuantityArray{T,N,D<:AbstractDimensions,Q<:AbstractQuantity,V<:AbstractArray}
4+
QuantityArray{T,N,D<:AbstractDimensions,Q<:UnionAbstractQuantity,V<:AbstractArray}
55
66
An array of quantities with value `value` of type `V` and dimensions `dimensions` of type `D`
77
(which are shared across all elements of the array). This is a subtype of `AbstractArray{Q,N}`,
@@ -14,33 +14,53 @@ and so can be used in most places where a normal array would be used, including
1414
1515
# Constructors
1616
17-
- `QuantityArray(value::AbstractArray, dimensions::AbstractDimensions)`: Create a `QuantityArray` with value `value` and dimensions `dimensions`.
18-
- `QuantityArray(value::AbstractArray, quantity::Quantity)`: Create a `QuantityArray` with value `value` and dimensions inferred
19-
with `dimension(quantity)`. This is so that you can easily create an array with the units module, like so:
17+
- `QuantityArray(v::AbstractArray, d::AbstractDimensions)`: Create a `QuantityArray` with value `v` and dimensions `d`,
18+
using `Quantity` if the eltype of `v` is numeric, and `GenericQuantity` otherwise.
19+
- `QuantityArray(v::AbstractArray{<:Number}, q::AbstractQuantity)`: Create a `QuantityArray` with value `v` and dimensions inferred
20+
with `dimension(q)`. This is so that you can easily create an array with the units module, like so:
2021
```julia
2122
julia> A = QuantityArray(randn(32), 1u"m")
2223
```
23-
- `QuantityArray(v::AbstractArray{<:AbstractQuantity})`: Create a `QuantityArray` from an array of quantities. This means the following
24+
- `QuantityArray(v::AbstractArray{<:Any}, q::AbstractGenericQuantity)`: Create a `QuantityArray` with
25+
value `v` and dimensions inferred with `dimension(q)`.
26+
This is so that you can easily create quantity arrays of non-numeric eltypes, like so:
27+
```julia
28+
julia> A = QuantityArray([[1.0], [2.0, 3.0]], GenericQuantity(1u"m"))
29+
```
30+
- `QuantityArray(v::AbstractArray{<:UnionAbstractQuantity})`: Create a `QuantityArray` from an array of quantities. This means the following
2431
syntax works:
2532
```julia
2633
julia> A = QuantityArray(randn(32) .* 1u"km/s")
2734
```
35+
- `QuantityArray(v::AbstractArray; kws...)`: Create a `QuantityArray` with dimensions inferred from the keyword arguments. For example:
36+
```julia
37+
julia> A = QuantityArray(randn(32); length=1)
38+
```
39+
is equivalent to
40+
```julia
41+
julia> A = QuantityArray(randn(32), u"m")
42+
```
43+
The keyword arguments are passed to `DEFAULT_DIM_TYPE`.
2844
"""
29-
struct QuantityArray{T,N,D<:AbstractDimensions,Q<:AbstractQuantity{T,D},V<:AbstractArray{T,N}} <: AbstractArray{Q,N}
45+
struct QuantityArray{T,N,D<:AbstractDimensions,Q<:UnionAbstractQuantity{T,D},V<:AbstractArray{T,N}} <: AbstractArray{Q,N}
3046
value::V
3147
dimensions::D
3248

33-
function QuantityArray(v::_V, d::_D, ::Type{_Q}) where {_T,_N,_D<:AbstractDimensions,_Q<:AbstractQuantity,_V<:AbstractArray{_T,_N}}
34-
Q_out = constructor_of(_Q){_T,_D}
49+
function QuantityArray(v::_V, d::_D, ::Type{_Q}) where {_T,_N,_D<:AbstractDimensions,_Q<:UnionAbstractQuantity,_V<:AbstractArray{_T,_N}}
50+
Q_out = with_type_parameters(_Q, _T, _D)
3551
return new{_T,_N,_D,Q_out,_V}(v, d)
3652
end
3753
end
3854

3955
# Construct with a Quantity (easier, as you can use the units):
4056
QuantityArray(v::AbstractArray; kws...) = QuantityArray(v, DEFAULT_DIM_TYPE(; kws...))
41-
QuantityArray(v::AbstractArray, d::AbstractDimensions) = QuantityArray(v, d, Quantity)
42-
QuantityArray(v::AbstractArray, q::AbstractQuantity) = QuantityArray(v .* ustrip(q), dimension(q), typeof(q))
43-
QuantityArray(v::QA) where {Q<:AbstractQuantity,QA<:AbstractArray{Q}} =
57+
for (type, base_type, default_type) in ABSTRACT_QUANTITY_TYPES
58+
@eval begin
59+
QuantityArray(v::AbstractArray{<:$base_type}, q::$type) = QuantityArray(v .* ustrip(q), dimension(q), typeof(q))
60+
QuantityArray(v::AbstractArray{<:$base_type}, d::AbstractDimensions) = QuantityArray(v, d, $default_type)
61+
end
62+
end
63+
QuantityArray(v::QA) where {Q<:UnionAbstractQuantity,QA<:AbstractArray{Q}} =
4464
let
4565
allequal(dimension.(v)) || throw(DimensionError(first(v), v))
4666
QuantityArray(ustrip.(v), dimension(first(v)), Q)
@@ -58,7 +78,7 @@ function Base.promote_rule(::Type{QA1}, ::Type{QA2}) where {QA1<:QuantityArray,Q
5878
"Cannot promote quantity arrays with different dimensions."
5979
)
6080
@assert(
61-
Q <: AbstractQuantity{T,D} && V <: AbstractArray{T},
81+
Q <: UnionAbstractQuantity{T,D} && V <: AbstractArray{T},
6282
"Incompatible promotion rules between\n $(QA1)\nand\n $(QA2)\nPlease convert to a common quantity type first."
6383
)
6484

@@ -69,15 +89,15 @@ function Base.convert(::Type{QA}, A::QA) where {QA<:QuantityArray}
6989
return A
7090
end
7191
function Base.convert(::Type{QA1}, A::QA2) where {QA1<:QuantityArray,QA2<:QuantityArray}
72-
Q = quantity_type(QA1)
7392
V = array_type(QA1)
74-
N = ndims(QA1)
75-
76-
raw_array = Base.Fix1(convert, Q).(A)
77-
output = QuantityArray(convert(constructor_of(V){Q,N}, raw_array))
78-
# TODO: This will mess with static arrays
93+
D = dim_type(QA1)
94+
Q = quantity_type(QA1)
7995

80-
return output::QA1
96+
return QuantityArray(
97+
convert(V, ustrip(A)),
98+
convert(D, dimension(A)),
99+
Q,
100+
)::QA1
81101
end
82102

83103
@inline ustrip(A::QuantityArray) = A.value
@@ -92,9 +112,9 @@ quantity_type(A::QuantityArray) = quantity_type(typeof(A))
92112
dim_type(::Type{<:QuantityArray{T,N,D}}) where {T,N,D} = D
93113
dim_type(A::QuantityArray) = dim_type(typeof(A))
94114

95-
value_type(::Type{<:AbstractQuantity{T}}) where {T} = T
115+
value_type(::Type{<:UnionAbstractQuantity{T}}) where {T} = T
96116
value_type(::Type{<:QuantityArray{T}}) where {T} = T
97-
value_type(A::Union{<:QuantityArray,<:AbstractQuantity}) = value_type(typeof(A))
117+
value_type(A::Union{<:QuantityArray,<:UnionAbstractQuantity}) = value_type(typeof(A))
98118

99119
# One field:
100120
for f in (:size, :length, :axes)
@@ -109,11 +129,11 @@ function Base.getindex(A::QuantityArray, i...)
109129
return new_quantity(quantity_type(A), output_value, dimension(A))
110130
end
111131
end
112-
function Base.setindex!(A::QuantityArray{T,N,D,Q}, v::Q, i...) where {T,N,D,Q<:AbstractQuantity}
132+
function Base.setindex!(A::QuantityArray{T,N,D,Q}, v::Q, i...) where {T,N,D,Q<:UnionAbstractQuantity}
113133
dimension(A) == dimension(v) || throw(DimensionError(A, v))
114134
return unsafe_setindex!(A, v, i...)
115135
end
116-
function Base.setindex!(A::QuantityArray{T,N,D,Q}, v::AbstractQuantity, i...) where {T,N,D,Q<:AbstractQuantity}
136+
function Base.setindex!(A::QuantityArray{T,N,D,Q}, v::UnionAbstractQuantity, i...) where {T,N,D,Q<:UnionAbstractQuantity}
117137
return setindex!(A, convert(Q, v), i...)
118138
end
119139

@@ -134,7 +154,7 @@ end
134154

135155
Base.BroadcastStyle(::Type{QA}) where {QA<:QuantityArray} = Broadcast.ArrayStyle{QA}()
136156

137-
function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{QA}}, ::Type{ElType}) where {QA<:QuantityArray,ElType<:AbstractQuantity}
157+
function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{QA}}, ::Type{ElType}) where {QA<:QuantityArray,ElType<:UnionAbstractQuantity}
138158
T = value_type(ElType)
139159
output_array = similar(bc, T)
140160
first_output::ElType = materialize_first(bc)
@@ -156,10 +176,10 @@ end
156176
materialize_first(bc::Base.Broadcast.Broadcasted) = bc.f(materialize_first.(bc.args)...)
157177

158178
# Base cases
159-
materialize_first(q::AbstractQuantity{<:AbstractArray}) = new_quantity(typeof(q), first(ustrip(q)), dimension(q))
160-
materialize_first(q::AbstractQuantity) = q
179+
materialize_first(q::AbstractGenericQuantity{<:AbstractArray}) = new_quantity(typeof(q), first(ustrip(q)), dimension(q))
180+
materialize_first(q::UnionAbstractQuantity) = q
161181
materialize_first(q::QuantityArray) = first(q)
162-
materialize_first(q::AbstractArray{Q}) where {Q<:AbstractQuantity} = first(q)
182+
materialize_first(q::AbstractArray{Q}) where {Q<:UnionAbstractQuantity} = first(q)
163183

164184
# Derived calls
165185
materialize_first(r::Base.RefValue) = materialize_first(r.x)
@@ -202,8 +222,8 @@ for f in (:cat, :hcat, :vcat)
202222
end
203223
end
204224
end
205-
Base.fill(x::AbstractQuantity, dims::Dims...) = QuantityArray(fill(ustrip(x), dims...), dimension(x), typeof(x))
206-
Base.fill(x::AbstractQuantity, t::Tuple{}) = QuantityArray(fill(ustrip(x), t), dimension(x), typeof(x))
225+
Base.fill(x::UnionAbstractQuantity, dims::Dims...) = QuantityArray(fill(ustrip(x), dims...), dimension(x), typeof(x))
226+
Base.fill(x::UnionAbstractQuantity, t::Tuple{}) = QuantityArray(fill(ustrip(x), t), dimension(x), typeof(x))
207227

208228
ulength(q::QuantityArray) = ulength(dimension(q))
209229
umass(q::QuantityArray) = umass(dimension(q))

‎src/disambiguities.jl‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Base.isless(::AbstractQuantity, ::Missing) = missing
2+
Base.isless(::Missing, ::AbstractQuantity) = missing
3+
Base.:(==)(::AbstractQuantity, ::Missing) = missing
4+
Base.:(==)(::Missing, ::AbstractQuantity) = missing
5+
Base.isapprox(::AbstractQuantity, ::Missing; kws...) = missing
6+
Base.isapprox(::Missing, ::AbstractQuantity; kws...) = missing
7+
8+
Base.:(==)(::AbstractQuantity, ::WeakRef) = error("Cannot compare a quantity to a weakref")
9+
Base.:(==)(::WeakRef, ::AbstractQuantity) = error("Cannot compare a weakref to a quantity")
10+
11+
Base.:*(l::AbstractDimensions, r::Number) = error("Please use an `UnionAbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
12+
Base.:*(l::Number, r::AbstractDimensions) = error("Please use an `UnionAbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
13+
Base.:/(l::AbstractDimensions, r::Number) = error("Please use an `UnionAbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")
14+
Base.:/(l::Number, r::AbstractDimensions) = error("Please use an `UnionAbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")

‎src/fixed_rational.jl‎

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,22 @@ end
5555
Base.iszero(x::FixedRational) = iszero(x.num)
5656
Base.isone(x::F) where {F<:FixedRational} = x.num == denom(F)
5757
Base.isinteger(x::F) where {F<:FixedRational} = iszero(x.num % denom(F))
58-
Base.convert(::Type{F}, x::Integer) where {F<:FixedRational} = unsafe_fixed_rational(x * denom(F), eltype(F), val_denom(F))
59-
Base.convert(::Type{F}, x::Rational) where {F<:FixedRational} = F(x)
60-
Base.convert(::Type{Rational{R}}, x::F) where {R,F<:FixedRational} = Rational{R}(x.num, denom(F))
61-
Base.convert(::Type{Rational}, x::F) where {F<:FixedRational} = Rational{eltype(F)}(x.num, denom(F))
62-
Base.convert(::Type{AF}, x::F) where {AF<:AbstractFloat,F<:FixedRational} = convert(AF, x.num) / convert(AF, denom(F))
63-
Base.convert(::Type{I}, x::F) where {I<:Integer,F<:FixedRational} =
58+
59+
Rational{R}(x::F) where {R,F<:FixedRational} = Rational{R}(x.num, denom(F))
60+
Rational(x::F) where {F<:FixedRational} = Rational{eltype(F)}(x)
61+
(::Type{AF})(x::F) where {AF<:AbstractFloat,F<:FixedRational} = convert(AF, x.num) / convert(AF, denom(F))
62+
(::Type{I})(x::F) where {I<:Integer,F<:FixedRational} =
6463
let
6564
isinteger(x) || throw(InexactError(:convert, I, x))
6665
convert(I, div(x.num, denom(F)))
6766
end
68-
Base.round(::Type{T}, x::F) where {T,F<:FixedRational} = div(convert(T, x.num), convert(T, denom(F)), RoundNearest)
67+
(::Type{Bool})(x::F) where {F<:FixedRational} =
68+
let
69+
iszero(x) || isone(x) || throw(InexactError(:convert, Bool, x))
70+
return x.num == denom(F)
71+
end
72+
73+
Base.round(::Type{T}, x::F, r::RoundingMode=RoundNearest) where {T,F<:FixedRational} = div(convert(T, x.num), convert(T, denom(F)), r)
6974
Base.decompose(x::F) where {T,F<:FixedRational{T}} = (x.num, zero(T), denom(F))
7075

7176
# Promotion rules:
@@ -78,13 +83,13 @@ end
7883
function Base.promote_rule(::Type{<:FixedRational{T1}}, ::Type{Rational{T2}}) where {T1,T2}
7984
return Rational{promote_type(T1,T2)}
8085
end
81-
function Base.promote_rule(::Type{<:FixedRational{T1}}, ::Type{T2}) where {T1,T2}
86+
function Base.promote_rule(::Type{<:FixedRational{T1}}, ::Type{T2}) where {T1,T2<:Real}
8287
return promote_type(Rational{T1}, T2)
8388
end
84-
85-
# Want to consume integers:
86-
Base.promote(x::Integer, y::F) where {F<:FixedRational} = (F(x), y)
87-
Base.promote(x::F, y::Integer) where {F<:FixedRational} = reverse(promote(y, x))
89+
function Base.promote_rule(::Type{F}, ::Type{<:Integer}) where {F<:FixedRational}
90+
# Want to consume integers:
91+
return F
92+
end
8893

8994
Base.string(x::FixedRational) =
9095
let
@@ -93,11 +98,10 @@ Base.string(x::FixedRational) =
9398
return string(div(x.num, g)) * "//" * string(div(denom(x), g))
9499
end
95100
Base.show(io::IO, x::FixedRational) = print(io, string(x))
96-
Base.zero(::Type{F}) where {F<:FixedRational} = unsafe_fixed_rational(0, eltype(F), val_denom(F))
97101

98102
tryrationalize(::Type{F}, x::F) where {F<:FixedRational} = x
99103
tryrationalize(::Type{F}, x::Union{Rational,Integer}) where {F<:FixedRational} = convert(F, x)
100104
tryrationalize(::Type{F}, x) where {F<:FixedRational} = unsafe_fixed_rational(round(eltype(F), x * denom(F)), eltype(F), val_denom(F))
101105

102106
# Fix method ambiguities
103-
Base.round(::Type{T}, ::F) where {T>:Missing, F<:FixedRational} = missing
107+
Base.round(::Type{T}, x::F, r::RoundingMode=RoundNearest) where {T>:Missing, F<:FixedRational} = round(Base.nonmissingtype_checked(T), x, r)

‎src/internal_utils.jl‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
This file contains utility functions that are not specific to the
3+
library, but are used throughout.
4+
"""
5+
6+
const SUPERSCRIPT_MAPPING = ('', '¹', '²', '³', '', '', '', '', '', '')
7+
const INTCHARS = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
8+
9+
function to_superscript(s::AbstractString)
10+
chars = map(replace(s, "//" => "")) do c
11+
if c INTCHARS
12+
SUPERSCRIPT_MAPPING[parse(Int, c)+1]
13+
elseif c == '-'
14+
''
15+
else
16+
c
17+
end
18+
end
19+
return join(chars)
20+
end

‎src/math.jl‎

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,54 @@
1-
Base.:*(l::AbstractDimensions, r::AbstractDimensions) = map_dimensions(+, l, r)
2-
Base.:*(l::AbstractQuantity, r::AbstractQuantity) = new_quantity(typeof(l), ustrip(l) * ustrip(r), dimension(l) * dimension(r))
3-
Base.:*(l::AbstractQuantity, r::AbstractDimensions) = new_quantity(typeof(l), ustrip(l), dimension(l) * r)
4-
Base.:*(l::AbstractDimensions, r::AbstractQuantity) = new_quantity(typeof(r), ustrip(r), l * dimension(r))
5-
Base.:*(l::AbstractQuantity, r) = new_quantity(typeof(l), ustrip(l) * r, dimension(l))
6-
Base.:*(l, r::AbstractQuantity) = new_quantity(typeof(r), l * ustrip(r), dimension(r))
7-
Base.:*(l::AbstractDimensions, r) = error("Please use an `AbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
8-
Base.:*(l, r::AbstractDimensions) = error("Please use an `AbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
1+
for (type, base_type, _) in ABSTRACT_QUANTITY_TYPES
2+
@eval begin
3+
Base.:*(l::$type, r::$type) = new_quantity(typeof(l), ustrip(l) * ustrip(r), dimension(l) * dimension(r))
4+
Base.:/(l::$type, r::$type) = new_quantity(typeof(l), ustrip(l) / ustrip(r), dimension(l) / dimension(r))
95

10-
Base.:/(l::AbstractDimensions, r::AbstractDimensions) = map_dimensions(-, l, r)
11-
Base.:/(l::AbstractQuantity, r::AbstractQuantity) = new_quantity(typeof(l), ustrip(l) / ustrip(r), dimension(l) / dimension(r))
12-
Base.:/(l::AbstractQuantity, r::AbstractDimensions) = new_quantity(typeof(l), ustrip(l), dimension(l) / r)
13-
Base.:/(l::AbstractDimensions, r::AbstractQuantity) = new_quantity(typeof(r), inv(ustrip(r)), l / dimension(r))
14-
Base.:/(l::AbstractQuantity, r) = new_quantity(typeof(l), ustrip(l) / r, dimension(l))
15-
Base.:/(l, r::AbstractQuantity) = l * inv(r)
16-
Base.:/(l::AbstractDimensions, r) = error("Please use an `AbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")
17-
Base.:/(l, r::AbstractDimensions) = error("Please use an `AbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")
6+
Base.:*(l::$type, r::$base_type) = new_quantity(typeof(l), ustrip(l) * r, dimension(l))
7+
Base.:/(l::$type, r::$base_type) = new_quantity(typeof(l), ustrip(l) / r, dimension(l))
188

19-
Base.:+(l::AbstractQuantity, r::AbstractQuantity) =
20-
let
21-
dimension(l) == dimension(r) || throw(DimensionError(l, r))
22-
new_quantity(typeof(l), ustrip(l) + ustrip(r), dimension(l))
23-
end
24-
Base.:-(l::AbstractQuantity) = new_quantity(typeof(l), -ustrip(l), dimension(l))
25-
Base.:-(l::AbstractQuantity, r::AbstractQuantity) = l + (-r)
9+
Base.:*(l::$base_type, r::$type) = new_quantity(typeof(r), l * ustrip(r), dimension(r))
10+
Base.:/(l::$base_type, r::$type) = new_quantity(typeof(r), l / ustrip(r), inv(dimension(r)))
2611

27-
Base.:+(l::AbstractQuantity, r) =
28-
let
29-
iszero(dimension(l)) || throw(DimensionError(l, r))
30-
new_quantity(typeof(l), ustrip(l) + r, dimension(l))
12+
Base.:*(l::$type, r::AbstractDimensions) = new_quantity(typeof(l), ustrip(l), dimension(l) * r)
13+
Base.:/(l::$type, r::AbstractDimensions) = new_quantity(typeof(l), ustrip(l), dimension(l) / r)
14+
15+
Base.:*(l::AbstractDimensions, r::$type) = new_quantity(typeof(r), ustrip(r), l * dimension(r))
16+
Base.:/(l::AbstractDimensions, r::$type) = new_quantity(typeof(r), inv(ustrip(r)), l / dimension(r))
3117
end
32-
Base.:+(l, r::AbstractQuantity) =
33-
let
34-
iszero(dimension(r)) || throw(DimensionError(l, r))
35-
new_quantity(typeof(r), l + ustrip(r), dimension(r))
18+
end
19+
20+
Base.:*(l::AbstractDimensions, r::AbstractDimensions) = map_dimensions(+, l, r)
21+
Base.:/(l::AbstractDimensions, r::AbstractDimensions) = map_dimensions(-, l, r)
22+
23+
# Defines + and -
24+
for (type, base_type, _) in ABSTRACT_QUANTITY_TYPES, op in (:+, :-)
25+
@eval begin
26+
function Base.$op(l::$type, r::$type)
27+
dimension(l) == dimension(r) || throw(DimensionError(l, r))
28+
return new_quantity(typeof(l), $op(ustrip(l), ustrip(r)), dimension(l))
29+
end
30+
function Base.$op(l::$type, r::$base_type)
31+
iszero(dimension(l)) || throw(DimensionError(l, r))
32+
return new_quantity(typeof(l), $op(ustrip(l), r), dimension(l))
33+
end
34+
function Base.$op(l::$base_type, r::$type)
35+
iszero(dimension(r)) || throw(DimensionError(l, r))
36+
return new_quantity(typeof(r), $op(l, ustrip(r)), dimension(r))
37+
end
3638
end
37-
Base.:-(l::AbstractQuantity, r) = l + (-r)
38-
Base.:-(l, r::AbstractQuantity) = l + (-r)
39+
end
40+
41+
Base.:-(l::UnionAbstractQuantity) = new_quantity(typeof(l), -ustrip(l), dimension(l))
42+
43+
# Combining different abstract types
44+
for op in (:*, :/, :+, :-),
45+
(t1, _, _) in ABSTRACT_QUANTITY_TYPES,
46+
(t2, _, _) in ABSTRACT_QUANTITY_TYPES
47+
48+
t1 == t2 && continue
49+
50+
@eval Base.$op(l::$t1, r::$t2) = $op(promote(l, r)...)
51+
end
3952

4053
# We don't promote on the dimension types:
4154
function Base.:^(l::AbstractDimensions{R}, r::Integer) where {R}
@@ -57,26 +70,33 @@ for (p, ex) in [
5770
@eval @inline Base.literal_pow(::typeof(^), l::AbstractDimensions, ::Val{$p}) = $ex
5871
end
5972

60-
function Base.:^(l::AbstractQuantity{T,D}, r::Integer) where {T,R,D<:AbstractDimensions{R}}
73+
function _pow_int(l::UnionAbstractQuantity{T,D}, r) where {T,R,D<:AbstractDimensions{R}}
6174
return new_quantity(typeof(l), ustrip(l)^r, dimension(l)^r)
6275
end
63-
function Base.:^(l::AbstractQuantity{T,D}, r::Number) where {T,R,D<:AbstractDimensions{R}}
76+
function _pow(l::UnionAbstractQuantity{T,D}, r) where {T,R,D<:AbstractDimensions{R}}
6477
dim_pow = tryrationalize(R, r)
6578
val_pow = convert(T, dim_pow)
6679
# Need to ensure we take the numerical power by the rationalized quantity:
6780
return new_quantity(typeof(l), ustrip(l)^val_pow, dimension(l)^dim_pow)
6881
end
82+
for (type, _, _) in ABSTRACT_QUANTITY_TYPES
83+
@eval begin
84+
Base.:^(l::$type, r::Integer) = _pow_int(l, r)
85+
Base.:^(l::$type, r::Number) = _pow(l, r)
86+
Base.:^(l::$type, r::Rational) = _pow(l, r)
87+
end
88+
end
6989
@inline Base.literal_pow(::typeof(^), l::AbstractDimensions, ::Val{p}) where {p} = map_dimensions(Base.Fix1(*, p), l)
70-
@inline Base.literal_pow(::typeof(^), l::AbstractQuantity, ::Val{p}) where {p} = new_quantity(typeof(l), Base.literal_pow(^, ustrip(l), Val(p)), Base.literal_pow(^, dimension(l), Val(p)))
90+
@inline Base.literal_pow(::typeof(^), l::UnionAbstractQuantity, ::Val{p}) where {p} = new_quantity(typeof(l), Base.literal_pow(^, ustrip(l), Val(p)), Base.literal_pow(^, dimension(l), Val(p)))
7191

7292
Base.inv(d::AbstractDimensions) = map_dimensions(-, d)
73-
Base.inv(q::AbstractQuantity) = new_quantity(typeof(q), inv(ustrip(q)), inv(dimension(q)))
93+
Base.inv(q::UnionAbstractQuantity) = new_quantity(typeof(q), inv(ustrip(q)), inv(dimension(q)))
7494

7595
Base.sqrt(d::AbstractDimensions{R}) where {R} = d^inv(convert(R, 2))
76-
Base.sqrt(q::AbstractQuantity) = new_quantity(typeof(q), sqrt(ustrip(q)), sqrt(dimension(q)))
96+
Base.sqrt(q::UnionAbstractQuantity) = new_quantity(typeof(q), sqrt(ustrip(q)), sqrt(dimension(q)))
7797
Base.cbrt(d::AbstractDimensions{R}) where {R} = d^inv(convert(R, 3))
78-
Base.cbrt(q::AbstractQuantity) = new_quantity(typeof(q), cbrt(ustrip(q)), cbrt(dimension(q)))
98+
Base.cbrt(q::UnionAbstractQuantity) = new_quantity(typeof(q), cbrt(ustrip(q)), cbrt(dimension(q)))
7999

80-
Base.abs(q::AbstractQuantity) = new_quantity(typeof(q), abs(ustrip(q)), dimension(q))
81-
Base.abs2(q::AbstractQuantity) = new_quantity(typeof(q), abs2(ustrip(q)), dimension(q)^2)
82-
Base.angle(q::AbstractQuantity{T}) where {T<:Complex} = angle(ustrip(q))
100+
Base.abs(q::UnionAbstractQuantity) = new_quantity(typeof(q), abs(ustrip(q)), dimension(q))
101+
Base.abs2(q::UnionAbstractQuantity) = new_quantity(typeof(q), abs2(ustrip(q)), dimension(q)^2)
102+
Base.angle(q::UnionAbstractQuantity{T}) where {T<:Complex} = angle(ustrip(q))

‎src/symbolic_dimensions.jl‎

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct SymbolicDimensions{R} <: AbstractDimensions{R}
3939
nzvals::Vector{R}
4040
end
4141

42-
static_fieldnames(::Type{<:SymbolicDimensions}) = ALL_SYMBOLS
42+
@inline dimension_names(::Type{<:SymbolicDimensions}) = ALL_SYMBOLS
4343
function Base.getproperty(d::SymbolicDimensions{R}, s::Symbol) where {R}
4444
nzdims = getfield(d, :nzdims)
4545
i = get(ALL_MAPPING, s, INDEX_TYPE(0))
@@ -53,7 +53,9 @@ function Base.getproperty(d::SymbolicDimensions{R}, s::Symbol) where {R}
5353
end
5454
Base.propertynames(::SymbolicDimensions) = ALL_SYMBOLS
5555
Base.getindex(d::SymbolicDimensions, k::Symbol) = getproperty(d, k)
56-
constructor_of(::Type{<:SymbolicDimensions}) = SymbolicDimensions
56+
57+
constructorof(::Type{<:SymbolicDimensions}) = SymbolicDimensions
58+
with_type_parameters(::Type{<:SymbolicDimensions}, ::Type{R}) where {R} = SymbolicDimensions{R}
5759

5860
SymbolicDimensions{R}(d::SymbolicDimensions) where {R} = SymbolicDimensions{R}(getfield(d, :nzdims), convert(Vector{R}, getfield(d, :nzvals)))
5961
SymbolicDimensions(; kws...) = SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}(; kws...)
@@ -68,61 +70,66 @@ function SymbolicDimensions{R}(; kws...) where {R}
6870
end
6971
(::Type{<:SymbolicDimensions})(::Type{R}; kws...) where {R} = SymbolicDimensions{R}(; kws...)
7072

71-
function Base.convert(::Type{Quantity{T,SymbolicDimensions}}, q::Quantity{<:Any,<:Dimensions}) where {T}
72-
return convert(Quantity{T,SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}}, q)
73-
end
74-
function Base.convert(::Type{Quantity{T,SymbolicDimensions{R}}}, q::Quantity{<:Any,<:Dimensions}) where {T,R}
75-
syms = (:m, :kg, :s, :A, :K, :cd, :mol)
76-
vals = (ulength(q), umass(q), utime(q), ucurrent(q), utemperature(q), uluminosity(q), uamount(q))
77-
I = INDEX_TYPE[ALL_MAPPING[s] for (s, v) in zip(syms, vals) if !iszero(v)]
78-
V = R[tryrationalize(R, v) for v in vals if !iszero(v)]
79-
p = sortperm(I)
80-
permute!(I, p)
81-
permute!(V, p)
82-
dims = SymbolicDimensions{R}(I, V)
83-
return Quantity(convert(T, ustrip(q)), dims)
84-
end
85-
function Base.convert(::Type{Quantity{T,D}}, q::Quantity{<:Any,<:SymbolicDimensions}) where {T,D<:Dimensions}
86-
result = Quantity(T(ustrip(q)), D())
87-
d = dimension(q)
88-
for (idx, value) in zip(getfield(d, :nzdims), getfield(d, :nzvals))
89-
if !iszero(value)
90-
result = result * convert(Quantity{T,D}, ALL_VALUES[idx]) ^ value
73+
for (type, _, _) in ABSTRACT_QUANTITY_TYPES
74+
@eval begin
75+
function Base.convert(::Type{Q}, q::UnionAbstractQuantity{<:Any,<:Dimensions}) where {T,Q<:$type{T,SymbolicDimensions}}
76+
return convert(with_type_parameters(Q, T,SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}), q)
77+
end
78+
function Base.convert(::Type{Q}, q::UnionAbstractQuantity{<:Any,<:Dimensions}) where {T,R,Q<:$type{T,SymbolicDimensions{R}}}
79+
syms = (:m, :kg, :s, :A, :K, :cd, :mol)
80+
vals = (ulength(q), umass(q), utime(q), ucurrent(q), utemperature(q), uluminosity(q), uamount(q))
81+
I = INDEX_TYPE[ALL_MAPPING[s] for (s, v) in zip(syms, vals) if !iszero(v)]
82+
V = R[tryrationalize(R, v) for v in vals if !iszero(v)]
83+
p = sortperm(I)
84+
permute!(I, p)
85+
permute!(V, p)
86+
dims = SymbolicDimensions{R}(I, V)
87+
return constructorof(Q)(convert(T, ustrip(q)), dims)
88+
end
89+
function Base.convert(::Type{Q}, q::UnionAbstractQuantity{<:Any,<:SymbolicDimensions}) where {T,D<:Dimensions,Q<:$type{T,D}}
90+
result = constructorof(Q)(convert(T, ustrip(q)), D())
91+
d = dimension(q)
92+
for (idx, value) in zip(getfield(d, :nzdims), getfield(d, :nzvals))
93+
if !iszero(value)
94+
result = result * convert(with_type_parameters(Q, T, D), ALL_VALUES[idx]) ^ value
95+
end
96+
end
97+
return result
9198
end
9299
end
93-
return result
94100
end
95101

102+
96103
"""
97-
uexpand(q::Quantity{<:Any,<:SymbolicDimensions})
104+
uexpand(q::UnionAbstractQuantity{<:Any,<:SymbolicDimensions})
98105
99106
Expand the symbolic units in a quantity to their base SI form.
100107
In other words, this converts a `Quantity` with `SymbolicDimensions`
101108
to one with `Dimensions`. The opposite of this function is `uconvert`,
102109
for converting to specific symbolic units, or `convert(Quantity{<:Any,<:SymbolicDimensions}, q)`,
103110
for assuming SI units as the output symbols.
104111
"""
105-
function uexpand(q::Q) where {T,R,D<:SymbolicDimensions{R},Q<:AbstractQuantity{T,D}}
106-
return convert(constructor_of(Q){T,Dimensions{R}}, q)
112+
function uexpand(q::Q) where {T,R,D<:SymbolicDimensions{R},Q<:UnionAbstractQuantity{T,D}}
113+
return convert(with_type_parameters(Q, T, Dimensions{R}), q)
107114
end
108115
uexpand(q::QuantityArray) = uexpand.(q)
109116
# TODO: Make the array-based one more efficient
110117

111118
"""
112-
uconvert(qout::AbstractQuantity{<:Any, <:SymbolicDimensions}, q::AbstractQuantity{<:Any, <:Dimensions})
119+
uconvert(qout::UnionAbstractQuantity{<:Any, <:SymbolicDimensions}, q::UnionAbstractQuantity{<:Any, <:Dimensions})
113120
114121
Convert a quantity `q` with base SI units to the symbolic units of `qout`, for `q` and `qout` with compatible units.
115122
Mathematically, the result has value `q / uexpand(qout)` and units `dimension(qout)`.
116123
"""
117-
function uconvert(qout::AbstractQuantity{<:Any, <:SymbolicDimensions}, q::AbstractQuantity{<:Any, <:Dimensions})
124+
function uconvert(qout::UnionAbstractQuantity{<:Any, <:SymbolicDimensions}, q::UnionAbstractQuantity{<:Any, <:Dimensions})
118125
@assert isone(ustrip(qout)) "You passed a quantity with a non-unit value to uconvert."
119126
qout_expanded = uexpand(qout)
120127
dimension(q) == dimension(qout_expanded) || throw(DimensionError(q, qout_expanded))
121128
new_val = ustrip(q) / ustrip(qout_expanded)
122129
new_dim = dimension(qout)
123130
return new_quantity(typeof(q), new_val, new_dim)
124131
end
125-
function uconvert(qout::AbstractQuantity{<:Any,<:SymbolicDimensions}, q::QuantityArray{<:Any,<:Any,<:Dimensions})
132+
function uconvert(qout::UnionAbstractQuantity{<:Any,<:SymbolicDimensions}, q::QuantityArray{<:Any,<:Any,<:Dimensions})
126133
@assert isone(ustrip(qout)) "You passed a quantity with a non-unit value to uconvert."
127134
qout_expanded = uexpand(qout)
128135
dimension(q) == dimension(qout_expanded) || throw(DimensionError(q, qout_expanded))
@@ -133,12 +140,12 @@ end
133140
# TODO: Method for converting SymbolicDimensions -> SymbolicDimensions
134141

135142
"""
136-
uconvert(qout::AbstractQuantity{<:Any, <:SymbolicDimensions})
143+
uconvert(qout::UnionAbstractQuantity{<:Any, <:SymbolicDimensions})
137144
138145
Create a function that converts an input quantity `q` with base SI units to the symbolic units of `qout`, i.e
139146
a function equivalent to `q -> uconvert(qout, q)`.
140147
"""
141-
uconvert(qout::AbstractQuantity{<:Any, <:SymbolicDimensions}) = Base.Fix1(uconvert, qout)
148+
uconvert(qout::UnionAbstractQuantity{<:Any, <:SymbolicDimensions}) = Base.Fix1(uconvert, qout)
142149

143150
Base.copy(d::SymbolicDimensions) = SymbolicDimensions(copy(getfield(d, :nzdims)), copy(getfield(d, :nzvals)))
144151
function Base.:(==)(l::SymbolicDimensions, r::SymbolicDimensions)
@@ -382,3 +389,10 @@ namespace collisions, a few physical constants are automatically converted.
382389
macro us_str(s)
383390
return esc(SymbolicUnitsParse.sym_uparse(s))
384391
end
392+
393+
function Base.promote_rule(::Type{SymbolicDimensions{R1}}, ::Type{SymbolicDimensions{R2}}) where {R1,R2}
394+
return SymbolicDimensions{promote_type(R1,R2)}
395+
end
396+
function Base.promote_rule(::Type{SymbolicDimensions{R1}}, ::Type{Dimensions{R2}}) where {R1,R2}
397+
return Dimensions{promote_type(R1,R2)}
398+
end

‎src/types.jl‎

Lines changed: 140 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Tricks: static_fieldnames, static_fieldtypes
1+
using Tricks: static_fieldnames
22

33
const DEFAULT_DIM_BASE_TYPE = FixedRational{DEFAULT_NUMERATOR_TYPE,DEFAULT_DENOM}
44
const DEFAULT_VALUE_TYPE = Float64
@@ -8,7 +8,7 @@ const DEFAULT_VALUE_TYPE = Float64
88
99
An abstract type for dimension types. `R` is the type of the exponents of the dimensions,
1010
and by default is set to `DynamicQuantities.DEFAULT_DIM_BASE_TYPE`.
11-
AbstractDimensions are used to store the dimensions of `AbstractQuantity` objects.
11+
AbstractDimensions are used to store the dimensions of `UnionAbstractQuantity` objects.
1212
Together these enable many operators in Base to manipulate dimensions.
1313
This type has generic constructors for creating dimension objects, so user-defined
1414
dimension types can be created by simply subtyping `AbstractDimensions`, without
@@ -17,23 +17,54 @@ the need to define many other functions.
1717
The key function that one could wish to overload is
1818
`DynamicQuantities.dimension_name(::AbstractDimensions, k::Symbol)` for mapping from a field name
1919
to a base unit (e.g., `length` by default maps to `m`). You may also need to overload
20-
`DynamicQuantities.constructor_of(::Type{T})` in case of non-standard construction.
20+
`constructorof(::Type{T})` in case of non-standard construction.
2121
"""
2222
abstract type AbstractDimensions{R} end
2323

2424
"""
25-
AbstractQuantity{T,D}
25+
AbstractQuantity{T,D} <: Number
2626
2727
An abstract type for quantities. `T` is the type of the value of the quantity,
28-
and `D` is the type of the dimensions of the quantity. By default, `D` is set to
28+
which should be `<:Number`.
29+
`D` is the type of the dimensions of the quantity. By default, `D` is set to
2930
`DynamicQuantities.DEFAULT_DIM_TYPE`. `T` is inferred from the value in a calculation,
3031
but in other cases is defaulted to `DynamicQuantities.DEFAULT_VALUE_TYPE`.
3132
It is assumed that the value is stored in the `:value` field, and the dimensions
3233
object is stored in the `:dimensions` field. These fields can be accessed with
3334
`ustrip` and `dimension`, respectively. Many operators in `Base` are defined on
3435
`AbstractQuantity` objects, including `+, -, *, /, ^, sqrt, cbrt, abs`.
36+
37+
See also `AbstractGenericQuantity` for creating quantities subtyped to `Any`.
38+
39+
**Note**: In general, you should probably
40+
specialize on `UnionAbstractQuantity` which is
41+
the union of both `AbstractQuantity` and `AbstractGenericQuantity`,
42+
_as well as any other future abstract quantity types_,
43+
"""
44+
abstract type AbstractQuantity{T,D} <: Number end
45+
46+
"""
47+
AbstractGenericQuantity{T,D} <: Any
48+
49+
This has the same behavior as `AbstractQuantity` but is subtyped to `Any` rather
50+
than `Number`.
51+
52+
**Note**: In general, you should probably
53+
specialize on `UnionAbstractQuantity` which is
54+
the union of both `AbstractQuantity` and `AbstractGenericQuantity`,
55+
_as well as any other future abstract quantity types_,
56+
"""
57+
abstract type AbstractGenericQuantity{T,D} end
58+
59+
"""
60+
UnionAbstractQuantity{T,D}
61+
62+
This is a union of both `AbstractQuantity{T,D}` and `AbstractGenericQuantity{T,D}`.
63+
It is used throughout the library to declare methods which can take both types.
64+
You should generally specialize on this type, rather than its constituents,
65+
as it will also include future abstract quantity types.
3566
"""
36-
abstract type AbstractQuantity{T,D} end
67+
const UnionAbstractQuantity{T,D} = Union{AbstractQuantity{T,D},AbstractGenericQuantity{T,D}}
3768

3869
"""
3970
Dimensions{R<:Real} <: AbstractDimensions{R}
@@ -56,7 +87,7 @@ which is by default a rational number.
5687
5788
# Constructors
5889
59-
- `Dimensions(args...)`: Pass all the dimensions as arguments. `R` is set to `DEFAULT_DIM_BASE_TYPE`.
90+
- `Dimensions(args...)`: Pass all the dimensions as arguments.
6091
- `Dimensions(; kws...)`: Pass a subset of dimensions as keyword arguments. `R` is set to `DEFAULT_DIM_BASE_TYPE`.
6192
- `Dimensions(::Type{R}; kws...)` or `Dimensions{R}(; kws...)`: Pass a subset of dimensions as keyword arguments, with the output type set to `Dimensions{R}`.
6293
- `Dimensions{R}()`: Create a dimensionless object typed as `Dimensions{R}`.
@@ -72,15 +103,19 @@ struct Dimensions{R<:Real} <: AbstractDimensions{R}
72103
amount::R
73104
end
74105

75-
(::Type{D})(::Type{R}; kws...) where {R,D<:AbstractDimensions} = constructor_of(D){R}((tryrationalize(R, get(kws, k, zero(R))) for k in static_fieldnames(D))...)
76-
(::Type{D})(; kws...) where {R,D<:AbstractDimensions{R}} = constructor_of(D)(R; kws...)
106+
(::Type{D})(::Type{R}; kws...) where {R,D<:AbstractDimensions} = with_type_parameters(D, R)((tryrationalize(R, get(kws, k, zero(R))) for k in dimension_names(D))...)
107+
(::Type{D})(; kws...) where {R,D<:AbstractDimensions{R}} = constructorof(D)(R; kws...)
77108
(::Type{D})(; kws...) where {D<:AbstractDimensions} = D(DEFAULT_DIM_BASE_TYPE; kws...)
78-
(::Type{D})(d::AbstractDimensions) where {R,D<:AbstractDimensions{R}} = D((getproperty(d, k) for k in static_fieldnames(D))...)
109+
function (::Type{D})(d::D2) where {R,D<:AbstractDimensions{R},D2<:AbstractDimensions}
110+
dimension_names_equal(D, D2) ||
111+
error("Cannot create a dimensions of `$(D)` from `$(D2)`. Please write a custom method for construction.")
112+
D((getproperty(d, k) for k in dimension_names(D))...)
113+
end
79114

80115
const DEFAULT_DIM_TYPE = Dimensions{DEFAULT_DIM_BASE_TYPE}
81116

82117
"""
83-
Quantity{T,D<:AbstractDimensions} <: AbstractQuantity{T,D}
118+
Quantity{T<:Number,D<:AbstractDimensions} <: AbstractQuantity{T,D} <: Number
84119
85120
Physical quantity with value `value` of type `T` and dimensions `dimensions` of type `D`.
86121
For example, the velocity of an object with mass 1 kg and velocity
@@ -109,28 +144,110 @@ dimensions according to the operation.
109144
- `Quantity{T,D}(...)`: As above, but converting the value to type `T` and dimensions to `D`. You may also pass a
110145
`Quantity` as input.
111146
"""
112-
struct Quantity{T,D<:AbstractDimensions} <: AbstractQuantity{T,D}
147+
struct Quantity{T<:Number,D<:AbstractDimensions} <: AbstractQuantity{T,D}
113148
value::T
114149
dimensions::D
115150

116151
Quantity(x::_T, dimensions::_D) where {_T,_D<:AbstractDimensions} = new{_T,_D}(x, dimensions)
117152
end
118-
(::Type{Q})(x::T, ::Type{D}; kws...) where {D<:AbstractDimensions,T,T2,Q<:AbstractQuantity{T2}} = constructor_of(Q)(convert(T2, x), D(; kws...))
119-
(::Type{Q})(x, ::Type{D}; kws...) where {D<:AbstractDimensions,Q<:AbstractQuantity} = constructor_of(Q)(x, D(; kws...))
120-
(::Type{Q})(x::T; kws...) where {T,T2,Q<:AbstractQuantity{T2}} = constructor_of(Q)(convert(T2, x), dim_type(Q)(; kws...))
121-
(::Type{Q})(x; kws...) where {Q<:AbstractQuantity} = constructor_of(Q)(x, dim_type(Q)(; kws...))
122153

123-
(::Type{Q})(q::AbstractQuantity) where {T,D<:AbstractDimensions,Q<:AbstractQuantity{T,D}} = constructor_of(Q)(convert(T, ustrip(q)), convert(D, dimension(q)))
124-
(::Type{Q})(q::AbstractQuantity) where {T,Q<:AbstractQuantity{T}} = constructor_of(Q)(convert(T, ustrip(q)), dimension(q))
154+
"""
155+
GenericQuantity{T<:Any,D<:AbstractDimensions} <: AbstractGenericQuantity{T,D} <: Any
156+
157+
This has the same behavior as `Quantity` but is subtyped to `AbstractGenericQuantity <: Any`
158+
rather than `AbstractQuantity <: Number`.
159+
"""
160+
struct GenericQuantity{T,D<:AbstractDimensions} <: AbstractGenericQuantity{T,D}
161+
value::T
162+
dimensions::D
163+
164+
GenericQuantity(x::_T, dimensions::_D) where {_T,_D<:AbstractDimensions} = new{_T,_D}(x, dimensions)
165+
end
166+
167+
"""
168+
ABSTRACT_QUANTITY_TYPES
169+
170+
A constant tuple of the existing abstract quantity types,
171+
each as a tuple with (1) the abstract type,
172+
(2) the base type, and (3) the default exported concrete type.
173+
"""
174+
const ABSTRACT_QUANTITY_TYPES = ((AbstractQuantity, Number, Quantity), (AbstractGenericQuantity, Any, GenericQuantity))
175+
176+
for (type, base_type, _) in ABSTRACT_QUANTITY_TYPES
177+
@eval begin
178+
(::Type{Q})(x::T, ::Type{D}; kws...) where {D<:AbstractDimensions,T<:$base_type,T2,Q<:$type{T2}} = constructorof(Q)(convert(T2, x), D(; kws...))
179+
(::Type{Q})(x::$base_type, ::Type{D}; kws...) where {D<:AbstractDimensions,Q<:$type} = constructorof(Q)(x, D(; kws...))
180+
(::Type{Q})(x::T; kws...) where {T<:$base_type,T2,Q<:$type{T2}} = constructorof(Q)(convert(T2, x), dim_type(Q)(; kws...))
181+
(::Type{Q})(x::$base_type; kws...) where {Q<:$type} = constructorof(Q)(x, dim_type(Q)(; kws...))
182+
end
183+
for (type2, _, _) in ABSTRACT_QUANTITY_TYPES
184+
@eval begin
185+
(::Type{Q})(q::$type2) where {T,D<:AbstractDimensions,Q<:$type{T,D}} = constructorof(Q)(convert(T, ustrip(q)), convert(D, dimension(q)))
186+
(::Type{Q})(q::$type2) where {T,Q<:$type{T}} = constructorof(Q)(convert(T, ustrip(q)), dimension(q))
187+
(::Type{Q})(q::$type2) where {Q<:$type} = constructorof(Q)(ustrip(q), dimension(q))
188+
end
189+
end
190+
end
125191

126192
const DEFAULT_QUANTITY_TYPE = Quantity{DEFAULT_VALUE_TYPE, DEFAULT_DIM_TYPE}
127193

128-
new_dimensions(::Type{D}, dims...) where {D<:AbstractDimensions} = constructor_of(D)(dims...)
129-
new_quantity(::Type{Q}, l, r) where {Q<:AbstractQuantity} = constructor_of(Q)(l, r)
194+
new_dimensions(::Type{D}, dims...) where {D<:AbstractDimensions} = constructorof(D)(dims...)
195+
new_quantity(::Type{Q}, l, r) where {Q<:UnionAbstractQuantity} = constructorof(Q)(l, r)
196+
197+
dim_type(::Type{Q}) where {T,D<:AbstractDimensions,Q<:UnionAbstractQuantity{T,D}} = D
198+
dim_type(::Type{<:UnionAbstractQuantity}) = DEFAULT_DIM_TYPE
199+
200+
"""
201+
constructorof(::Type{<:AbstractDimensions})
202+
constructorof(::Type{<:UnionAbstractQuantity})
203+
204+
Return the constructor of the given type. This is used to create new objects
205+
of the same type as the input. Overload a method for a new type, especially
206+
if you need custom behavior.
207+
"""
208+
constructorof(::Type{<:Dimensions}) = Dimensions
209+
constructorof(::Type{<:Quantity}) = Quantity
210+
constructorof(::Type{<:GenericQuantity}) = GenericQuantity
211+
212+
"""
213+
with_type_parameters(::Type{<:AbstractDimensions}, ::Type{R})
214+
with_type_parameters(::Type{<:UnionAbstractQuantity}, ::Type{T}, ::Type{D})
215+
216+
Return the type with the given type parameters instead of the ones in the input type.
217+
This is used to get `Dimensions{R}` from input `(Dimensions{R1}, R)`, for example.
218+
Overload a method for a new type, especially if you need custom behavior.
219+
"""
220+
function with_type_parameters(::Type{<:Dimensions}, ::Type{R}) where {R}
221+
return Dimensions{R}
222+
end
223+
function with_type_parameters(::Type{<:Quantity}, ::Type{T}, ::Type{D}) where {T,D}
224+
return Quantity{T,D}
225+
end
226+
function with_type_parameters(::Type{<:GenericQuantity}, ::Type{T}, ::Type{D}) where {T,D}
227+
return GenericQuantity{T,D}
228+
end
229+
230+
# The following functions should be overloaded for special types
231+
function constructorof(::Type{T}) where {T<:Union{UnionAbstractQuantity,AbstractDimensions}}
232+
return Base.typename(T).wrapper
233+
end
234+
function with_type_parameters(::Type{D}, ::Type{R}) where {D<:AbstractDimensions,R}
235+
return constructorof(D){R}
236+
end
237+
function with_type_parameters(::Type{Q}, ::Type{T}, ::Type{D}) where {Q<:UnionAbstractQuantity,T,D}
238+
return constructorof(Q){T,D}
239+
end
240+
241+
"""
242+
dimension_names(::Type{<:AbstractDimensions})
130243
131-
dim_type(::Type{Q}) where {T,D<:AbstractDimensions,Q<:AbstractQuantity{T,D}} = D
132-
dim_type(::Type{<:AbstractQuantity}) = DEFAULT_DIM_TYPE
133-
constructor_of(::Type{T}) where {T} = Base.typename(T).wrapper
244+
Return a tuple of symbols with the names of the dimensions of the given type.
245+
This should be static so that it can be hardcoded during compilation.
246+
The default is to use `fieldnames`, but you can overload this for custom behavior.
247+
"""
248+
@inline function dimension_names(::Type{D}) where {D<:AbstractDimensions}
249+
return static_fieldnames(D)
250+
end
134251

135252
struct DimensionError{Q1,Q2} <: Exception
136253
q1::Q1

‎src/units.jl‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ end
6868
kg,
6969
)
7070
@doc(
71-
"Time in seconds. Available variants: `fs`, `ps`, `ns`, `μs` (/`us`), `ms`, `min`, `h` (/`hr`), `day`, `yr`, `kyr`, `Myr`, `Gyr`.",
71+
"Time in seconds. Available variants: `fs`, `ps`, `ns`, `μs` (/`us`), `ms`, `min` (/`minute`), `h` (/`hr`), `day` (/`d`), `wk`, `yr`, `kyr`, `Myr`, `Gyr`.",
7272
s,
7373
)
7474
@doc(
@@ -158,15 +158,21 @@ end
158158
# Common assorted units
159159
## Time
160160
@register_unit min 60 * s
161+
@register_unit minute min
161162
@register_unit h 60 * min
162163
@register_unit hr h
163164
@register_unit day 24 * h
165+
@register_unit d day
166+
@register_unit wk 7 * day
164167
@register_unit yr 365.25 * day
165168

166169
@add_prefixes min ()
170+
@add_prefixes minute ()
167171
@add_prefixes h ()
168172
@add_prefixes hr ()
169173
@add_prefixes day ()
174+
@add_prefixes d ()
175+
@add_prefixes wk ()
170176
@add_prefixes yr (k, M, G)
171177

172178
## Volume

‎src/uparse.jl‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ function _generate_units_import()
1414
end
1515
return import_expr
1616
end
17+
macro generate_units_import()
18+
return _generate_units_import()
19+
end
1720

18-
eval(_generate_units_import())
21+
@generate_units_import
1922

2023
"""
2124
uparse(s::AbstractString)

‎src/utils.jl‎

Lines changed: 114 additions & 86 deletions
Large diffs are not rendered by default.

‎test/test_measurements.jl‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ using DynamicQuantities
22
using Measurements
33
using Measurements: value, uncertainty
44

5-
x = 1.0u"m/s" ± 0.1u"m/s"
5+
for Q in (Quantity, GenericQuantity)
6+
x = Q(1.0u"m/s") ± Q(0.1u"m/s")
67

7-
@test ustrip(x^2) == ustrip(x)^2
8-
@test value(x) == 1.0u"m/s"
9-
@test uncertainty(x) == 0.1u"m/s"
10-
@test dimension(x)^2 == dimension(x^2)
11-
@test_throws DimensionError 0.5u"m" ± 0.1u"s"
8+
@test ustrip(x^2) == ustrip(x)^2
9+
@test value(x) == 1.0u"m/s"
10+
@test uncertainty(x) == 0.1u"m/s"
11+
@test dimension(x)^2 == dimension(x^2)
12+
@test_throws DimensionError 0.5u"m" ± 0.1u"s"
1213

13-
# Mixed types:
14-
y = Quantity{Float16}(0.1u"m/s") ± Quantity{Float32}(0.1u"m/s")
15-
@test typeof(y) <: Quantity{Measurement{Float32}}
14+
# Mixed types:
15+
y = Q{Float16}(0.1u"m/s") ± Q{Float32}(0.1u"m/s")
16+
@test typeof(y) <: Q{Measurement{Float32}}
17+
end

‎test/test_unitful.jl‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ x = 1.0u"scale"
3838
@test typeof(x) <: Unitful.Quantity{Float64, MyScaleUnit.𝐒}
3939
@test_throws ErrorException convert(DynamicQuantities.Quantity, x)
4040
# These are not supported because there is no SI equivalency
41+
42+
# issue 79
43+
symbolic = DynamicQuantities.us"s"
44+
@test_throws ArgumentError convert(Unitful.Quantity, symbolic)
45+
@test convert(Unitful.Quantity, DynamicQuantities.uexpand(symbolic)) == 1.0 * Unitful.u"s"

‎test/unittests.jl‎

Lines changed: 456 additions & 267 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.