Skip to content

Commit 66d04fe

Browse files
committed
Merge branch 'abstract-types-2' into extra-utils-2
2 parents 4f12c12 + b583e46 commit 66d04fe

File tree

6 files changed

+163
-152
lines changed

6 files changed

+163
-152
lines changed

ext/DynamicQuantitiesUnitfulExt.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ Base.convert(::Type{Unitful.Quantity}, x::DynamicQuantities.Quantity) =
3535
end
3636

3737
Base.convert(::Type{DynamicQuantities.Quantity}, x::Unitful.Quantity{T}) where {T} = convert(DynamicQuantities.Quantity{T,DynamicQuantities.DEFAULT_DIM_TYPE}, x)
38-
Base.convert(::Type{DynamicQuantities.Quantity{T,R}}, x::Unitful.Quantity) where {T,R} =
38+
Base.convert(::Type{DynamicQuantities.Quantity{T,D}}, x::Unitful.Quantity) where {T,R,D<:DynamicQuantities.AbstractDimensions{R}} =
3939
let
4040
value = Unitful.ustrip(Unitful.upreferred(x))
41-
dimension = convert(DynamicQuantities.Dimensions{R}, Unitful.dimension(x))
41+
dimension = convert(D, Unitful.dimension(x))
4242
return DynamicQuantities.Quantity(convert(T, value), dimension)
4343
end
4444

src/math.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ Base.:*(l::AbstractQuantity, r::AbstractDimensions) = new_quantity(typeof(l), us
44
Base.:*(l::AbstractDimensions, r::AbstractQuantity) = new_quantity(typeof(r), ustrip(r), l * dimension(r))
55
Base.:*(l::AbstractQuantity, r) = new_quantity(typeof(l), ustrip(l) * r, dimension(l))
66
Base.:*(l, r::AbstractQuantity) = new_quantity(typeof(r), l * ustrip(r), dimension(r))
7-
Base.:*(l::AbstractDimensions, r) = new_quantity(typeof(l), r, l)
8-
Base.:*(l, r::AbstractDimensions) = new_quantity(typeof(r), l, 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)).")
99

1010
Base.:/(l::AbstractDimensions, r::AbstractDimensions) = map_dimensions(-, l, r)
1111
Base.:/(l::AbstractQuantity, r::AbstractQuantity) = new_quantity(typeof(l), ustrip(l) / ustrip(r), dimension(l) / dimension(r))
1212
Base.:/(l::AbstractQuantity, r::AbstractDimensions) = new_quantity(typeof(l), ustrip(l), dimension(l) / r)
1313
Base.:/(l::AbstractDimensions, r::AbstractQuantity) = new_quantity(typeof(r), inv(ustrip(r)), l / dimension(r))
1414
Base.:/(l::AbstractQuantity, r) = new_quantity(typeof(l), ustrip(l) / r, dimension(l))
1515
Base.:/(l, r::AbstractQuantity) = l * inv(r)
16-
Base.:/(l::AbstractDimensions, r) = new_quantity(typeof(l), inv(r), l)
17-
Base.:/(l, r::AbstractDimensions) = new_quantity(typeof(r), 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)).")
1818

