Skip to content

Create AutoFloat type for units #66

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions src/DynamicQuantities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
export uparse, @u_str, sym_uparse, @us_str, uexpand, uconvert

include("fixed_rational.jl")
include("lazy_float.jl")

include("types.jl")
include("utils.jl")
include("math.jl")
Expand All @@ -22,6 +24,7 @@ export expand_units

import PackageExtensionCompat: @require_extensions
import .Units
import .Units: DEFAULT_UNIT_TYPE
import .Constants
import .UnitsParse: uparse, @u_str

Expand Down
9 changes: 4 additions & 5 deletions src/constants.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
module Constants

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

const _CONSTANT_SYMBOLS = Symbol[]
const _CONSTANT_VALUES = DEFAULT_QUANTITY_TYPE[]
const _CONSTANT_VALUES = DEFAULT_UNIT_TYPE[]

macro register_constant(name, value)
return esc(_register_constant(name, value))
Expand All @@ -20,7 +19,7 @@ end
function _register_constant(name::Symbol, value)
s = string(name)
return quote
const $name = $value
const $name = convert(DEFAULT_UNIT_TYPE, $value)
push!(_CONSTANT_SYMBOLS, Symbol($s))
push!(_CONSTANT_VALUES, $name)
end
Expand Down Expand Up @@ -87,7 +86,7 @@ end
)

# Measured
@register_constant alpha DEFAULT_QUANTITY_TYPE(7.2973525693e-3)
@register_constant alpha DEFAULT_UNIT_TYPE(7.2973525693e-3)
@register_constant u 1.66053906660e-27 * U.kg
@register_constant G 6.67430e-11 * U.m^3 / (U.kg * U.s^2)
@register_constant mu_0 4π * alpha * hbar / (e^2 * c)
Expand Down
43 changes: 43 additions & 0 deletions src/lazy_float.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
AutoFloat <: AbstractFloat

A wrapper around a `Float64` which automatically demotes
itself to `Float32` or `Float16` if interacting with
such a type. In other cases it will respect promotion
rules associated with `Float64`.
"""
struct AutoFloat <: AbstractFloat
value::Float64

AutoFloat(x::AbstractFloat) = new(convert(Float64, x))
end

Base.float(x::AutoFloat) = x.value

Base.convert(::Type{AutoFloat}, x::AutoFloat) = x
Base.convert(::Type{AutoFloat}, x::FixedRational) = AutoFloat(convert(Float64, x))
Base.convert(::Type{AutoFloat}, x::Number) = AutoFloat(x)
Base.convert(::Type{T}, x::AutoFloat) where {T<:Number} = convert(T, float(x))
Base.promote_rule(::Type{AutoFloat}, ::Type{T}) where {T<:AbstractFloat} = T
Base.promote_rule(::Type{AutoFloat}, ::Type{T}) where {T} = promote_type(Float64, T)

Base.show(io::IO, x::AutoFloat) = print(io, float(x))

Base.:(==)(a::AutoFloat, b::AutoFloat) = float(a) == float(b)
Base.:+(a::AutoFloat, b::AutoFloat) = AutoFloat(float(a) + float(b))
Base.:-(a::AutoFloat) = AutoFloat(-float(a))
Base.:-(a::AutoFloat, b::AutoFloat) = AutoFloat(float(a) + float(-b))
Base.:*(a::AutoFloat, b::AutoFloat) = AutoFloat(float(a) * float(b))
Base.inv(a::AutoFloat) = AutoFloat(inv(float(a)))
Base.abs(a::AutoFloat) = AutoFloat(abs(float(a)))
Base.:/(a::AutoFloat, b::AutoFloat) = a * inv(b)
Base.:^(a::AutoFloat, b::Int) = AutoFloat(float(a) ^ b)
Base.:^(a::AutoFloat, b::AutoFloat) = AutoFloat(float(a) ^ float(b))
Base.sqrt(a::AutoFloat) = AutoFloat(sqrt(float(a)))
Base.cbrt(a::AutoFloat) = AutoFloat(cbrt(float(a)))
Base.eps(::Type{AutoFloat}) = eps(Float64)

# Ambiguities:
for T in (:(Rational{<:Any}), :(Base.TwicePrecision), :AbstractChar, :Complex, :Number)
@eval AutoFloat(x::$T) = AutoFloat(float(x))
end
27 changes: 16 additions & 11 deletions src/symbolic_dimensions.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import .Units: UNIT_SYMBOLS, UNIT_MAPPING, UNIT_VALUES
import .Units: UNIT_SYMBOLS, UNIT_MAPPING, UNIT_VALUES, DEFAULT_UNIT_BASE_TYPE
import .Constants: CONSTANT_SYMBOLS, CONSTANT_MAPPING, CONSTANT_VALUES
import ..DEFAULT_DIM_BASE_TYPE

const SYMBOL_CONFLICTS = intersect(UNIT_SYMBOLS, CONSTANT_SYMBOLS)

Expand Down Expand Up @@ -93,6 +94,8 @@ function Base.convert(::Type{Quantity{T,D}}, q::Quantity{<:Any,<:SymbolicDimensi
return result
end

const DEFAULT_SYMBOLIC_UNIT_TYPE = Quantity{DEFAULT_UNIT_BASE_TYPE,SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}}

"""
uexpand(q::Quantity{<:Any,<:SymbolicDimensions})

