Skip to content

Commit fb19037

Browse files
authored
Merge pull request #14 from oscardssmith/patch-3
Give `Dimensions` a type parameter.
2 parents e345d27 + 4f6c175 commit fb19037

11 files changed

+193
-183
lines changed

Project.toml

+3-5
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ authors = ["MilesCranmer <[email protected]> and contributors"]
44
version = "0.1.0"
55

66
[deps]
7-
Ratios = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
87
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
9-
SaferIntegers = "88634af6-177f-5301-88b8-7819386cfa38"
108

119
[weakdeps]
1210
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
@@ -15,16 +13,16 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
1513
DynamicQuantitiesUnitfulExt = "Unitful"
1614

1715
[compat]
18-
Ratios = "0.4"
1916
Requires = "1"
20-
SaferIntegers = "3"
2117
Unitful = "1"
2218
julia = "1.6"
2319

2420
[extras]
21+
Ratios = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
22+
SaferIntegers = "88634af6-177f-5301-88b8-7819386cfa38"
2523
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
2624
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2725
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
2826

2927
[targets]
30-
test = ["Test", "SafeTestsets", "Unitful"]
28+
test = ["Test", "Ratios", "SaferIntegers", "SafeTestsets", "Unitful"]

benchmark/benchmarks.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ end
3434
SUITE["with_quantity"] = let s = BenchmarkGroup()
3535
f5(x, y) = x / y
3636
s["/y"] = @benchmarkable $f5(x, y) setup = (x = default(); y = default()) evals = 1000
37-
f6(x, y) = x^y
38-
s["^y"] = @benchmarkable $f6(x, y) setup = (x = default(); y = default() / dimension(default())) evals = 1000
37+
f6(x, y) = x + y
38+
s["+y"] = @benchmarkable $f6(x, y) setup = (x = default(); y = x + rand() * x) evals = 1000
3939
s
4040
end

ext/DynamicQuantitiesUnitfulExt.jl

+8-6
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,22 @@ Base.convert(::Type{Unitful.Quantity}, x::DynamicQuantities.Quantity) =
2929
cumulator
3030
end
3131

32-
Base.convert(::Type{DynamicQuantities.Quantity}, x::Unitful.Quantity) =
32+
Base.convert(::Type{DynamicQuantities.Quantity}, x::Unitful.Quantity{T}) where {T} = convert(DynamicQuantities.Quantity{T,DynamicQuantities.DEFAULT_DIM_TYPE}, x)
33+
Base.convert(::Type{DynamicQuantities.Quantity{T,R}}, x::Unitful.Quantity) where {T,R} =
3334
let
3435
value = Unitful.ustrip(Unitful.upreferred(x))
35-
dimension = convert(DynamicQuantities.Dimensions, Unitful.dimension(x))
36-
return DynamicQuantities.Quantity(value, dimension)
36+
dimension = convert(DynamicQuantities.Dimensions{R}, Unitful.dimension(x))
37+
return DynamicQuantities.Quantity(convert(T, value), dimension)
3738
end
3839

39-
Base.convert(::Type{DynamicQuantities.Dimensions}, d::Unitful.Dimensions{D}) where {D} =
40+
Base.convert(::Type{DynamicQuantities.Dimensions}, d::Unitful.Dimensions) = convert(DynamicQuantities.Dimensions{DynamicQuantities.DEFAULT_DIM_TYPE}, d)
41+
Base.convert(::Type{DynamicQuantities.Dimensions{R}}, d::Unitful.Dimensions{D}) where {R,D} =
4042
let
41-
cumulator = DynamicQuantities.Dimensions()
43+
cumulator = DynamicQuantities.Dimensions{R}()
4244
for dim in D
4345
dim_symbol = _map_dim_name_to_dynamic_units(typeof(dim))
4446
dim_power = dim.power
45-
cumulator *= DynamicQuantities.Dimensions(; dim_symbol => dim_power)
47+
cumulator *= DynamicQuantities.Dimensions(R; dim_symbol => dim_power)
4648
end
4749
cumulator
4850
end