1919
Base.:+(l::AbstractQuantity, r::AbstractQuantity) = dimension(l) == dimension(r) ? new_quantity(typeof(l), ustrip(l) + ustrip(r), dimension(l)) : throw(DimensionError(l, r))
2020
Base.:-(l::AbstractQuantity) = new_quantity(typeof(l), -ustrip(l), dimension(l))
@@ -30,8 +30,8 @@ _pow(l::AbstractQuantity{T}, r) where {T} = new_quantity(typeof(l), ustrip(l)^r,
3030
_pow_as_T(l::AbstractQuantity{T}, r) where {T} = new_quantity(typeof(l), ustrip(l)^convert(T, r), _pow(l.dimensions, r))
3131
Base.:^(l::AbstractDimensions{R}, r::Integer) where {R} = _pow(l, r)
3232
Base.:^(l::AbstractDimensions{R}, r::Number) where {R} = _pow(l, tryrationalize(R, r))
33-
Base.:^(l::AbstractQuantity{T,R}, r::Integer) where {T,R} = _pow(l, r)
34-
Base.:^(l::AbstractQuantity{T,R}, r::Number) where {T,R} = _pow_as_T(l, tryrationalize(R, r))
33+
Base.:^(l::AbstractQuantity{T,D}, r::Integer) where {T,R,D<:AbstractDimensions{R}} = _pow(l, r)
34+
Base.:^(l::AbstractQuantity{T,D}, r::Number) where {T,R,D<:AbstractDimensions{R}} = _pow_as_T(l, tryrationalize(R, r))
3535

3636
Base.inv(d::AbstractDimensions) = map_dimensions(-, d)
3737
Base.inv(q::AbstractQuantity) = new_quantity(typeof(q), inv(ustrip(q)), inv(dimension(q)))

src/types.jl

Lines changed: 33 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import Tricks: static_fieldnames, static_fieldtypes
22

3-
const DEFAULT_DIM_TYPE = FixedRational{Int32, 2^4 * 3^2 * 5^2 * 7}
3+
const DEFAULT_DIM_BASE_TYPE = FixedRational{Int32,2^4 * 3^2 * 5^2 * 7}
44
const DEFAULT_VALUE_TYPE = Float64
55

6-
abstract type AbstractQuantity{T,R} end
6+
abstract type AbstractQuantity{T,D} end
77
abstract type AbstractDimensions{R} end
88

99
"""
@@ -27,10 +27,10 @@ which is by default a rational number.
2727
2828
# Constructors
2929
30-
- `Dimensions(args...)`: Pass all the dimensions as arguments. `R` is set to `DEFAULT_DIM_TYPE`.
31-
- `Dimensions(; kws...)`: Pass a subset of dimensions as keyword arguments. `R` is set to `DEFAULT_DIM_TYPE`.
30+
- `Dimensions(args...)`: Pass all the dimensions as arguments. `R` is set to `DEFAULT_DIM_BASE_TYPE`.
31+
- `Dimensions(; kws...)`: Pass a subset of dimensions as keyword arguments. `R` is set to `DEFAULT_DIM_BASE_TYPE`.
3232
- `Dimensions(::Type{R}; kws...)` or `Dimensions{R}(; kws...)`: Pass a subset of dimensions as keyword arguments, with the output type set to `Dimensions{R}`.
33-
- `Dimensions{R}(args...)`: Pass all the dimensions as arguments, with the output type set to `Dimensions{R}`.
33+
- `Dimensions{R}()`: Create a dimensionless object typed as `Dimensions{R}`.
3434
- `Dimensions{R}(d::Dimensions)`: Copy the dimensions from another `Dimensions` object, with the output type set to `Dimensions{R}`.
3535
"""
3636
struct Dimensions{R<:Real} <: AbstractDimensions{R}
@@ -43,18 +43,17 @@ struct Dimensions{R<:Real} <: AbstractDimensions{R}
4343
amount::R
4444
end
4545

46-
(::Type{D})(::Type{R}; kws...) where {R,D<:AbstractDimensions} = D{R}((tryrationalize(R, get(kws, k, zero(R))) for k in static_fieldnames(D))...)
47-
(::Type{D})(; kws...) where {D<:AbstractDimensions} = D(DEFAULT_DIM_TYPE; kws...)
48-
49-
(::Type{D})(args...) where {R,D<:AbstractDimensions{R}} = dimension_constructor(D)(Base.Fix1(convert, R).(args)...)
50-
(::Type{D})(; kws...) where {R,D<:AbstractDimensions{R}} = dimension_constructor(D)(R; kws...)
46+
(::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))...)
47+
(::Type{D})(; kws...) where {R,D<:AbstractDimensions{R}} = constructor_of(D)(R; kws...)
48+
(::Type{D})(; kws...) where {D<:AbstractDimensions} = D(DEFAULT_DIM_BASE_TYPE; kws...)
5149
(::Type{D})(d::AbstractDimensions) where {R,D<:AbstractDimensions{R}} = D((getproperty(d, k) for k in static_fieldnames(D))...)
5250

51+
const DEFAULT_DIM_TYPE = Dimensions{DEFAULT_DIM_BASE_TYPE}
5352

