Skip to content

Commit b27ed80

Browse files
authored
Merge pull request #85 from SymbolicML/real-quantity-2
Add AbstractRealQuantity
2 parents 5f438b5 + 0609463 commit b27ed80

16 files changed

+644
-196
lines changed

ext/DynamicQuantitiesUnitfulExt.jl

+26-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module DynamicQuantitiesUnitfulExt
22

3-
import DynamicQuantities
3+
using DynamicQuantities: DynamicQuantities, ABSTRACT_QUANTITY_TYPES
4+
45
import Unitful
56
import Unitful: @u_str
67

@@ -23,30 +24,33 @@ function unitful_equivalences()
2324
return NamedTuple((k => si_units[k] for k in keys(si_units)))
2425
end
2526

26-
Base.convert(::Type{Unitful.Quantity}, x::DynamicQuantities.Quantity) =
27-
let
28-
validate_upreferred()
29-
cumulator = DynamicQuantities.ustrip(x)
30-
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`."))
27+
for (_, _, Q) in ABSTRACT_QUANTITY_TYPES
28+
@eval begin
29+
function Base.convert(::Type{Unitful.Quantity}, x::$Q)
30+
validate_upreferred()
31+
cumulator = DynamicQuantities.ustrip(x)
32+
dims = DynamicQuantities.dimension(x)
33+
if dims isa DynamicQuantities.SymbolicDimensions
34+
throw(ArgumentError("Conversion of a `DynamicQuantities." * string($Q) * "` 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`."))
35+
end
36+
equiv = unitful_equivalences()
37+
for dim in keys(dims)
38+
value = dims[dim]
39+
iszero(value) && continue
40+
cumulator *= equiv[dim]^value
41+
end
42+
cumulator
3343
end
34-
equiv = unitful_equivalences()
35-
for dim in keys(dims)
36-
value = dims[dim]
37-
iszero(value) && continue
38-
cumulator *= equiv[dim]^value
44+
function Base.convert(::Type{$Q}, x::Unitful.Quantity{T}) where {T}
45+
return convert($Q{T,DynamicQuantities.DEFAULT_DIM_TYPE}, x)
46+
end
47+
function Base.convert(::Type{$Q{T,D}}, x::Unitful.Quantity) where {T,R,D<:DynamicQuantities.AbstractDimensions{R}}
48+
value = Unitful.ustrip(Unitful.upreferred(x))
49+
dimension = convert(D, Unitful.dimension(x))
50+
return $Q(convert(T, value), dimension)
3951
end
40-
cumulator
41-
end
42-
43-
Base.convert(::Type{DynamicQuantities.Quantity}, x::Unitful.Quantity{T}) where {T} = convert(DynamicQuantities.Quantity{T,DynamicQuantities.DEFAULT_DIM_TYPE}, x)
44-
Base.convert(::Type{DynamicQuantities.Quantity{T,D}}, x::Unitful.Quantity) where {T,R,D<:DynamicQuantities.AbstractDimensions{R}} =
45-
let
46-
value = Unitful.ustrip(Unitful.upreferred(x))
47-
dimension = convert(D, Unitful.dimension(x))
48-
return DynamicQuantities.Quantity(convert(T, value), dimension)
4952
end
53+
end
5054

5155
Base.convert(::Type{DynamicQuantities.Dimensions}, d::Unitful.Dimensions) = convert(DynamicQuantities.DEFAULT_DIM_TYPE, d)
5256
Base.convert(::Type{DynamicQuantities.Dimensions{R}}, d::Unitful.Dimensions{D}) where {R,D} =

src/DynamicQuantities.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module DynamicQuantities
22

33
export Units, Constants
4-
export AbstractDimensions, AbstractQuantity, AbstractGenericQuantity, UnionAbstractQuantity
5-
export Quantity, GenericQuantity, Dimensions, SymbolicDimensions, QuantityArray, DimensionError
4+
export AbstractDimensions, AbstractQuantity, AbstractGenericQuantity, AbstractRealQuantity, UnionAbstractQuantity
5+
export Quantity, GenericQuantity, RealQuantity, 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

