From 55fbf51d370ecc5a1477ef39409b075afee339ed Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 29 Jun 2023 07:33:45 -0400 Subject: [PATCH 1/3] Create LazyFloat64 type for units --- src/DynamicQuantities.jl | 4 +++- src/lazy_float.jl | 32 +++++++++++++++++++++++++++++++ src/units.jl | 41 ++++++++++++++++++++-------------------- test/unittests.jl | 25 ++++++++++++++++-------- 4 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 src/lazy_float.jl diff --git a/src/DynamicQuantities.jl b/src/DynamicQuantities.jl index a636b171..09a16bed 100644 --- a/src/DynamicQuantities.jl +++ b/src/DynamicQuantities.jl @@ -6,13 +6,15 @@ export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount export uparse, @u_str include("fixed_rational.jl") +include("lazy_float.jl") + include("types.jl") include("utils.jl") include("math.jl") include("units.jl") import Requires: @init, @require -import .Units: uparse, @u_str +import .Units: uparse, @u_str, DEFAULT_UNIT_TYPE if !isdefined(Base, :get_extension) @init @require Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" include("../ext/DynamicQuantitiesUnitfulExt.jl") diff --git a/src/lazy_float.jl b/src/lazy_float.jl new file mode 100644 index 00000000..af0e96ef --- /dev/null +++ b/src/lazy_float.jl @@ -0,0 +1,32 @@ +# This is used to store floats without forcing promotion on other +# numeric types. +struct LazyFloat64 <: AbstractFloat + value::Float64 +end + +LazyFloat64(x::LazyFloat64) = x +LazyFloat64(x::Number) = LazyFloat64(convert(Float64, x)) +float(x::LazyFloat64) = x.value + +Base.convert(::Type{LazyFloat64}, x::LazyFloat64) = x +Base.convert(::Type{LazyFloat64}, x::FixedRational) = LazyFloat64(convert(Float64, x)) +Base.convert(::Type{LazyFloat64}, x::Number) = LazyFloat64(x) +Base.convert(::Type{T}, x::LazyFloat64) where {T<:Number} = convert(T, float(x)) +Base.promote_rule(::Type{LazyFloat64}, ::Type{T}) where {T} = T + +(::Type{T})(x::LazyFloat64) where {T<:Number} = T(float(x)) + +Base.show(io::IO, x::LazyFloat64) = print(io, float(x)) + +Base.:+(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) + float(b)) +Base.:-(a::LazyFloat64) = LazyFloat64(-float(a)) +Base.:-(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) - float(b)) +Base.:*(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) * float(b)) +Base.inv(a::LazyFloat64) = LazyFloat64(inv(float(a))) +Base.abs(a::LazyFloat64) = LazyFloat64(abs(float(a))) +Base.:/(a::LazyFloat64, b::LazyFloat64) = a * inv(b) +Base.:^(a::LazyFloat64, b::Int) = LazyFloat64(float(a) ^ b) +Base.:^(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) ^ float(b)) +Base.sqrt(a::LazyFloat64) = LazyFloat64(sqrt(float(a))) +Base.cbrt(a::LazyFloat64) = LazyFloat64(cbrt(float(a))) +Base.eps(::Type{LazyFloat64}) = eps(Float64) diff --git a/src/units.jl b/src/units.jl index bbba5ffa..e0887505 100644 --- a/src/units.jl +++ b/src/units.jl @@ -5,8 +5,9 @@ export uparse, @u_str import ..DEFAULT_DIM_TYPE import ..DEFAULT_VALUE_TYPE import ..Quantity +import ..LazyFloat64 -@assert DEFAULT_VALUE_TYPE == Float64 "`units.jl` must be updated to support a different default value type." +const DEFAULT_UNIT_TYPE = LazyFloat64 macro add_prefixes(base_unit, prefixes) @assert prefixes.head == :tuple @@ -23,26 +24,26 @@ function _add_prefixes(base_unit::Symbol, prefixes) for (prefix, value) in zip(keys(all_prefixes), values(all_prefixes)) prefix in prefixes || continue new_unit = Symbol(prefix, base_unit) - push!(expr.args, :(const $new_unit = $value * $base_unit)) + push!(expr.args, :(const $new_unit = DEFAULT_UNIT_TYPE($value) * $base_unit)) end return expr end # SI base units "Length in meters. Available variants: `fm`, `pm`, `nm`, `μm` (/`um`), `cm`, `dm`, `mm`, `km`, `Mm`, `Gm`." -const m = Quantity(1.0, length=1) +const m = Quantity(DEFAULT_UNIT_TYPE(1.0), length=1) "Mass in grams. Available variants: `μg` (/`ug`), `mg`, `kg`." -const g = Quantity(1e-3, mass=1) +const g = Quantity(DEFAULT_UNIT_TYPE(1e-3), mass=1) "Time in seconds. Available variants: `fs`, `ps`, `ns`, `μs` (/`us`), `ms`, `min`, `h` (/`hr`), `day`, `yr`, `kyr`, `Myr`, `Gyr`." -const s = Quantity(1.0, time=1) +const s = Quantity(DEFAULT_UNIT_TYPE(1.0), time=1) "Current in Amperes. Available variants: `nA`, `μA` (/`uA`), `mA`, `kA`." -const A = Quantity(1.0, current=1) +const A = Quantity(DEFAULT_UNIT_TYPE(1.0), current=1) "Temperature in Kelvin. Available variant: `mK`." -const K = Quantity(1.0, temperature=1) +const K = Quantity(DEFAULT_UNIT_TYPE(1.0), temperature=1) "Luminosity in candela. Available variant: `mcd`." -const cd = Quantity(1.0, luminosity=1) +const cd = Quantity(DEFAULT_UNIT_TYPE(1.0), luminosity=1) "Amount in moles. Available variant: `mmol`." -const mol = Quantity(1.0, amount=1) +const mol = Quantity(DEFAULT_UNIT_TYPE(1.0), amount=1) @add_prefixes m (f, p, n, μ, u, c, d, m, k, M, G) @add_prefixes g (μ, u, m, k) @@ -56,9 +57,9 @@ const mol = Quantity(1.0, amount=1) "Frequency in Hertz. Available variants: `kHz`, `MHz`, `GHz`." const Hz = inv(s) "Force in Newtons." -const N = kg * m / s^2 +const N = kg * m / (s * s) "Pressure in Pascals. Available variant: `kPa`." -const Pa = N / m^2 +const Pa = N / (m * m) "Energy in Joules. Available variant: `kJ`." const J = N * m "Power in Watts. Available variants: `kW`, `MW`, `GW`." @@ -89,11 +90,11 @@ const T = N / (A * m) # Common assorted units ## Time -const min = 60 * s -const h = 60 * min +const min = DEFAULT_UNIT_TYPE(60) * s +const h = DEFAULT_UNIT_TYPE(60) * min const hr = h -const day = 24 * h -const yr = 365.25 * day +const day = DEFAULT_UNIT_TYPE(24) * h +const yr = DEFAULT_UNIT_TYPE(365.25) * day @add_prefixes min () @add_prefixes h () @@ -103,13 +104,13 @@ const yr = 365.25 * day ## Volume "Volume in liters. Available variants: `mL`, `dL`." -const L = dm^3 +const L = dm * dm * dm @add_prefixes L (m, d) ## Pressure "Pressure in bars." -const bar = 100 * kPa +const bar = DEFAULT_UNIT_TYPE(100) * kPa @add_prefixes bar () @@ -126,12 +127,12 @@ Parse a string containing an expression of units and return the corresponding `Quantity` object with `Float64` value. For example, `uparse("m/s")` would be parsed to `Quantity(1.0, length=1, time=-1)`. """ -function uparse(s::AbstractString) - return as_quantity(eval(Meta.parse(s)))::Quantity{DEFAULT_VALUE_TYPE,DEFAULT_DIM_TYPE} +function uparse(s::AbstractString)::Quantity{LazyFloat64,DEFAULT_DIM_TYPE} + return as_quantity(eval(Meta.parse(s))) end as_quantity(q::Quantity) = q -as_quantity(x::Number) = Quantity(convert(DEFAULT_VALUE_TYPE, x), DEFAULT_DIM_TYPE) +as_quantity(x::Number) = Quantity(convert(LazyFloat64, x), DEFAULT_DIM_TYPE) as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))") """ diff --git a/test/unittests.jl b/test/unittests.jl index cea8a5e5..6076c4d8 100644 --- a/test/unittests.jl +++ b/test/unittests.jl @@ -1,6 +1,6 @@ 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 Ratios: SimpleRatio using SaferIntegers: SafeInt16 using Test @@ -335,13 +335,22 @@ 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") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} + @test typeof(u"1f0") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} + @test typeof(u"s"^2) == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} + @test typeof(u"Ω") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} + @test typeof(u"Gyr") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} + @test typeof(u"fm") == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} + @test typeof(u"fm"^2) == Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} + + # Test type demotion + @test typeof(1u"m") == Quantity{Int64,DEFAULT_DIM_TYPE} + @test typeof(1f0u"m") == Quantity{Float32,DEFAULT_DIM_TYPE} + @test typeof(1.0u"m") == Quantity{Float64,DEFAULT_DIM_TYPE} + + @test typeof(1u"m^2/s") == Quantity{Int64,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 From 943c7489bbe3ed562c840b1157d4ec3055241088 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 29 Jun 2023 07:44:19 -0400 Subject: [PATCH 2/3] Ensure consistency of unit types --- src/units.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/units.jl b/src/units.jl index e0887505..4c767921 100644 --- a/src/units.jl +++ b/src/units.jl @@ -57,9 +57,9 @@ const mol = Quantity(DEFAULT_UNIT_TYPE(1.0), amount=1) "Frequency in Hertz. Available variants: `kHz`, `MHz`, `GHz`." const Hz = inv(s) "Force in Newtons." -const N = kg * m / (s * s) +const N = kg * m / s^2 "Pressure in Pascals. Available variant: `kPa`." -const Pa = N / (m * m) +const Pa = N / m^2 "Energy in Joules. Available variant: `kJ`." const J = N * m "Power in Watts. Available variants: `kW`, `MW`, `GW`." @@ -104,7 +104,7 @@ const yr = DEFAULT_UNIT_TYPE(365.25) * day ## Volume "Volume in liters. Available variants: `mL`, `dL`." -const L = dm * dm * dm +const L = dm^3 @add_prefixes L (m, d) @@ -127,12 +127,12 @@ Parse a string containing an expression of units and return the corresponding `Quantity` object with `Float64` value. For example, `uparse("m/s")` would be parsed to `Quantity(1.0, length=1, time=-1)`. """ -function uparse(s::AbstractString)::Quantity{LazyFloat64,DEFAULT_DIM_TYPE} +function uparse(s::AbstractString)::Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} return as_quantity(eval(Meta.parse(s))) end as_quantity(q::Quantity) = q -as_quantity(x::Number) = Quantity(convert(LazyFloat64, x), DEFAULT_DIM_TYPE) +as_quantity(x::Number) = Quantity(convert(DEFAULT_UNIT_TYPE, x), DEFAULT_DIM_TYPE) as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))") """ From a1cfbbb432e0e02b817dd636d6e0094aa8b7fc4c Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 9 Jul 2023 17:06:39 -0400 Subject: [PATCH 3/3] Clean up merge --- src/constants.jl | 9 ++++----- src/lazy_float.jl | 3 ++- src/units.jl | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/constants.jl b/src/constants.jl index 736a71b4..2d401735 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -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)) @@ -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 @@ -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) diff --git a/src/lazy_float.jl b/src/lazy_float.jl index af0e96ef..24789901 100644 --- a/src/lazy_float.jl +++ b/src/lazy_float.jl @@ -12,7 +12,8 @@ Base.convert(::Type{LazyFloat64}, x::LazyFloat64) = x Base.convert(::Type{LazyFloat64}, x::FixedRational) = LazyFloat64(convert(Float64, x)) Base.convert(::Type{LazyFloat64}, x::Number) = LazyFloat64(x) Base.convert(::Type{T}, x::LazyFloat64) where {T<:Number} = convert(T, float(x)) -Base.promote_rule(::Type{LazyFloat64}, ::Type{T}) where {T} = T +Base.promote_rule(::Type{LazyFloat64}, ::Type{T}) where {T<:AbstractFloat} = T +Base.promote_rule(::Type{LazyFloat64}, ::Type{T}) where {T} = promote_type(Float64, T) (::Type{T})(x::LazyFloat64) where {T<:Number} = T(float(x)) diff --git a/src/units.jl b/src/units.jl index 254b898e..041ee78a 100644 --- a/src/units.jl +++ b/src/units.jl @@ -7,7 +7,7 @@ import ..Quantity import ..LazyFloat64 const DEFAULT_UNIT_BASE_TYPE = LazyFloat64 -const DEFAULT_UNIT_TYPE = Quantity{DEFAULT_UNIT_TYPE,DEFAULT_DIM_TYPE} +const DEFAULT_UNIT_TYPE = Quantity{DEFAULT_UNIT_BASE_TYPE,DEFAULT_DIM_TYPE} const _UNIT_SYMBOLS = Symbol[] const _UNIT_VALUES = DEFAULT_UNIT_TYPE[]