Skip to content

Commit

Permalink
Get majority of tests working with RealQuantity
Browse files Browse the repository at this point in the history
  • Loading branch information
MilesCranmer committed Nov 21, 2023
1 parent 9424a22 commit 8f844a2
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 66 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
12 changes: 7 additions & 5 deletions src/fixed_rational.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@ 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))

num_type(::Type{F}) where {T,F<:FixedRational{T}} = T
num_type(::Type{<:FixedRational{T}}) where {T} = T
num_type(x::FixedRational) = num_type(typeof(x))

const DEFAULT_NUMERATOR_TYPE = Int32
const DEFAULT_DENOM = DEFAULT_NUMERATOR_TYPE(2^4 * 3^2 * 5^2 * 7)
Expand Down Expand Up @@ -75,9 +77,9 @@ Base.decompose(x::F) where {T,F<:FixedRational{T}} = (x.num, zero(T), denom(F))

# Promotion with self or rational-like
function Base.promote_rule(::Type{F1}, ::Type{F2}) where {F1<:FixedRational,F2<:FixedRational}
denom(F1) == denom(F2) ||
_denom(F1) == _denom(F2) ||
error("Refusing to promote `FixedRational` types with mixed denominators. Use `Rational` instead.")
return FixedRational{promote_type(num_type(F1), num_type(F2)),denom(F1)}
return FixedRational{promote_type(num_type(F1), num_type(F2)), _denom(F1)}
end
function Base.promote_rule(::Type{F}, ::Type{Rational{T2}}) where {F<:FixedRational,T2}
return Rational{promote_type(num_type(F),T2)}
Expand Down
8 changes: 4 additions & 4 deletions test/test_unitful.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import Ratios: SimpleRatio
import SaferIntegers: SafeInt16
using Test

risapprox(x::Unitful.Quantity, y::Unitful.Quantity; kws...) =
let (xfloat, yfloat) = (Unitful.ustrip Unitful.upreferred).((x, y))
return isapprox(xfloat, yfloat; kws...)
end
function risapprox(x::Unitful.Quantity, y::Unitful.Quantity; kws...)
(xfloat, yfloat) = (Unitful.ustrip Unitful.upreferred).((x, y))
return isapprox(xfloat, yfloat; kws...)
end

for T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_BASE_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
D = DynamicQuantities.Dimensions{R}
Expand Down
71 changes: 36 additions & 35 deletions test/unittests.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using DynamicQuantities
using DynamicQuantities: FixedRational
using DynamicQuantities: DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
using DynamicQuantities: DEFAULT_QUANTITY_TYPE, DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
using DynamicQuantities: array_type, value_type, dim_type, quantity_type
using DynamicQuantities: GenericQuantity
using DynamicQuantities: GenericQuantity, with_type_parameters, constructorof
using Ratios: SimpleRatio
using SaferIntegers: SafeInt16
using StaticArrays: SArray, MArray
Expand All @@ -20,7 +20,7 @@ end

@testset "Basic utilities" begin

for Q in [Quantity, GenericQuantity], T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_BASE_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
for Q in [Quantity, GenericQuantity, RealQuantity], T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_BASE_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
D = Dimensions{R}
x = Q(T(0.2), D, length=1, mass=2.5)

Expand Down Expand Up @@ -390,32 +390,32 @@ end
@test utime(x) == -2

y = 0.9u"sqrt(mΩ)"
@test typeof(y) == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(y) == with_type_parameters(DEFAULT_QUANTITY_TYPE, Float64, DEFAULT_DIM_TYPE)
@test ustrip(y) 0.02846049894151541
@test ucurrent(y) == -1
@test ulength(y) == 1

y = BigFloat(0.3) * u"mΩ"
@test typeof(y) == Quantity{BigFloat,DEFAULT_DIM_TYPE}
@test typeof(y) == with_type_parameters(DEFAULT_QUANTITY_TYPE, BigFloat, DEFAULT_DIM_TYPE)
@test ustrip(y) 0.0003
@test ulength(y) == 2

y32 = convert(Quantity{Float32,Dimensions{Rational{Int16}}}, y)
@test typeof(y32) == Quantity{Float32,Dimensions{Rational{Int16}}}
y32 = convert(with_type_parameters(DEFAULT_QUANTITY_TYPE, Float32, Dimensions{Rational{Int16}}), y)
@test typeof(y32) == with_type_parameters(DEFAULT_QUANTITY_TYPE, Float32, Dimensions{Rational{Int16}})
@test ustrip(y32) 0.0003

z = u"yr"
@test utime(z) == 1
@test ustrip(z) 60 * 60 * 24 * 365.25