Expand Down Expand Up @@ -269,20 +272,22 @@ module SymbolicUnitsParse
import ..CONSTANT_SYMBOLS
import ..SYMBOL_CONFLICTS
import ..SymbolicDimensions
import ..DEFAULT_SYMBOLIC_UNIT_TYPE
import ..DEFAULT_UNIT_BASE_TYPE
import ..DEFAULT_DIM_BASE_TYPE

import ...Quantity
import ...DEFAULT_VALUE_TYPE
import ...DEFAULT_DIM_BASE_TYPE

# Lazily create unit symbols (since there are so many)
module Constants
import ..CONSTANT_SYMBOLS
import ..SYMBOL_CONFLICTS
import ..SymbolicDimensions
import ..DEFAULT_SYMBOLIC_UNIT_TYPE
import ..DEFAULT_UNIT_BASE_TYPE
import ..DEFAULT_DIM_BASE_TYPE

import ..Quantity
import ..DEFAULT_VALUE_TYPE
import ..DEFAULT_DIM_BASE_TYPE

import ...Constants as EagerConstants

Expand All @@ -292,11 +297,11 @@ module SymbolicUnitsParse
CONSTANT_SYMBOLS_EXIST[] || lock(CONSTANT_SYMBOLS_LOCK) do
CONSTANT_SYMBOLS_EXIST[] && return nothing
for unit in setdiff(CONSTANT_SYMBOLS, SYMBOL_CONFLICTS)
@eval const $unit = Quantity(DEFAULT_VALUE_TYPE(1.0), SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}; $(unit)=1)
@eval const $unit = Quantity(DEFAULT_UNIT_BASE_TYPE(1.0), SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}; $(unit)=1)
end
# Evaluate conflicting symbols to non-symbolic form:
for unit in SYMBOL_CONFLICTS
@eval const $unit = convert(Quantity{DEFAULT_VALUE_TYPE,SymbolicDimensions}, EagerConstants.$unit)
@eval const $unit = convert(DEFAULT_SYMBOLIC_UNIT_TYPE, EagerConstants.$unit)
end
CONSTANT_SYMBOLS_EXIST[] = true
end
Expand All @@ -311,7 +316,7 @@ module SymbolicUnitsParse
UNIT_SYMBOLS_EXIST[] || lock(UNIT_SYMBOLS_LOCK) do
UNIT_SYMBOLS_EXIST[] && return nothing
for unit in UNIT_SYMBOLS
@eval const $unit = Quantity(DEFAULT_VALUE_TYPE(1.0), SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}; $(unit)=1)
@eval const $unit = Quantity(DEFAULT_UNIT_BASE_TYPE(1.0), SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}; $(unit)=1)
end
UNIT_SYMBOLS_EXIST[] = true
end
Expand All @@ -338,11 +343,11 @@ module SymbolicUnitsParse
_generate_unit_symbols()
Constants._generate_unit_symbols()
raw_result = eval(Meta.parse(raw_string))
return copy(as_quantity(raw_result))::Quantity{DEFAULT_VALUE_TYPE,SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}}
return copy(as_quantity(raw_result))::DEFAULT_SYMBOLIC_UNIT_TYPE
end

