Skip to content

Commit

Permalink
Merge pull request #85 from SymbolicML/real-quantity-2
Browse files Browse the repository at this point in the history
Add AbstractRealQuantity
  • Loading branch information
MilesCranmer authored Nov 23, 2023
2 parents 5f438b5 + 0609463 commit b27ed80
Show file tree
Hide file tree
Showing 16 changed files with 644 additions and 196 deletions.
48 changes: 26 additions & 22 deletions ext/DynamicQuantitiesUnitfulExt.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module DynamicQuantitiesUnitfulExt

import DynamicQuantities
using DynamicQuantities: DynamicQuantities, ABSTRACT_QUANTITY_TYPES

import Unitful
import Unitful: @u_str

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

Base.convert(::Type{Unitful.Quantity}, x::DynamicQuantities.Quantity) =
let
validate_upreferred()
cumulator = DynamicQuantities.ustrip(x)
dims = DynamicQuantities.dimension(x)
if dims isa DynamicQuantities.SymbolicDimensions
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`."))
for (_, _, Q) in ABSTRACT_QUANTITY_TYPES
@eval begin
function Base.convert(::Type{Unitful.Quantity}, x::$Q)
validate_upreferred()
cumulator = DynamicQuantities.ustrip(x)
dims = DynamicQuantities.dimension(x)
if dims isa DynamicQuantities.SymbolicDimensions
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`."))
end
equiv = unitful_equivalences()
for dim in keys(dims)
value = dims[dim]
iszero(value) && continue
cumulator *= equiv[dim]^value
end
cumulator
end
equiv = unitful_equivalences()
for dim in keys(dims)
value = dims[dim]
iszero(value) && continue
cumulator *= equiv[dim]^value
function Base.convert(::Type{$Q}, x::Unitful.Quantity{T}) where {T}
return convert($Q{T,DynamicQuantities.DEFAULT_DIM_TYPE}, x)
end
function Base.convert(::Type{$Q{T,D}}, x::Unitful.Quantity) where {T,R,D<:DynamicQuantities.AbstractDimensions{R}}
value = Unitful.ustrip(Unitful.upreferred(x))
dimension = convert(D, Unitful.dimension(x))
return $Q(convert(T, value), dimension)
end
cumulator
end

Base.convert(::Type{DynamicQuantities.Quantity}, x::Unitful.Quantity{T}) where {T} = convert(DynamicQuantities.Quantity{T,DynamicQuantities.DEFAULT_DIM_TYPE}, x)
Base.convert(::Type{DynamicQuantities.Quantity{T,D}}, x::Unitful.Quantity) where {T,R,D<:DynamicQuantities.AbstractDimensions{R}} =
let
value = Unitful.ustrip(Unitful.upreferred(x))
dimension = convert(D, Unitful.dimension(x))
return DynamicQuantities.Quantity(convert(T, value), dimension)
end
end

Base.convert(::Type{DynamicQuantities.Dimensions}, d::Unitful.Dimensions) = convert(DynamicQuantities.DEFAULT_DIM_TYPE, d)
Base.convert(::Type{DynamicQuantities.Dimensions{R}}, d::Unitful.Dimensions{D}) where {R,D} =
Expand Down
4 changes: 2 additions & 2 deletions src/DynamicQuantities.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module DynamicQuantities