# Test type stability of extreme range of units
@test typeof(u"1") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"1f0") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"s"^2) == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"Gyr") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"fm") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"fm"^2) == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(u"1") == DEFAULT_QUANTITY_TYPE
@test typeof(u"1f0") == DEFAULT_QUANTITY_TYPE
@test typeof(u"s"^2) == DEFAULT_QUANTITY_TYPE
@test typeof(u"") == DEFAULT_QUANTITY_TYPE
@test typeof(u"Gyr") == DEFAULT_QUANTITY_TYPE
@test typeof(u"fm") == DEFAULT_QUANTITY_TYPE
@test typeof(u"fm"^2) == DEFAULT_QUANTITY_TYPE

@test_throws LoadError eval(:(u":x"))
end
Expand Down Expand Up @@ -486,10 +486,10 @@ end
a = 0.5u"km/s"
b = MyNumber(0.5)
ar = [a, b]
@test ar isa Vector{Number}
@test ar isa Vector{Real}
@test a === ar[1]
@test b === ar[2]
@test promote_type(MyNumber, typeof(a)) == Number
@test promote_type(MyNumber, typeof(a)) == Real

# Explicit conversion so coverage can see it:
D = DEFAULT_DIM_TYPE
Expand Down Expand Up @@ -609,7 +609,7 @@ end

q = 1.5us"km/s"
@test q == 1.5 * us"km" / us"s"
@test typeof(q) <: Quantity{Float64,<:SymbolicDimensions}
@test typeof(q) <: with_type_parameters(DEFAULT_QUANTITY_TYPE, Float64, SymbolicDimensions{DEFAULT_DIM_BASE_TYPE})
@test string(dimension(q)) == "s⁻¹ km"
@test uexpand(q) == 1.5u"km/s"
@test string(dimension(us"Constants.au^1.5")) == "au³ᐟ²"
Expand Down Expand Up @@ -683,13 +683,13 @@ end
@test_throws DimensionError uconvert(us"nm * J", 5e-9u"m")

# Types:
@test typeof(uconvert(us"nm", 5e-9u"m")) <: Quantity{Float64,<:SymbolicDimensions}
@test typeof(uconvert(us"nm", 5e-9u"m")) <: RealQuantity{Float64,<:SymbolicDimensions}
@test typeof(uconvert(us"nm", GenericQuantity(5e-9u"m"))) <: GenericQuantity{Float64,<:SymbolicDimensions}
@test uconvert(GenericQuantity(us"nm"), GenericQuantity(5e-9u"m")) 5us"nm"
@test uconvert(GenericQuantity(us"nm"), GenericQuantity(5e-9u"m")) GenericQuantity(5us"nm")

# We only want to convert the dimensions, and ignore the quantity type:
@test typeof(uconvert(GenericQuantity(us"nm"), 5e-9u"m")) <: Quantity{Float64,<:SymbolicDimensions}
@test typeof(uconvert(GenericQuantity(us"nm"), 5e-9u"m")) <: RealQuantity{Float64,<:SymbolicDimensions}

q = 1.5u"Constants.M_sun"
qs = uconvert(us"Constants.M_sun", 5.0 * q)
Expand All @@ -704,7 +704,7 @@ end
VERSION >= v"1.8" &&
@test_throws "You passed a quantity" uconvert(1.2us"m", 1.0u"m")