5453
"""
55-
Quantity{T,R}
54+
Quantity{T,D}
5655
57-
Physical quantity with value `value` of type `T` and dimensions `dimensions` of type `Dimensions{R}`.
56+
Physical quantity with value `value` of type `T` and dimensions `dimensions` of type `D`.
5857
For example, the velocity of an object with mass 1 kg and velocity
5958
2 m/s is `Quantity(2, mass=1, length=1, time=-1)`.
6059
You should access these fields with `ustrip(q)`, and `dimensions(q)`.
@@ -68,77 +67,37 @@ dimensions according to the operation.
6867
# Fields
6968
7069
- `value::T`: value of the quantity of some type `T`. Access with `ustrip(::Quantity)`
71-
- `dimensions::Dimensions{R}`: dimensions of the quantity with dimension type `R`. Access with `dimension(::Quantity)`
70+
- `dimensions::D`: dimensions of the quantity. Access with `dimension(::Quantity)`
7271
7372
# Constructors
7473
75-
- `Quantity(x; kws...)`: Construct a quantity with value `x` and dimensions given by the keyword arguments. The value type is inferred from `x`. `R` is set to `DEFAULT_DIM_TYPE`.
76-
- `Quantity(x, ::Type{R}; kws...)`: Construct a quantity with value `x`. The dimensions parametric type is set to `R`.
77-
- `Quantity(x, d::Dimensions{R})`: Construct a quantity with value `x` and dimensions `d`.
78-
- `Quantity{T}(q::Quantity)`: Construct a quantity with value `q.value` and dimensions `q.dimensions`, but with value type converted to `T`.
79-
- `Quantity{T,R}(q::Quantity)`: Construct a quantity with value `q.value` and dimensions `q.dimensions`, but with value type converted to `T` and dimensions parametric type set to `R`.
74+
- `Quantity(x; kws...)`: Construct a quantity with value `x` and dimensions given by the keyword arguments. The value
75+
type is inferred from `x`. `R` is set to `DEFAULT_DIM_TYPE`.
76+
- `Quantity(x, ::Type{D}; kws...)`: Construct a quantity with value `x` with dimensions given by the keyword arguments,
77+
and the dimensions type set to `D`.
78+
- `Quantity(x, d::D)`: Construct a quantity with value `x` and dimensions `d` of type `D`.
79+
- `Quantity{T}(...)`: As above, but converting the value to type `T`. You may also pass a `Quantity` as input.
80+
- `Quantity{T,D}(...)`: As above, but converting the value to type `T` and dimensions to `D`. You may also pass a
81+
`Quantity` as input.
8082
"""
81-
struct Quantity{T,R} <: AbstractQuantity{T,R}
83+
struct Quantity{T,D<:AbstractDimensions} <: AbstractQuantity{T,D}
8284
value::T
83-
dimensions::Dimensions{R}
84-
end
85-
86-
(::Type{Q})(x, ::Type{R}; kws...) where {R,Q<:AbstractQuantity} = quantity_constructor(Q){typeof(x), R}(x, dimension_constructor(Q)(R; kws...))
87-
(::Type{Q})(x; kws...) where {Q<:AbstractQuantity} = Q(x, DEFAULT_DIM_TYPE; kws...)
88-
(::Type{Q})(q::AbstractQuantity) where {T,Q<:AbstractQuantity{T}} = new_quantity(Q, convert(T, ustrip(q)), dimension(q))
89-
(::Type{Q})(q::AbstractQuantity) where {T,R,Q<:AbstractQuantity{T,R}} = new_quantity(Q, convert(T, ustrip(q)), dimension_constructor(Q){R}(dimension(q)))
90-
91-
new_dimensions(::Type{QD}, dims...) where {QD<:Union{AbstractQuantity,AbstractDimensions}} = dimension_constructor(QD)(dims...)
92-
new_quantity(::Type{QD}, l, r) where {QD<:Union{AbstractQuantity,AbstractDimensions}} = quantity_constructor(QD)(l, r)
93-
94-
function constructor_of(::Type{T}) where {T}
95-
return Base.typename(T).wrapper
85+
dimensions::D
9686
end
97-
@generated function get_dim_type(::Type{Q}) where {Q<:AbstractQuantity}
98-
quantity_type = constructor_of(Q)
99-
field_type = NamedTuple(static_fieldnames(quantity_type) .=> static_fieldtypes(quantity_type))[:dimensions]
100-
out = constructor_of(field_type)
101-
return :($out)
102-
end
103-
104-
"""
105-
dimension_constructor(::Type{<:AbstractDimensions})
106-
107-
This function returns the container for a particular `AbstractDimensions`.
108-
For example, `Dimensions` will get returned as `Dimensions`, and
109-
`Dimensions{Rational{Int64}}` will also get returned as `Dimensions`.
110-
"""
111-
dimension_constructor(::Type{D}) where {D<:AbstractDimensions} = constructor_of(D)
87+
(::Type{Q})(x::T, ::Type{D}; kws...) where {D<:AbstractDimensions,T,T2,Q<:AbstractQuantity{T2}} = constructor_of(Q)(convert(T2, x), D(; kws...))
88+
(::Type{Q})(x, ::Type{D}; kws...) where {D<:AbstractDimensions,Q<:AbstractQuantity} = constructor_of(Q)(x, D(; kws...))
89+
(::Type{Q})(x::T; kws...) where {T,T2,Q<:AbstractQuantity{T2}} = constructor_of(Q)(convert(T2, x), dim_type(Q)(; kws...))
90+
(::Type{Q})(x; kws...) where {Q<:AbstractQuantity} = constructor_of(Q)(x, dim_type(Q)(; kws...))
11291