src/arrays.jl

+5-4
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ struct QuantityArray{T,N,D<:AbstractDimensions,Q<:UnionAbstractQuantity{T,D},V<:
5252
end
5353
end
5454

55-
# Construct with a Quantity (easier, as you can use the units):
5655
QuantityArray(v::AbstractArray; kws...) = QuantityArray(v, DEFAULT_DIM_TYPE(; kws...))
5756
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)
57+
@eval QuantityArray(v::AbstractArray{<:$base_type}, q::$type) = QuantityArray(v .* ustrip(q), dimension(q), typeof(q))
58+
59+
# Only define defaults for Quantity and GenericQuantity. Other types, the user needs to declare explicitly.
60+
if type in (AbstractQuantity, AbstractGenericQuantity)
61+
@eval QuantityArray(v::AbstractArray{<:$base_type}, d::AbstractDimensions) = QuantityArray(v, d, $default_type)
6162
end
6263
end
6364
QuantityArray(v::QA) where {Q<:UnionAbstractQuantity,QA<:AbstractArray{Q}} =

src/constants.jl

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module Constants
22

33
import ..DEFAULT_QUANTITY_TYPE
4-
import ..Quantity
54
import ..Units as U
65
import ..Units: _add_prefixes
76

src/disambiguities.jl

+102-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,108 @@
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
1+
for op in (:isless, :(==), :isequal, :(<)), (type, _, _) in ABSTRACT_QUANTITY_TYPES
2+
@eval begin
3+
Base.$(op)(::$type, ::Missing) = missing
4+
Base.$(op)(::Missing, ::$type) = missing
5+
end
6+
end
7+
for op in (:isapprox,), (type, _, _) in ABSTRACT_QUANTITY_TYPES
8+
@eval begin
9+
Base.$(op)(::$type, ::Missing; kws...) = missing
10+
Base.$(op)(::Missing, ::$type; kws...) = missing
11+
end
12+
end
713

8-
Base.:(==)(::AbstractQuantity, ::WeakRef) = error("Cannot compare a quantity to a weakref")
9-
Base.:(==)(::WeakRef, ::AbstractQuantity) = error("Cannot compare a weakref to a quantity")
14+
for (type, _, _) in ABSTRACT_QUANTITY_TYPES
15+
@eval begin
16+
Base.:(==)(::$type, ::WeakRef) = error("Cannot compare a quantity to a weakref")
17+
Base.:(==)(::WeakRef, ::$type) = error("Cannot compare a weakref to a quantity")
18+
end
19+
end
1020