export Units, Constants
export AbstractDimensions, AbstractQuantity, AbstractGenericQuantity, UnionAbstractQuantity
export Quantity, GenericQuantity, Dimensions, SymbolicDimensions, QuantityArray, DimensionError
export AbstractDimensions, AbstractQuantity, AbstractGenericQuantity, AbstractRealQuantity, UnionAbstractQuantity
export Quantity, GenericQuantity, RealQuantity, Dimensions, SymbolicDimensions, QuantityArray, DimensionError
export ustrip, dimension
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
export uparse, @u_str, sym_uparse, @us_str, uexpand, uconvert
Expand Down
9 changes: 5 additions & 4 deletions src/arrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ struct QuantityArray{T,N,D<:AbstractDimensions,Q<:UnionAbstractQuantity{T,D},V<:
end
end

# Construct with a Quantity (easier, as you can use the units):
QuantityArray(v::AbstractArray; kws...) = QuantityArray(v, DEFAULT_DIM_TYPE(; kws...))
for (type, base_type, default_type) in ABSTRACT_QUANTITY_TYPES
@eval begin
QuantityArray(v::AbstractArray{<:$base_type}, q::$type) = QuantityArray(v .* ustrip(q), dimension(q), typeof(q))
QuantityArray(v::AbstractArray{<:$base_type}, d::AbstractDimensions) = QuantityArray(v, d, $default_type)
@eval QuantityArray(v::AbstractArray{<:$base_type}, q::$type) = QuantityArray(v .* ustrip(q), dimension(q), typeof(q))

# Only define defaults for Quantity and GenericQuantity. Other types, the user needs to declare explicitly.
if type in (AbstractQuantity, AbstractGenericQuantity)
@eval QuantityArray(v::AbstractArray{<:$base_type}, d::AbstractDimensions) = QuantityArray(v, d, $default_type)
end
end
QuantityArray(v::QA) where {Q<:UnionAbstractQuantity,QA<:AbstractArray{Q}} =
Expand Down
1 change: 0 additions & 1 deletion src/constants.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module Constants

import ..DEFAULT_QUANTITY_TYPE
import ..Quantity
import ..Units as U
import ..Units: _add_prefixes

Expand Down
110 changes: 102 additions & 8 deletions src/disambiguities.jl
Original file line number Diff line number Diff line change
@@ -1,14 +1,108 @@
Base.isless(::AbstractQuantity, ::Missing) = missing
Base.isless(::Missing, ::AbstractQuantity) = missing
Base.:(==)(::AbstractQuantity, ::Missing) = missing
Base.:(==)(::Missing, ::AbstractQuantity) = missing
Base.isapprox(::AbstractQuantity, ::Missing; kws...) = missing
Base.isapprox(::Missing, ::AbstractQuantity; kws...) = missing
for op in (:isless, :(==), :isequal, :(<)), (type, _, _) in ABSTRACT_QUANTITY_TYPES
@eval begin
Base.$(op)(::$type, ::Missing) = missing
Base.$(op)(::Missing, ::$type) = missing
end
end
for op in (:isapprox,), (type, _, _) in ABSTRACT_QUANTITY_TYPES
@eval begin
Base.$(op)(::$type, ::Missing; kws...) = missing
Base.$(op)(::Missing, ::$type; kws...) = missing
end
end

Base.:(==)(::AbstractQuantity, ::WeakRef) = error("Cannot compare a quantity to a weakref")
Base.:(==)(::WeakRef, ::AbstractQuantity) = error("Cannot compare a weakref to a quantity")
for (type, _, _) in ABSTRACT_QUANTITY_TYPES
@eval begin
Base.:(==)(::$type, ::WeakRef) = error("Cannot compare a quantity to a weakref")
Base.:(==)(::WeakRef, ::$type) = error("Cannot compare a weakref to a quantity")
end
end

Base.:*(l::AbstractDimensions, r::Number) = error("Please use an `UnionAbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
Base.:*(l::Number, r::AbstractDimensions) = error("Please use an `UnionAbstractQuantity` for multiplication. You used multiplication on types: $(typeof(l)) and $(typeof(r)).")
Base.:/(l::AbstractDimensions, r::Number) = error("Please use an `UnionAbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")
Base.:/(l::Number, r::AbstractDimensions) = error("Please use an `UnionAbstractQuantity` for division. You used division on types: $(typeof(l)) and $(typeof(r)).")

# Promotion ambiguities
function Base.promote_rule(::Type{F}, ::Type{Bool}) where {F<:FixedRational}
return F
end
function Base.promote_rule(::Type{Bool}, ::Type{F}) where {F<:FixedRational}
return F
end
function Base.promote_rule(::Type{F}, ::Type{BigFloat}) where {F<:FixedRational}
return promote_type(Rational{eltype(F)}, BigFloat)
end
function Base.promote_rule(::Type{BigFloat}, ::Type{F}) where {F<:FixedRational}
return promote_type(Rational{eltype(F)}, BigFloat)
end
function Base.promote_rule(::Type{F}, ::Type{T}) where {F<:FixedRational,T<:AbstractIrrational}
return promote_type(Rational{eltype(F)}, T)
end
function Base.promote_rule(::Type{T}, ::Type{F}) where {F<:FixedRational,T<:AbstractIrrational}
return promote_type(Rational{eltype(F)}, T)
end

################################################################################
# Assorted calls found by Aqua: ################################################
################################################################################

for type in (Signed, Float64, Float32, Rational), op in (:flipsign, :copysign)
@eval function Base.$(op)(x::$type, y::AbstractRealQuantity)
return $(op)(x, ustrip(y))
end
end
for type in (:(Complex), :(Complex{Bool}))
@eval begin
function Base.:*(l::$type, r::AbstractRealQuantity)
new_quantity(typeof(r), l * ustrip(r), dimension(r))
end
function Base.:*(l::AbstractRealQuantity, r::$type)
new_quantity(typeof(l), ustrip(l) * r, dimension(l))
end
end
end
function Complex{T}(q::AbstractRealQuantity) where {T<:Real}
@assert iszero(dimension(q)) "$(typeof(q)): $(q) has dimensions! Use `ustrip` instead."
return Complex{T}(ustrip(q))
end
for type in (:Bool, :Complex)
@eval function $type(q::AbstractRealQuantity)
@assert iszero(dimension(q)) "$(typeof(q)): $(q) has dimensions! Use `ustrip` instead."
return $type(ustrip(q))
end
end
function Base.:/(l::Complex, r::AbstractRealQuantity)
new_quantity(typeof(r), l / ustrip(r), inv(dimension(r)))
end
function Base.:/(l::AbstractRealQuantity, r::Complex)
new_quantity(typeof(l), ustrip(l) / r, dimension(l))
end
for op in (:(==), :isequal), base_type in (AbstractIrrational, AbstractFloat)
@eval begin
function Base.$(op)(l::AbstractRealQuantity, r::$base_type)
return $(op)(ustrip(l), r) && iszero(dimension(l))
end
function Base.$(op)(l::$base_type, r::AbstractRealQuantity)
return $(op)(l, ustrip(r)) && iszero(dimension(r))
end
end
end
function Base.isless(l::AbstractRealQuantity, r::AbstractFloat)
iszero(dimension(l)) || throw(DimensionError(l, r))
return isless(ustrip(l), r)
end
function Base.isless(l::AbstractFloat, r::AbstractRealQuantity)
iszero(dimension(r)) || throw(DimensionError(l, r))
return isless(l, ustrip(r))
end
for (type, _, _) in ABSTRACT_QUANTITY_TYPES, numeric_type in (Bool, BigFloat)
@eval begin
function Base.promote_rule(::Type{Q}, ::Type{$numeric_type}) where {T,D,Q<:$type{T,D}}
return with_type_parameters(promote_quantity_on_value(Q, $numeric_type), promote_type(T, $numeric_type), D)
end
function Base.promote_rule(::Type{$numeric_type}, ::Type{Q}) where {T,D,Q<:$type{T,D}}
return with_type_parameters(promote_quantity_on_value(Q, $numeric_type), promote_type(T, $numeric_type), D)
end
end
end
40 changes: 26 additions & 14 deletions src/fixed_rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,22 @@ struct FixedRational{T<:Integer,den} <: Real
num::T
global unsafe_fixed_rational(num::Integer, ::Type{T}, ::Val{den}) where {T,den} = new{T,den}(num)
end
@inline _denom(::Type{F}) where {T,den,F<:FixedRational{T,den}} = den

"""
denom(F::FixedRational)
Since `den` can be a different type than `T`, this function
is used to get the denominator as a `T`.
"""
denom(::Type{F}) where {T,den,F<:FixedRational{T,den}} = convert(T, den)
denom(::Type{<:F}) where {T,F<:FixedRational{T}} = convert(T, _denom(F))
denom(x::FixedRational) = denom(typeof(x))

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

Base.eltype(::Type{F}) where {T,F<:FixedRational{T}} = T
Base.eltype(::Type{<:FixedRational{T}}) where {T} = T

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

# Promotion rules:
function Base.promote_rule(::Type{<:FixedRational{T1,den1}}, ::Type{<:FixedRational{T2,den2}}) where {T1,T2,den1,den2}
return error("Refusing to promote `FixedRational` types with mixed denominators. Use `Rational` instead.")
# Promotion with self or rational-like
function Base.promote_rule(::Type{F1}, ::Type{F2}) where {F1<:FixedRational,F2<:FixedRational}
_denom(F1) == _denom(F2) ||
error("Refusing to promote `FixedRational` types with mixed denominators. Use `Rational` instead.")
return FixedRational{promote_type(eltype(F1), eltype(F2)), _denom(F1)}
end
function Base.promote_rule(::Type{<:FixedRational{T1,den}}, ::Type{<:FixedRational{T2,den}}) where {T1,T2,den}
return FixedRational{promote_type(T1,T2),den}
function Base.promote_rule(::Type{F}, ::Type{Rational{T2}}) where {F<:FixedRational,T2}
return Rational{promote_type(eltype(F),T2)}
end
function Base.promote_rule(::Type{<:FixedRational{T1}}, ::Type{Rational{T2}}) where {T1,T2}
return Rational{promote_type(T1,T2)}
end
function Base.promote_rule(::Type{<:FixedRational{T1}}, ::Type{T2}) where {T1,T2<:Real}
return promote_type(Rational{T1}, T2)
function Base.promote_rule(::Type{Rational{T2}}, ::Type{F}) where {F<:FixedRational,T2}
return Rational{promote_type(eltype(F),T2)}
end

# We want to consume integers
function Base.promote_rule(::Type{F}, ::Type{<:Integer}) where {F<:FixedRational}
# Want to consume integers:
return F
end
function Base.promote_rule(::Type{<:Integer}, ::Type{F}) where {F<:FixedRational}
return F
end

# Promotion with general types promotes like a rational
function Base.promote_rule(::Type{T}, ::Type{T2}) where {T2<:Real,T<:FixedRational}
return promote_type(Rational{eltype(T)}, T2)
end
function Base.promote_rule(::Type{T2}, ::Type{T}) where {T2<:Real,T<:FixedRational}
return promote_type(Rational{eltype(T)}, T2)
end

Base.string(x::FixedRational) =
let
Expand Down
Loading

0 comments on commit b27ed80

Please sign in to comment.