src/DynamicQuantities.jl

-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ module DynamicQuantities
33
export Quantity, Dimensions, ustrip, dimension, valid
44
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
55

6-
import Ratios: SimpleRatio
7-
86
include("types.jl")
97
include("utils.jl")
108
include("math.jl")

src/math.jl

+6-12
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,17 @@ Base.:/(l::Number, r::Dimensions) = Quantity(l, inv(r), true)
1919
Base.:+(l::Quantity, r::Quantity) = Quantity(l.value + r.value, l.dimensions, l.valid && r.valid && l.dimensions == r.dimensions)
2020
Base.:-(l::Quantity, r::Quantity) = Quantity(l.value - r.value, l.dimensions, l.valid && r.valid && l.dimensions == r.dimensions)
2121

22-
Base.:^(l::Quantity, r::Quantity) =
23-
let rr = tryrationalize(R, r.value)
24-
Quantity(l.value^rr, l.dimensions^rr, l.valid && r.valid && iszero(r.dimensions))
25-
end
26-
Base.:^(l::Dimensions, r::R) = @map_dimensions(Base.Fix1(*, r), l)
27-
Base.:^(l::Dimensions, r::Number) = l^tryrationalize(R, r)
28-
Base.:^(l::Quantity, r::Number) =
29-
let rr = tryrationalize(R, r)
30-
Quantity(l.value^rr, l.dimensions^rr, l.valid)
31-
end
22+
_pow(l::Dimensions{R}, r::R) where {R} = @map_dimensions(Base.Fix1(*, r), l)
23+
_pow(l::Quantity{T,R}, r::R) where {T,R} = Quantity(l.value^convert(T, r), _pow(l.dimensions, r), l.valid)
24+
Base.:^(l::Dimensions{R}, r::Number) where {R} = _pow(l, tryrationalize(R, r))
25+
Base.:^(l::Quantity{T,R}, r::Number) where {T,R} = _pow(l, tryrationalize(R, r))
3226

3327
Base.inv(d::Dimensions) = @map_dimensions(-, d)
3428
Base.inv(q::Quantity) = Quantity(inv(q.value), inv(q.dimensions), q.valid)
3529