1121
Base.:*(l::AbstractDimensions, r::Number) = error("Please use an `UnionAbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
1222
Base.:*(l::Number, r::AbstractDimensions) = error("Please use an `UnionAbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
1323
Base.:/(l::AbstractDimensions, r::Number) = error("Please use an `UnionAbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")
1424
Base.:/(l::Number, r::AbstractDimensions) = error("Please use an `UnionAbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")
25+
26+
# Promotion ambiguities
27+
function Base.promote_rule(::Type{F}, ::Type{Bool}) where {F<:FixedRational}
28+
return F
29+
end
30+
function Base.promote_rule(::Type{Bool}, ::Type{F}) where {F<:FixedRational}
31+
return F
32+
end
33+
function Base.promote_rule(::Type{F}, ::Type{BigFloat}) where {F<:FixedRational}
34+
return promote_type(Rational{eltype(F)}, BigFloat)
35+
end
36+
function Base.promote_rule(::Type{BigFloat}, ::Type{F}) where {F<:FixedRational}
37+
return promote_type(Rational{eltype(F)}, BigFloat)
38+
end
39+
function Base.promote_rule(::Type{F}, ::Type{T}) where {F<:FixedRational,T<:AbstractIrrational}
40+
return promote_type(Rational{eltype(F)}, T)
41+
end
42+
function Base.promote_rule(::Type{T}, ::Type{F}) where {F<:FixedRational,T<:AbstractIrrational}
43+
return promote_type(Rational{eltype(F)}, T)
44+
end
45+
46+
################################################################################
47+
# Assorted calls found by Aqua: ################################################
48+
################################################################################
49+
50+
for type in (Signed, Float64, Float32, Rational), op in (:flipsign, :copysign)
51+
@eval function Base.$(op)(x::$type, y::AbstractRealQuantity)
52+
return $(op)(x, ustrip(y))
53+
end
54+
end
55+
for type in (:(Complex), :(Complex{Bool}))
56+
@eval begin
57+
function Base.:*(l::$type, r::AbstractRealQuantity)
58+
new_quantity(typeof(r), l * ustrip(r), dimension(r))
59+
end
60+
function Base.:*(l::AbstractRealQuantity, r::$type)
61+
new_quantity(typeof(l), ustrip(l) * r, dimension(l))
62+
end
63+
end
64+
end
65+
function Complex{T}(q::AbstractRealQuantity) where {T<:Real}
66+
@assert iszero(dimension(q)) "$(typeof(q)): $(q) has dimensions! Use `ustrip` instead."
67+
return Complex{T}(ustrip(q))
68+
end
69+
for type in (:Bool, :Complex)
70+
@eval function $type(q::AbstractRealQuantity)
71+
@assert iszero(dimension(q)) "$(typeof(q)): $(q) has dimensions! Use `ustrip` instead."
72+
return $type(ustrip(q))
73+
end
74+
end
75+
function Base.:/(l::Complex, r::AbstractRealQuantity)
76+
new_quantity(typeof(r), l / ustrip(r), inv(dimension(r)))
77+
end
78+
function Base.:/(l::AbstractRealQuantity, r::Complex)
79+
new_quantity(typeof(l), ustrip(l) / r, dimension(l))
80+
end
81+
for op in (:(==), :isequal), base_type in (AbstractIrrational, AbstractFloat)
82+
@eval begin
83+
function Base.$(op)(l::AbstractRealQuantity, r::$base_type)
84+
return $(op)(ustrip(l), r) && iszero(dimension(l))
85+
end
86+
function Base.$(op)(l::$base_type, r::AbstractRealQuantity)
87+
return $(op)(l, ustrip(r)) && iszero(dimension(r))
88+
end
89+
end
90+
end
91+
function Base.isless(l::AbstractRealQuantity, r::AbstractFloat)
92+
iszero(dimension(l)) || throw(DimensionError(l, r))
93+
return isless(ustrip(l), r)
94+
end
95+
function Base.isless(l::AbstractFloat, r::AbstractRealQuantity)
96+
iszero(dimension(r)) || throw(DimensionError(l, r))
97+
return isless(l, ustrip(r))
98+
end
99+
for (type, _, _) in ABSTRACT_QUANTITY_TYPES, numeric_type in (Bool, BigFloat)
100+
@eval begin
101+
function Base.promote_rule(::Type{Q}, ::Type{$numeric_type}) where {T,D,Q<:$type{T,D}}
102+
return with_type_parameters(promote_quantity_on_value(Q, $numeric_type), promote_type(T, $numeric_type), D)
103+
end
104+
function Base.promote_rule(::Type{$numeric_type}, ::Type{Q}) where {T,D,Q<:$type{T,D}}
105+
return with_type_parameters(promote_quantity_on_value(Q, $numeric_type), promote_type(T, $numeric_type), D)
106+
end
107+
end
108+
end

src/fixed_rational.jl

+26-14
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,22 @@ struct FixedRational{T<:Integer,den} <: Real
1414
num::T
1515
global unsafe_fixed_rational(num::Integer, ::Type{T}, ::Val{den}) where {T,den} = new{T,den}(num)
1616
end
17+
@inline _denom(::Type{F}) where {T,den,F<:FixedRational{T,den}} = den
1718

1819
"""
1920
denom(F::FixedRational)
2021
2122
Since `den` can be a different type than `T`, this function
2223
is used to get the denominator as a `T`.
2324
"""
24-
denom(::Type{F}) where {T,den,F<:FixedRational{T,den}} = convert(T, den)
25+
denom(::Type{<:F}) where {T,F<:FixedRational{T}} = convert(T, _denom(F))
2526
denom(x::FixedRational) = denom(typeof(x))
2627

2728
# But, for Val(den), we need to use the same type as at init.
2829
# Otherwise, we would have type instability.
29-
val_denom(::Type{F}) where {T,den,F<:FixedRational{T,den}} = Val(den)
30+
val_denom(::Type{<:F}) where {F<:FixedRational} = Val(_denom(F))
3031

31-
Base.eltype(::Type{F}) where {T,F<:FixedRational{T}} = T
32+
Base.eltype(::Type{<:FixedRational{T}}) where {T} = T
3233

3334
const DEFAULT_NUMERATOR_TYPE = Int32
3435
const DEFAULT_DENOM = DEFAULT_NUMERATOR_TYPE(2^4 * 3^2 * 5^2 * 7)
@@ -73,23 +74,34 @@ Rational(x::F) where {F<:FixedRational} = Rational{eltype(F)}(x)
7374
Base.round(::Type{T}, x::F, r::RoundingMode=RoundNearest) where {T,F<:FixedRational} = div(convert(T, x.num), convert(T, denom(F)), r)
7475
Base.decompose(x::F) where {T,F<:FixedRational{T}} = (x.num, zero(T), denom(F))
7576

76-
# Promotion rules:
77-
function Base.promote_rule(::Type{<:FixedRational{T1,den1}}, ::Type{<:FixedRational{T2,den2}}) where {T1,T2,den1,den2}
78-
return error("Refusing to promote `FixedRational` types with mixed denominators. Use `Rational` instead.")
77+
# Promotion with self or rational-like
78+
function Base.promote_rule(::Type{F1}, ::Type{F2}) where {F1<:FixedRational,F2<:FixedRational}
79+
_denom(F1) == _denom(F2) ||
80+
error("Refusing to promote `FixedRational` types with mixed denominators. Use `Rational` instead.")
81+
return FixedRational{promote_type(eltype(F1), eltype(F2)), _denom(F1)}
7982
end
80-
function Base.promote_rule(::Type{<:FixedRational{T1,den}}, ::Type{<:FixedRational{T2,den}}) where {T1,T2,den}
81-
return FixedRational{promote_type(T1,T2),den}
83+
function Base.promote_rule(::Type{F}, ::Type{Rational{T2}}) where {F<:FixedRational,T2}
84+
return Rational{promote_type(eltype(F),T2)}
8285
end
83-
function Base.promote_rule(::Type{<:FixedRational{T1}}, ::Type{Rational{T2}}) where {T1,T2}
84-
return Rational{promote_type(T1,T2)}
85-
end
86-
function Base.promote_rule(::Type{<:FixedRational{T1}}, ::Type{T2}) where {T1,T2<:Real}
87-
return promote_type(Rational{T1}, T2)
86+
function Base.promote_rule(::Type{Rational{T2}}, ::Type{F}) where {F<:FixedRational,T2}
87+
return Rational{promote_type(eltype(F),T2)}
8888
end
89+
90+
# We want to consume integers
8991
function Base.promote_rule(::Type{F}, ::Type{<:Integer}) where {F<:FixedRational}
90-
# Want to consume integers:
9192
return F
9293
end
94+
function Base.promote_rule(::Type{<:Integer}, ::Type{F}) where {F<:FixedRational}
95+
return F
96+
end
97+
98+
# Promotion with general types promotes like a rational
99+
function Base.promote_rule(::Type{T}, ::Type{T2}) where {T2<:Real,T<:FixedRational}
100+
return promote_type(Rational{eltype(T)}, T2)
101+
end
102+
function Base.promote_rule(::Type{T2}, ::Type{T}) where {T2<:Real,T<:FixedRational}
103+
return promote_type(Rational{eltype(T)}, T2)
104+
end
93105

94106
Base.string(x::FixedRational) =
95107
let

0 commit comments

Comments
 (0)