From 55fbf51d370ecc5a1477ef39409b075afee339ed Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 29 Jun 2023 07:33:45 -0400 Subject: [PATCH 1/9] 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/9] 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/9] 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[] From 2c7f91b91457837fd80c1d4fb51acb3ab0031a3b Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 16 Oct 2023 09:15:48 +0100 Subject: [PATCH 4/9] Fix ambiguities in LazyFloat64 --- src/lazy_float.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lazy_float.jl b/src/lazy_float.jl index 24789901..f52114e0 100644 --- a/src/lazy_float.jl +++ b/src/lazy_float.jl @@ -2,11 +2,11 @@ # numeric types. struct LazyFloat64 <: AbstractFloat value::Float64 + + LazyFloat64(x::AbstractFloat) = new(convert(Float64, x)) end -LazyFloat64(x::LazyFloat64) = x -LazyFloat64(x::Number) = LazyFloat64(convert(Float64, x)) -float(x::LazyFloat64) = x.value +Base.float(x::LazyFloat64) = x.value Base.convert(::Type{LazyFloat64}, x::LazyFloat64) = x Base.convert(::Type{LazyFloat64}, x::FixedRational) = LazyFloat64(convert(Float64, x)) @@ -15,8 +15,6 @@ Base.convert(::Type{T}, x::LazyFloat64) where {T<:Number} = convert(T, float(x)) 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)) - Base.show(io::IO, x::LazyFloat64) = print(io, float(x)) Base.:+(a::LazyFloat64, b::LazyFloat64) = LazyFloat64(float(a) + float(b)) @@ -31,3 +29,8 @@ 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) + +# Ambiguities: +for T in (:(Rational{<:Any}), :(Base.TwicePrecision), :AbstractChar, :Complex, :Number) + @eval LazyFloat64(x::$T) = LazyFloat64(float(x)) +end From 3cb83b0ebba51a0867497f63b84ef5fa3b6610a8 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 16 Oct 2023 09:16:07 +0100 Subject: [PATCH 5/9] Fix promotion of WeakFloat64 and Int --- src/uparse.jl | 9 +++++---- test/unittests.jl | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/uparse.jl b/src/uparse.jl index e6f22854..618a8d3b 100644 --- a/src/uparse.jl +++ b/src/uparse.jl @@ -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() @@ -30,11 +30,12 @@ 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) +# Quantity(convert(DEFAULT_VALUE_TYPE, x), DEFAULT_DIM_TYPE) as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))") """ diff --git a/test/unittests.jl b/test/unittests.jl index 586887f5..3444f82d 100644 --- a/test/unittests.jl +++ b/test/unittests.jl @@ -379,20 +379,21 @@ end @test ustrip(z) ≈ 60 * 60 * 24 * 365.25 # Test type stability of extreme range of units - @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 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{Int64,DEFAULT_DIM_TYPE} + @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{Int64,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} @@ -651,7 +652,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 From dff6849722c0eea0e98b92c4455b56a72e50d034 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 16 Oct 2023 09:43:09 +0100 Subject: [PATCH 6/9] Use LazyFloat64 in SymbolicDimensions as well --- src/symbolic_dimensions.jl | 27 ++++++++++++++++----------- src/uparse.jl | 1 - 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/symbolic_dimensions.jl b/src/symbolic_dimensions.jl index 2a49a1cd..9c952752 100644 --- a/src/symbolic_dimensions.jl +++ b/src/symbolic_dimensions.jl @@ -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) @@ -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}) @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/src/uparse.jl b/src/uparse.jl index 618a8d3b..5ebd5b16 100644 --- a/src/uparse.jl +++ b/src/uparse.jl @@ -35,7 +35,6 @@ end as_quantity(q::Quantity) = convert(DEFAULT_UNIT_TYPE, q) as_quantity(x::Number) = DEFAULT_UNIT_TYPE(x) -# Quantity(convert(DEFAULT_VALUE_TYPE, x), DEFAULT_DIM_TYPE) as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))") """ From 51e46d65f0c4dd295f8801e767b19c48dfb67f23 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 16 Oct 2023 10:22:08 +0100 Subject: [PATCH 7/9] Rename `LazyFloat64` -> `AutoFloat` --- src/lazy_float.jl | 46 +++++++++++++++++++++++----------------------- src/units.jl | 4 ++-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/lazy_float.jl b/src/lazy_float.jl index f52114e0..caca8579 100644 --- a/src/lazy_float.jl +++ b/src/lazy_float.jl @@ -1,36 +1,36 @@ # This is used to store floats without forcing promotion on other # numeric types. -struct LazyFloat64 <: AbstractFloat +struct AutoFloat <: AbstractFloat value::Float64 - LazyFloat64(x::AbstractFloat) = new(convert(Float64, x)) + AutoFloat(x::AbstractFloat) = new(convert(Float64, x)) end -Base.float(x::LazyFloat64) = x.value +Base.float(x::AutoFloat) = 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<:AbstractFloat} = T -Base.promote_rule(::Type{LazyFloat64}, ::Type{T}) where {T} = promote_type(Float64, T) +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::LazyFloat64) = print(io, float(x)) +Base.show(io::IO, x::AutoFloat) = 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) +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 LazyFloat64(x::$T) = LazyFloat64(float(x)) + @eval AutoFloat(x::$T) = AutoFloat(float(x)) end diff --git a/src/units.jl b/src/units.jl index 041ee78a..da7b238e 100644 --- a/src/units.jl +++ b/src/units.jl @@ -4,9 +4,9 @@ import ..DEFAULT_DIM_TYPE import ..DEFAULT_VALUE_TYPE import ..Quantity import ..Quantity -import ..LazyFloat64 +import ..AutoFloat -const DEFAULT_UNIT_BASE_TYPE = LazyFloat64 +const DEFAULT_UNIT_BASE_TYPE = AutoFloat const DEFAULT_UNIT_TYPE = Quantity{DEFAULT_UNIT_BASE_TYPE,DEFAULT_DIM_TYPE} const _UNIT_SYMBOLS = Symbol[] From 8ec0891f602eb6532e4ccae31ce628c3ad0ecd30 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 16 Oct 2023 10:24:13 +0100 Subject: [PATCH 8/9] Add AutoFloat docstring --- src/lazy_float.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lazy_float.jl b/src/lazy_float.jl index caca8579..2ff17de5 100644 --- a/src/lazy_float.jl +++ b/src/lazy_float.jl @@ -1,5 +1,11 @@ -# This is used to store floats without forcing promotion on other -# numeric types. +""" + 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 From 3bc44636a87a4f1b403e8c6824c5c96f609a3dde Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 16 Oct 2023 10:35:51 +0100 Subject: [PATCH 9/9] More tests of AutoFloat --- src/lazy_float.jl | 3 ++- test/unittests.jl | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/lazy_float.jl b/src/lazy_float.jl index 2ff17de5..5e7a4a04 100644 --- a/src/lazy_float.jl +++ b/src/lazy_float.jl @@ -23,9 +23,10 @@ Base.promote_rule(::Type{AutoFloat}, ::Type{T}) where {T} = promote_type(Float64 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.:*(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))) diff --git a/test/unittests.jl b/test/unittests.jl index 3444f82d..c0d9bdf8 100644 --- a/test/unittests.jl +++ b/test/unittests.jl @@ -1,6 +1,7 @@ using DynamicQuantities using DynamicQuantities: FixedRational 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 @@ -8,6 +9,12 @@ 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}] @@ -400,6 +407,31 @@ end @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" @@ -431,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"