36-
Base.sqrt(d::Dimensions) = d^(1 // 2)
30+
Base.sqrt(d::Dimensions{R}) where {R} = d^inv(convert(R, 2))
3731
Base.sqrt(q::Quantity) = Quantity(sqrt(q.value), sqrt(q.dimensions), q.valid)
38-
Base.cbrt(d::Dimensions) = d^(1 // 3)
32+
Base.cbrt(d::Dimensions{R}) where {R} = d^inv(convert(R, 3))
3933
Base.cbrt(q::Quantity) = Quantity(cbrt(q.value), cbrt(q.dimensions), q.valid)
4034

4135
Base.abs(q::Quantity) = Quantity(abs(q.value), q.dimensions, q.valid)

src/types.jl

+41-33
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
import Ratios: SimpleRatio
2-
import SaferIntegers: SafeInt
3-
4-
const INT_TYPE = SafeInt
5-
const R = SimpleRatio{INT_TYPE}
6-
const ZERO = R(0)
7-
const DIMENSION_NAMES = (:length, :mass, :time, :current, :temperature, :luminosity, :amount)
8-
const DIMENSION_SYNONYMS = (:𝐋, :𝐌, :𝐓, :𝐈, :𝚯, :𝐉, :𝐍)
9-
const SYNONYM_MAPPING = NamedTuple(DIMENSION_NAMES .=> DIMENSION_SYNONYMS)
1+
const DEFAULT_DIM_TYPE = Rational{Int16}
102

113
"""
124
Dimensions
@@ -17,15 +9,15 @@ example, the dimensions of velocity are `Dimensions(length=1, time=-1)`.
179
1810
# Fields
1911
20-
- `length::Rational{Int}`: length dimension (i.e., meters^(length))
21-
- `mass::Rational{Int}`: mass dimension (i.e., kg^(mass))
22-
- `time::Rational{Int}`: time dimension (i.e., s^(time))
23-
- `current::Rational{Int}`: current dimension (i.e., A^(current))
24-
- `temperature::Rational{Int}`: temperature dimension (i.e., K^(temperature))
25-
- `luminosity::Rational{Int}`: luminosity dimension (i.e., cd^(luminosity))
26-
- `amount::Rational{Int}`: amount dimension (i.e., mol^(amount))
12+
- `length`: length dimension (i.e., meters^(length))
13+
- `mass`: mass dimension (i.e., kg^(mass))
14+
- `time`: time dimension (i.e., s^(time))
15+
- `current`: current dimension (i.e., A^(current))
16+
- `temperature`: temperature dimension (i.e., K^(temperature))
17+
- `luminosity`: luminosity dimension (i.e., cd^(luminosity))
18+
- `amount`: amount dimension (i.e., mol^(amount))
2719
"""
28-
struct Dimensions
20+
struct Dimensions{R <: Real}
2921
length::R
3022
mass::R
3123
time::R
@@ -34,19 +26,33 @@ struct Dimensions
3426
luminosity::R
3527
amount::R
3628

37-
Dimensions(length::R, mass::R, time::R, current::R, temperature::R, luminosity::R, amount::R) =
38-
new(length, mass, time, current, temperature, luminosity, amount)
39-
Dimensions(; kws...) = Dimensions(
40-
tryrationalize(R, get(kws, :length, ZERO)),
41-
tryrationalize(R, get(kws, :mass, ZERO)),
42-
tryrationalize(R, get(kws, :time, ZERO)),
43-
tryrationalize(R, get(kws, :current, ZERO)),
44-
tryrationalize(R, get(kws, :temperature, ZERO)),
45-
tryrationalize(R, get(kws, :luminosity, ZERO)),
46-
tryrationalize(R, get(kws, :amount, ZERO)),
29+
function Dimensions(length::_R,
30+
mass::_R,
31+
time::_R,
32+
current::_R,
33+
temperature::_R,
34+
luminosity::_R,
35+
amount::_R) where {_R<:Real}
36+
new{_R}(length, mass, time, current, temperature, luminosity, amount)
37+
end
38+
Dimensions(; kws...) = Dimensions(DEFAULT_DIM_TYPE; kws...)
39+
Dimensions(::Type{_R}; kws...) where {_R} = Dimensions(
40+
tryrationalize(_R, get(kws, :length, zero(_R))),
41+
tryrationalize(_R, get(kws, :mass, zero(_R))),
42+
tryrationalize(_R, get(kws, :time, zero(_R))),
43+
tryrationalize(_R, get(kws, :current, zero(_R))),
44+
tryrationalize(_R, get(kws, :temperature, zero(_R))),
45+
tryrationalize(_R, get(kws, :luminosity, zero(_R))),
46+
tryrationalize(_R, get(kws, :amount, zero(_R))),
4747
)
48+
Dimensions{_R}(; kws...) where {_R} = Dimensions(_R; kws...)
49+
Dimensions{_R}(args...) where {_R} = Dimensions(Base.Fix1(convert, _R).(args)...)
4850
end
4951

52+
const DIMENSION_NAMES = Base.fieldnames(Dimensions)
53+
const DIMENSION_SYNONYMS = (:𝐋, :𝐌, :𝐓, :𝐈, :𝚯, :𝐉, :𝐍)
54+
const SYNONYM_MAPPING = NamedTuple(DIMENSION_NAMES .=> DIMENSION_SYNONYMS)
55+
5056
"""
5157
Quantity{T}
5258
@@ -67,13 +73,15 @@ including `*`, `+`, `-`, `/`, `^`, `sqrt`, and `cbrt`.
6773
- `dimensions::Dimensions`: dimensions of the quantity
6874
- `valid::Bool`: whether the quantity is valid or not
6975
"""
70-
struct Quantity{T}
76+
struct Quantity{T, R}
7177
value::T
72-
dimensions::Dimensions
78+
dimensions::Dimensions{R}
7379
valid::Bool
7480

75-
Quantity(x; kws...) = new{typeof(x)}(x, Dimensions(; kws...), true)
76-
Quantity(x, valid::Bool; kws...) = new{typeof(x)}(x, Dimensions(; kws...), valid)
77-
Quantity(x, d::Dimensions) = new{typeof(x)}(x, d, true)
78-
Quantity(x, d::Dimensions, valid::Bool) = new{typeof(x)}(x, d, valid)
81+
Quantity(x; kws...) = new{typeof(x), DEFAULT_DIM_TYPE}(x, Dimensions(; kws...), true)
82+
Quantity(x, valid::Bool; kws...) = new{typeof(x), DEFAULT_DIM_TYPE}(x, Dimensions(; kws...), valid)
83+
Quantity(x, ::Type{_R}, valid::Bool; kws...) where {_R} = new{typeof(x), _R}(x, Dimensions(_R; kws...), valid)
84+
Quantity(x, ::Type{_R}; kws...) where {_R} = new{typeof(x), _R}(x, Dimensions(_R; kws...), true)
85+
Quantity(x, d::Dimensions{_R}) where {_R} = new{typeof(x), _R}(x, d, true)
86+
Quantity(x, d::Dimensions{_R}, valid::Bool) where {_R} = new{typeof(x), _R}(x, d, valid)
7987
end

src/utils.jl

+11-15
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,21 @@ Base.iszero(q::Quantity) = iszero(q.value)
3838
Base.getindex(d::Dimensions, k::Symbol) = getfield(d, k)
3939
Base.:(==)(l::Dimensions, r::Dimensions) = @all_dimensions(==, l, r)
4040
Base.:(==)(l::Quantity, r::Quantity) = l.value == r.value && l.dimensions == r.dimensions && l.valid == r.valid
41-
Base.:()(l::Quantity, r::Quantity) = l.value r.value && l.dimensions == r.dimensions && l.valid == r.valid
41+
Base.isapprox(l::Quantity, r::Quantity; kws...) = isapprox(l.value, r.value; kws...) && l.dimensions == r.dimensions && l.valid == r.valid
4242
Base.length(::Dimensions) = 1
4343
Base.length(::Quantity) = 1
4444
Base.iterate(d::Dimensions) = (d, nothing)
4545
Base.iterate(::Dimensions, ::Nothing) = nothing
4646
Base.iterate(q::Quantity) = (q, nothing)
4747
Base.iterate(::Quantity, ::Nothing) = nothing
48+
Base.zero(::Type{Quantity{T,R}}) where {T,R} = Quantity(zero(T), R)
49+
Base.one(::Type{Quantity{T,R}}) where {T,R} = Quantity(one(T), R)
4850
Base.zero(::Type{Quantity{T}}) where {T} = Quantity(zero(T))
4951
Base.one(::Type{Quantity{T}}) where {T} = Quantity(one(T))
52+
Base.zero(::Type{Quantity}) = Quantity(zero(DEFAULT_DIM_TYPE))
53+
Base.one(::Type{Quantity}) = Quantity(one(DEFAULT_DIM_TYPE))
5054
Base.one(::Type{Dimensions}) = Dimensions()
55+
Base.one(::Type{Dimensions{R}}) where {R} = Dimensions{R}()
5156

5257
Base.show(io::IO, d::Dimensions) =
5358
let tmp_io = IOBuffer()
@@ -65,12 +70,8 @@ Base.show(io::IO, d::Dimensions) =
6570
end
6671
Base.show(io::IO, q::Quantity) = q.valid ? print(io, q.value, " ", q.dimensions) : print(io, "INVALID")
6772

68-
string_rational(x::Rational) = isinteger(x) ? string(x.num) : string(x)
69-
string_rational(x::SimpleRatio) = string_rational(x.num // x.den)
70-
pretty_print_exponent(io::IO, x::R) =
71-
let
72-
print(io, " ", to_superscript(string_rational(x)))
73-
end
73+
string_rational(x) = isinteger(x) ? string(round(Int, x)) : string(x)
74+
pretty_print_exponent(io::IO, x) = print(io, " ", to_superscript(string_rational(x)))
7475
const SUPERSCRIPT_MAPPING = ['', '¹', '²', '³', '', '', '', '', '', '']
7576
const INTCHARS = ['0' + i for i = 0:9]
7677
to_superscript(s::AbstractString) = join(
@@ -79,14 +80,9 @@ to_superscript(s::AbstractString) = join(
7980
end
8081
)
8182

82-
tryrationalize(::Type{RI}, x::RI) where {RI} = x
83-
tryrationalize(::Type{RI}, x::Rational) where {RI} = RI(x)
84-
tryrationalize(::Type{RI}, x::Integer) where {RI} = RI(x)
85-
tryrationalize(::Type{RI}, x) where {RI} = simple_ratio_rationalize(RI, x)
86-
simple_ratio_rationalize(::Type{RI}, x) where {RI} =
87-
let int_type = RI.parameters[1]
88-
isinteger(x) ? RI(round(int_type, x)) : RI(rationalize(int_type, x))
89-
end
83+
tryrationalize(::Type{R}, x::R) where {R} = x
84+
tryrationalize(::Type{R}, x::Union{Rational,Integer}) where {R} = convert(R, x)
85+
tryrationalize(::Type{R}, x) where {R} = isinteger(x) ? convert(R, round(Int, x)) : convert(R, rationalize(Int, x))
9086

9187
"""
9288
ustrip(q::Quantity)

test/runtests.jl

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
using SafeTestsets
2+
import Ratios: SimpleRatio
23

3-
@safetestset "Unit tests" begin
4-
include("unittests.jl")
4+
@static if !hasmethod(round, Tuple{Int, SimpleRatio{Int}})
5+
@eval Base.round(T, x::SimpleRatio) = round(T, x.num // x.den)
56
end
67

78
@safetestset "Unitful.jl integration tests" begin
8-
include("unitful.jl")
9+
include("test_unitful.jl")
10+
end
11+
12+
@safetestset "Unit tests" begin
13+
include("unittests.jl")
914
end

test/test_unitful.jl

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import DynamicQuantities
2+
import Unitful
3+
import Unitful: @u_str
4+
import Ratios: SimpleRatio
5+
import SaferIntegers: SafeInt16
6+
using Test
7+
8+
# Try to work with different preferred units:
9+
Unitful.preferunits(u"km")
10+
11+
risapprox(x::Unitful.Quantity, y::Unitful.Quantity; kws...) =
12+
let (xfloat, yfloat) = (Unitful.ustrip Unitful.upreferred).((x, y))
13+
return isapprox(xfloat, yfloat; kws...)
14+
end
15+
16+
factor_for_preferred_units = 1e-3
17+
18+
for T in [Float16, Float32, Float64], R in [Rational{Int16}, Rational{Int32}, SimpleRatio{Int}, SimpleRatio{SafeInt16}]
19+
x = DynamicQuantities.Quantity(T(0.2*factor_for_preferred_units), R, length=1, amount=2, current=-1 // 2, luminosity=2 // 5)
20+
x_unitful = T(0.2)u"m*mol^2*A^(-1//2)*cd^(2//5)"
21+
22+
@test risapprox(convert(Unitful.Quantity, x), x_unitful; atol=1e-6)
23+
@test typeof(convert(DynamicQuantities.Quantity, convert(Unitful.Quantity, x))) <: DynamicQuantities.Quantity{T,DynamicQuantities.DEFAULT_DIM_TYPE}
24+
@test isapprox(convert(DynamicQuantities.Quantity, convert(Unitful.Quantity, x)), x; atol=1e-6)
25+
26+
@test isapprox(convert(DynamicQuantities.Quantity{T,R}, x_unitful), x; atol=1e-6)
27+
@test risapprox(convert(Unitful.Quantity, convert(DynamicQuantities.Quantity{T,R}, x_unitful)), Unitful.upreferred(x_unitful); atol=1e-6)
28+
end

test/unitful.jl

-10
This file was deleted.

0 commit comments

Comments
 (0)