as_quantity(q::Quantity) = q
as_quantity(x::Number) = Quantity(convert(DEFAULT_VALUE_TYPE, x), SymbolicDimensions{DEFAULT_DIM_BASE_TYPE})
as_quantity(q::Quantity) = convert(DEFAULT_SYMBOLIC_UNIT_TYPE, q)
as_quantity(x::Number) = DEFAULT_SYMBOLIC_UNIT_TYPE(x)
as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))")
end

Expand Down
26 changes: 14 additions & 12 deletions src/units.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ module Units

import ..DEFAULT_DIM_TYPE
import ..DEFAULT_VALUE_TYPE
import ..DEFAULT_QUANTITY_TYPE
import ..Quantity
import ..Quantity
import ..AutoFloat

@assert DEFAULT_VALUE_TYPE == Float64 "`units.jl` must be updated to support a different default value type."
const DEFAULT_UNIT_BASE_TYPE = AutoFloat
const DEFAULT_UNIT_TYPE = Quantity{DEFAULT_UNIT_BASE_TYPE,DEFAULT_DIM_TYPE}

const _UNIT_SYMBOLS = Symbol[]
const _UNIT_VALUES = DEFAULT_QUANTITY_TYPE[]
const _UNIT_VALUES = DEFAULT_UNIT_TYPE[]

macro register_unit(name, value)
return esc(_register_unit(name, value))
Expand All @@ -22,7 +24,7 @@ end
function _register_unit(name::Symbol, value)
s = string(name)
return quote
const $name = $value
const $name = convert(DEFAULT_UNIT_TYPE, $value)
push!(_UNIT_SYMBOLS, Symbol($s))
push!(_UNIT_VALUES, $name)
end
Expand All @@ -37,19 +39,19 @@ function _add_prefixes(base_unit::Symbol, prefixes, register_function)
for (prefix, value) in zip(keys(all_prefixes), values(all_prefixes))
prefix in prefixes || continue
new_unit = Symbol(prefix, base_unit)
push!(expr.args, register_function(new_unit, :($value * $base_unit)))
push!(expr.args, register_function(new_unit, :(convert(DEFAULT_UNIT_TYPE, $value * $base_unit))))
end
return expr
end

# SI base units
@register_unit m DEFAULT_QUANTITY_TYPE(1.0, length=1)
@register_unit g DEFAULT_QUANTITY_TYPE(1e-3, mass=1)
@register_unit s DEFAULT_QUANTITY_TYPE(1.0, time=1)
@register_unit A DEFAULT_QUANTITY_TYPE(1.0, current=1)
@register_unit K DEFAULT_QUANTITY_TYPE(1.0, temperature=1)
@register_unit cd DEFAULT_QUANTITY_TYPE(1.0, luminosity=1)
@register_unit mol DEFAULT_QUANTITY_TYPE(1.0, amount=1)
@register_unit m Quantity(1.0, length=1)
@register_unit g Quantity(1e-3, mass=1)
@register_unit s Quantity(1.0, time=1)
@register_unit A Quantity(1.0, current=1)
@register_unit K Quantity(1.0, temperature=1)
@register_unit cd Quantity(1.0, luminosity=1)
@register_unit mol Quantity(1.0, amount=1)

@add_prefixes m (f, p, n, μ, u, c, d, m, k, M, G)
@add_prefixes g (μ, u, m, k)
Expand Down
8 changes: 4 additions & 4 deletions src/uparse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module UnitsParse
import ..Quantity
import ..DEFAULT_DIM_TYPE
import ..DEFAULT_VALUE_TYPE
import ..Units: UNIT_SYMBOLS
import ..Units: UNIT_SYMBOLS, DEFAULT_UNIT_TYPE
import ..Constants

function _generate_units_import()
Expand All @@ -30,11 +30,11 @@ the quantity corresponding to the speed of light multiplied by Hertz,
squared.
"""
function uparse(s::AbstractString)
return as_quantity(eval(Meta.parse(s)))::Quantity{DEFAULT_VALUE_TYPE,DEFAULT_DIM_TYPE}
return as_quantity(eval(Meta.parse(s)))::DEFAULT_UNIT_TYPE
end

as_quantity(q::Quantity) = q
as_quantity(x::Number) = Quantity(convert(DEFAULT_VALUE_TYPE, x), DEFAULT_DIM_TYPE)
as_quantity(q::Quantity) = convert(DEFAULT_UNIT_TYPE, q)
as_quantity(x::Number) = DEFAULT_UNIT_TYPE(x)
as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))")

"""
Expand Down
65 changes: 51 additions & 14 deletions test/unittests.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
using DynamicQuantities
using DynamicQuantities: FixedRational
using DynamicQuantities: DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
using DynamicQuantities: DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE, DEFAULT_UNIT_TYPE
using DynamicQuantities: AutoFloat
using DynamicQuantities: array_type, value_type, dim_type, quantity_type
using Ratios: SimpleRatio
using SaferIntegers: SafeInt16
using StaticArrays: SArray, MArray
using LinearAlgebra: norm
using Test