113-
"""
114-
dimension_constructor(::Type{<:AbstractQuantity})
92+
(::Type{Q})(q::AbstractQuantity) where {T,D<:AbstractDimensions,Q<:AbstractQuantity{T,D}} = constructor_of(Q)(convert(T, ustrip(q)), convert(D, dimension(q)))
93+
(::Type{Q})(q::AbstractQuantity) where {T,Q<:AbstractQuantity{T}} = constructor_of(Q)(convert(T, ustrip(q)), dimension(q))
11594

116-
This function returns the `Dimensions` type used inside
117-
a particular `Quantity` type by reading the `.dimensions` field.
118-
It also strips the type parameter (i.e., `Dimensions{R} -> Dimensions`).
119-
"""
120-
dimension_constructor(::Type{Q}) where {Q<:AbstractQuantity} = get_dim_type(Q)
121-
122-
"""
123-
quantity_constructor(::Type{<:AbstractQuantity})
95+
new_dimensions(::Type{D}, dims...) where {D<:AbstractDimensions} = constructor_of(D)(dims...)
96+
new_quantity(::Type{Q}, l, r) where {Q<:AbstractQuantity} = constructor_of(Q)(l, r)
12497

125-
This function returns the container for a particular `AbstractQuantity`.
126-
For example, `Quantity` gets returned as `Quantity`, `Quantity{Float32}` also
127-
as `Quantity`, and `Quantity{Float32,Rational{Int64}}` also as `Quantity`.
128-
"""
129-
quantity_constructor(::Type{Q}) where {Q<:AbstractQuantity} = constructor_of(Q)
130-
131-
"""
132-
quantity_constructor(::Type{<:AbstractDimensions})
133-
134-
This function returns the `<:AbstractQuantity` type corresponding to
135-
a given `<:AbstractDimensions`. For example, `Dimensions -> Quantity`.
136-
If you define a custom dimensions type, you should overload this function
137-
so it returns your custom quantity type that uses that dimensions type.
138-
This is only needed if you wish to use the `(*)(::AbstractDimensions, ::Number)`
139-
function; otherwise it won't be necessary.
140-
"""
141-
quantity_constructor(::Type{D}) where {D<:Dimensions} = Quantity
98+
dim_type(::Type{Q}) where {T,D<:AbstractDimensions,Q<:AbstractQuantity{T,D}} = D
99+
dim_type(::Type{<:AbstractQuantity}) = DEFAULT_DIM_TYPE
100+
constructor_of(::Type{T}) where {T} = Base.typename(T).wrapper
142101

143102
struct DimensionError{Q1,Q2} <: Exception
144103
q1::Q1

src/utils.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ end
2929
Base.float(q::AbstractQuantity{T}) where {T<:AbstractFloat} = convert(T, q)
3030
Base.convert(::Type{T}, q::AbstractQuantity) where {T<:Real} =
3131
let
32-
@assert iszero(q.dimensions) "$(typeof(q)): $(q) has dimensions! Use `ustrip` instead."
33-
return convert(T, q.value)
32+
@assert iszero(dimension(q)) "$(typeof(q)): $(q) has dimensions! Use `ustrip` instead."
33+
return convert(T, ustrip(q))
3434
end
3535

3636
Base.keys(d::AbstractDimensions) = static_fieldnames(typeof(d))
3737
Base.getindex(d::AbstractDimensions, k::Symbol) = getfield(d, k)
3838

3939
# Compatibility with `.*`
40-
Base.length(::Union{AbstractQuantity,AbstractDimensions}) = 1
41-
Base.iterate(qd::Union{AbstractQuantity,AbstractDimensions}) = (qd, nothing)
42-
Base.iterate(::Union{AbstractQuantity,AbstractDimensions}, ::Nothing) = nothing
40+
Base.length(::AbstractQuantity) = 1
41+
Base.iterate(qd::AbstractQuantity) = (qd, nothing)
42+
Base.iterate(::AbstractQuantity, ::Nothing) = nothing
4343

