Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AbstractRealQuantity #85

Merged
merged 39 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f4b8112
[WIP] Trying to add RealQuantity
MilesCranmer Nov 12, 2023
af8815f
`isapprox` should throw error on mismatched dimensions
MilesCranmer Nov 19, 2023
c811845
Fix isapprox usage in tests
MilesCranmer Nov 19, 2023
54bb106
Generalize comparison operators
MilesCranmer Nov 19, 2023
adee292
Clean up more ambiguities
MilesCranmer Nov 19, 2023
d4cda51
Get simple complex operations working for RealQuantity
MilesCranmer Nov 19, 2023
9424a22
More ambiguities found by Aqua
MilesCranmer Nov 19, 2023
8f844a2
Get majority of tests working with RealQuantity
MilesCranmer Nov 19, 2023
27247a2
Fix scitypes test
MilesCranmer Nov 19, 2023
7bfbe2e
Fix conversion to base type
MilesCranmer Nov 19, 2023
0e41261
Change back to `eltype`
MilesCranmer Nov 19, 2023
4667128
Refactor some promotion rules
MilesCranmer Nov 19, 2023
745cd9e
No need to redefine `identity`
MilesCranmer Nov 21, 2023
79bfd78
Fix ambiguity in `div`
MilesCranmer Nov 21, 2023
037b6f2
Refactor promotion rules
MilesCranmer Nov 21, 2023
e90d046
Test all ambiguities
MilesCranmer Nov 21, 2023
814fa7e
Help coveralls see coverage
MilesCranmer Nov 21, 2023
d84bb6a
Reduce unnecessary methods
MilesCranmer Nov 21, 2023
664b188
Improve coverage
MilesCranmer Nov 21, 2023
c643ea6
Back to `Quantity` as default
MilesCranmer Nov 21, 2023
72d19eb
Cleanup docs
MilesCranmer Nov 21, 2023
75fa9e0
Fix coverage
MilesCranmer Nov 21, 2023
bc1a90b
Export `AbstractRealQuantity`
MilesCranmer Nov 21, 2023
2694747
Proper definition of `mod` and `rem`
MilesCranmer Nov 21, 2023
8c24478
Clean up conversion to numbers
MilesCranmer Nov 21, 2023
807bd21
Refactor div definition
MilesCranmer Nov 23, 2023
7d9aad8
Add unittest for ranges
MilesCranmer Nov 23, 2023
8eb83f7
Use `===` instead of `==`
MilesCranmer Nov 23, 2023
88ea317
Refactor disambiguities
MilesCranmer Nov 23, 2023
5b2b067
Split up `rem`/`mod` definitions
MilesCranmer Nov 23, 2023
cd339b6
Reduce invalidations
MilesCranmer Nov 23, 2023
4786b65
Expand measurements testing
MilesCranmer Nov 23, 2023
466c907
Clean up method invalidations
MilesCranmer Nov 23, 2023
b05cf1d
Delete useless `div` methods
MilesCranmer Nov 23, 2023
80cd5d3
Refactor definition of `mod`
MilesCranmer Nov 23, 2023
7999158
Improve test coverage
MilesCranmer Nov 23, 2023
50e3f2d
Refactor disambiguities
MilesCranmer Nov 23, 2023
58e6bf3
Fix complex ambiguity
MilesCranmer Nov 23, 2023
0609463
Avoid `rem` tests on earlier Julia versions
MilesCranmer Nov 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
gaurav-arya marked this conversation as resolved.
Show resolved Hide resolved
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
MilesCranmer marked this conversation as resolved.
Show resolved Hide resolved
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
Loading