for Q in (Quantity, GenericQuantity)
for Q in (RealQuantity, Quantity, GenericQuantity)
# Different types require converting both arguments:
q = convert(Q{Float16}, 1.5u"g")
qs = uconvert(convert(Q{Float16}, us"g"), 5 * q)
Expand Down Expand Up @@ -769,10 +769,10 @@ end
x = 1.0u"m"
y = x ^ (3//2)
@test y == Quantity(1.0, length=3//2)
@test typeof(y) == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(y) == RealQuantity{Float64,DEFAULT_DIM_TYPE}
end

for Q in (Quantity, GenericQuantity)
for Q in (RealQuantity, Quantity, GenericQuantity)
@testset "Arrays" begin
@testset "Basics" begin
x = QuantityArray(randn(32), Q(u"km/s"))
Expand All @@ -798,7 +798,7 @@ for Q in (Quantity, GenericQuantity)

# Test default constructors:
@test QuantityArray(ones(3), u"m/s") == QuantityArray(ones(3), length=1, time=-1)
@test typeof(QuantityArray(ones(3), u"m/s")) <: QuantityArray{Float64,1,<:Dimensions,<:Quantity,<:Array}
@test typeof(QuantityArray(ones(3), u"m/s")) <: QuantityArray{Float64,1,<:Dimensions,<:constructorof(DEFAULT_QUANTITY_TYPE),<:Array}

# We can create quantity arrays with generic quantity
@test typeof(QuantityArray([[1.0], [2.0, 3.0]], dimension(u"m/s"))) <: QuantityArray{<:Any,1,<:Dimensions,<:GenericQuantity,<:Array}
Expand Down Expand Up @@ -964,14 +964,14 @@ for Q in (Quantity, GenericQuantity)
@test ustrip(x .* y) == ustrip(x) .* ustrip(y)
end

Q == Quantity && @testset "Broadcast different arrays" begin
Q in (Quantity, RealQuantity) && @testset "Broadcast different arrays" begin
f(x, y, z, w) = x * y + z * w
g(x, y, z, w) = f.(x, y, z, w)

x = randn(32)
y = QuantityArray(randn(32), u"km/s")
z = rand(1:10, 32)
w = Quantity{Float32}(u"m/s")
w = Q{Float32}(u"m/s")
@test typeof(g(x, y, z, w)) <: QuantityArray{Float64}

y32 = QuantityArray(ustrip(y), dimension(y))
Expand All @@ -990,7 +990,7 @@ for Q in (Quantity, GenericQuantity)
@test typeof(b .* y) <: QuantityArray{Float64}
end

Q == Quantity && @testset "Broadcast scalars" begin
Q in (RealQuantity, Quantity) && @testset "Broadcast scalars" begin
for (x, qx) in ((0.5, 0.5u"s"), ([0.5, 0.2], GenericQuantity([0.5, 0.2], time=1)))
@test size(qx) == size(x)
@test length(qx) == length(x)
Expand Down Expand Up @@ -1128,8 +1128,8 @@ end
qy = QuantityArray(y; length=1)

@test typeof(convert(typeof(qx), qy)) == typeof(qx)
@test convert(typeof(qx), qy)[1] isa Quantity{Float64}
@test convert(typeof(qx), qy)[1] == convert(Quantity{Float64}, qy[1])
@test convert(typeof(qx), qy)[1] isa RealQuantity{Float64}
@test convert(typeof(qx), qy)[1] == convert(RealQuantity{Float64}, qy[1])
end
end

Expand All @@ -1152,21 +1152,21 @@ end
:log, :log2, :log10, :log1p, :exp, :exp2, :exp10, :expm1, :frexp, :exponent,
:atan, :atand
)
for Q in (Quantity, GenericQuantity), D in (Dimensions, SymbolicDimensions), f in functions
for Q in (RealQuantity, Quantity, GenericQuantity), D in (Dimensions, SymbolicDimensions), f in functions
# Only test on valid domain
valid_inputs = filter(
x -> is_input_valid(eval(f), x),
5rand(100) .- 2.5
)
for x in valid_inputs[1:3]
qx_dimensionless = Quantity(x, D)
qx_dimensions = Quantity(x, convert(D, dimension(u"m/s")))
qx_dimensionless = Q(x, D)
qx_dimensions = Q(x, convert(D, dimension(u"m/s")))
@eval @test $f($qx_dimensionless) == $f($x)
@eval @test_throws DimensionError $f($qx_dimensions)
if f in (:atan, :atand)
for y in valid_inputs[end-3:end]
qy_dimensionless = Quantity(y, D)
qy_dimensions = Quantity(y, convert(D, dimension(u"m/s")))
qy_dimensionless = Q(y, D)
qy_dimensions = Q(y, convert(D, dimension(u"m/s")))
@eval @test $f($y, $qx_dimensionless) == $f($y, $x)
@eval @test $f($qy_dimensionless, $x) == $f($y, $x)
@eval @test $f($qy_dimensionless, $qx_dimensionless) == $f($y, $x)
Expand All @@ -1189,8 +1189,9 @@ end
:floor, :trunc, :ceil, :significand,
:ldexp, :round,
)
for Q in (Quantity, GenericQuantity), D in (Dimensions, SymbolicDimensions), f in functions
for Q in (RealQuantity, Quantity, GenericQuantity), D in (Dimensions, SymbolicDimensions), f in functions
T = f in (:abs, :real, :imag, :conj) ? ComplexF64 : Float64
T <: Complex && Q == RealQuantity && continue
if f == :modf # Functions that return multiple outputs
for x in 5rand(T, 3) .- 2.5
dim = convert(D, dimension(u"m/s"))
Expand Down Expand Up @@ -1251,7 +1252,7 @@ end
end

@testset "Test div" begin
for Q in (Quantity, GenericQuantity)
for Q in (RealQuantity, Quantity, GenericQuantity)
x = Q{Int}(10, length=1)
y = Q{Int}(3, mass=-1)
@test div(x, y) == Q{Int}(3, length=1, mass=1)
Expand Down

0 comments on commit 8f844a2

Please sign in to comment.