4444
# Numeric checks
4545
Base.isapprox(l::AbstractQuantity, r::AbstractQuantity; kws...) = isapprox(ustrip(l), ustrip(r); kws...) && dimension(l) == dimension(r)
@@ -67,7 +67,7 @@ end
6767
# Base.one, typemin, typemax
6868
for f in (:one, :typemin, :typemax)
6969
@eval begin
70-
Base.$f(::Type{Q}) where {T,R,Q<:AbstractQuantity{T,R}} = new_quantity(Q, $f(T), R)
70+
Base.$f(::Type{Q}) where {T,D,Q<:AbstractQuantity{T,D}} = new_quantity(Q, $f(T), D)
7171
Base.$f(::Type{Q}) where {T,Q<:AbstractQuantity{T}} = $f(constructor_of(Q){T, DEFAULT_DIM_TYPE})
7272
Base.$f(::Type{Q}) where {Q<:AbstractQuantity} = $f(Q{DEFAULT_VALUE_TYPE, DEFAULT_DIM_TYPE})
7373
end
@@ -134,12 +134,12 @@ tryrationalize(::Type{R}, x) where {R} = isinteger(x) ? convert(R, round(Int, x)
134134

135135
Base.showerror(io::IO, e::DimensionError) = print(io, "DimensionError: ", e.q1, " and ", e.q2, " have incompatible dimensions")
136136

137-
Base.convert(::Type{Quantity}, q::Quantity) = q
138-
Base.convert(::Type{Quantity{T}}, q::Quantity) where {T} = Quantity{T}(q)
139-
Base.convert(::Type{Quantity{T,R}}, q::Quantity) where {T,R} = Quantity{T,R}(q)
137+
Base.convert(::Type{Q}, q::AbstractQuantity) where {Q<:AbstractQuantity} = q
138+
Base.convert(::Type{Q}, q::AbstractQuantity) where {T,Q<:AbstractQuantity{T}} = new_quantity(Q, convert(T, ustrip(q)), dimension(q))
139+
Base.convert(::Type{Q}, q::AbstractQuantity) where {T,D,Q<:AbstractQuantity{T,D}} = new_quantity(Q, convert(T, ustrip(q)), convert(D, dimension(q)))
140140

141-
Base.convert(::Type{Dimensions}, d::Dimensions) = d
142-
Base.convert(::Type{Dimensions{R}}, d::Dimensions) where {R} = Dimensions{R}(d)
141+
Base.convert(::Type{D}, d::AbstractDimensions) where {D<:AbstractDimensions} = d
142+
Base.convert(::Type{D}, d::AbstractDimensions) where {R,D<:AbstractDimensions{R}} = D(d)
143143

144144
"""
145145
ustrip(q::AbstractQuantity)

test/test_unitful.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import DynamicQuantities
2-
using DynamicQuantities: DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
2+
using DynamicQuantities: DEFAULT_DIM_BASE_TYPE, DEFAULT_VALUE_TYPE
33
import Unitful
44
import Unitful: @u_str
55
import Ratios: SimpleRatio
@@ -11,14 +11,15 @@ risapprox(x::Unitful.Quantity, y::Unitful.Quantity; kws...) =
1111
return isapprox(xfloat, yfloat; kws...)
1212
end
1313

14-
for T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
15-
x = DynamicQuantities.Quantity(T(0.2), R, length=1, amount=2, current=-1 // 2, luminosity=2 // 5)
14+
for T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_BASE_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
15+
D = DynamicQuantities.Dimensions{R}
16+
x = DynamicQuantities.Quantity(T(0.2), D, length=1, amount=2, current=-1 // 2, luminosity=2 // 5)
1617
x_unitful = T(0.2)u"m*mol^2*A^(-1//2)*cd^(2//5)"
1718

1819
@test risapprox(convert(Unitful.Quantity, x), x_unitful; atol=1e-6)
1920
@test typeof(convert(DynamicQuantities.Quantity, convert(Unitful.Quantity, x))) <: DynamicQuantities.Quantity{T,DynamicQuantities.DEFAULT_DIM_TYPE}
2021
@test isapprox(convert(DynamicQuantities.Quantity, convert(Unitful.Quantity, x)), x; atol=1e-6)
2122

22-
@test isapprox(convert(DynamicQuantities.Quantity{T,R}, x_unitful), x; atol=1e-6)
23-
@test risapprox(convert(Unitful.Quantity, convert(DynamicQuantities.Quantity{T,R}, x_unitful)), Unitful.upreferred(x_unitful); atol=1e-6)
23+
@test isapprox(convert(DynamicQuantities.Quantity{T,D}, x_unitful), x; atol=1e-6)
24+
@test risapprox(convert(Unitful.Quantity, convert(DynamicQuantities.Quantity{T,D}, x_unitful)), Unitful.upreferred(x_unitful); atol=1e-6)
2425
end

0 commit comments

Comments
 (0)