function show_string(i)
io = IOBuffer()
show(io, i)
return String(take!(io))
end

@testset "Basic utilities" begin

for T in [DEFAULT_VALUE_TYPE, Float16, Float32, Float64], R in [DEFAULT_DIM_BASE_TYPE, Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
Expand Down Expand Up @@ -379,17 +386,52 @@ end
@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_UNIT_TYPE
@test typeof(u"1f0") == DEFAULT_UNIT_TYPE
@test typeof(u"s"^2) == DEFAULT_UNIT_TYPE
@test typeof(u"Ω") == DEFAULT_UNIT_TYPE
@test typeof(u"Gyr") == DEFAULT_UNIT_TYPE
@test typeof(u"fm") == DEFAULT_UNIT_TYPE
@test typeof(u"fm"^2) == DEFAULT_UNIT_TYPE

# Test type demotion
@test typeof(1u"m") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(1f0u"m") == Quantity{Float32,DEFAULT_DIM_TYPE}
@test typeof(1.0u"m") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(Float16(1.0)u"m") == Quantity{Float16,DEFAULT_DIM_TYPE}

@test typeof(1u"m^2/s") == Quantity{Float64,DEFAULT_DIM_TYPE}
@test typeof(1f0u"m^2/s") == Quantity{Float32,DEFAULT_DIM_TYPE}
@test typeof(1.0u"m^2/s") == Quantity{Float64,DEFAULT_DIM_TYPE}

@test_throws LoadError eval(:(u":x"))
end

@testset "AutoFloat" begin
@test promote_type(AutoFloat, Float16) == Float16
@test promote_type(AutoFloat, Float32) == Float32
@test promote_type(AutoFloat, Float64) == Float64
@test promote_type(AutoFloat, BigFloat) == BigFloat
@test promote_type(AutoFloat, Int64) == Float64
@test promote_type(AutoFloat, ComplexF16) == promote_type(Float64, ComplexF16)

x = AutoFloat(1.5)
@test show_string(x) == "1.5"

@test -x == AutoFloat(-1.5)
@test abs(-x) == x
@test sqrt(x) == AutoFloat(sqrt(1.5))
@test cbrt(x) == AutoFloat(cbrt(1.5))
@test inv(x) == AutoFloat(inv(1.5))

y = AutoFloat(2.1)
@test x + y == AutoFloat(1.5 + 2.1)
@test x - y == AutoFloat(1.5 - 2.1)

# Should promote to array:
@test typeof([u"km/s", 1.5u"km/s"]) <: Vector{<:Quantity{Float64}}
end

@testset "Constants" begin
@test Constants.h * Constants.c / (1000.0u"nm") ≈ 1.9864458571489284e-19u"J"

Expand Down Expand Up @@ -421,11 +463,6 @@ end
@test convert(Rational, FixedRational{UInt8,6}(2)) === Rational{UInt8}(2)

# Showing rationals
function show_string(i)
io = IOBuffer()
show(io, i)
return String(take!(io))
end
@test show_string(FixedRational{Int,10}(2)) == "2"
@test show_string(FixedRational{Int,10}(11//10)) == "11//10"

Expand Down Expand Up @@ -642,7 +679,7 @@ end
@test promote(x, y) == (x, y)
@test_throws ErrorException promote(x, convert(FixedRational{Int32,100}, 10))
@test round(Missing, x) === missing
@test promote_type(typeof(u"km/s"), typeof(convert(Quantity{Float32}, u"km/s"))) <: Quantity{Float64}
@test promote_type(typeof(1.0u"km/s"), typeof(convert(Quantity{Float32}, u"km/s"))) <: Quantity{Float64}

x = 1.0u"m"
y = missing
Expand Down