Skip to content

Commit 9ffa627

Browse files
authored
Merge branch 'main' into ag-example
2 parents 996ff5c + 7d229fd commit 9ffa627

10 files changed

+262
-67
lines changed

Project.toml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,45 @@
11
name = "DynamicQuantities"
22
uuid = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
33
authors = ["MilesCranmer <[email protected]> and contributors"]
4-
version = "0.7.0"
4+
version = "0.7.3"
55

66
[deps]
77
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
88
PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930"
9-
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
10-
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
119
Tricks = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775"
1210

1311
[weakdeps]
1412
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
13+
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
1514
ScientificTypes = "321657f4-b219-11e9-178b-2701a2544e81"
16-
ScientificTypesBase = "30f210dd-8aff-4c5f-94ba-8e64358c1161"
1715
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
1816

1917
[extensions]
2018
DynamicQuantitiesLinearAlgebraExt = "LinearAlgebra"
21-
DynamicQuantitiesScientificTypesExt = ["ScientificTypes", "ScientificTypesBase"]
19+
DynamicQuantitiesMeasurementsExt = "Measurements"
20+
DynamicQuantitiesScientificTypesExt = "ScientificTypes"
2221
DynamicQuantitiesUnitfulExt = "Unitful"
2322

2423
[compat]
25-
Compat = "^3.42, 4"
26-
PackageExtensionCompat = "1"
24+
Compat = "3.42, 4"
25+
Measurements = "2"
26+
PackageExtensionCompat = "1.0.2"
2727
ScientificTypes = "3"
28-
ScientificTypesBase = "3"
2928
Tricks = "0.1"
3029
Unitful = "1"
3130
julia = "1.6"
3231

3332
[extras]
3433
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
3534
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
35+
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
3636
Ratios = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
3737
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
3838
SaferIntegers = "88634af6-177f-5301-88b8-7819386cfa38"
3939
ScientificTypes = "321657f4-b219-11e9-178b-2701a2544e81"
40-
ScientificTypesBase = "30f210dd-8aff-4c5f-94ba-8e64358c1161"
4140
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
4241
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4342
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
4443

4544
[targets]
46-
test = ["Test", "Aqua", "LinearAlgebra", "Ratios", "SaferIntegers", "SafeTestsets", "ScientificTypes", "ScientificTypesBase", "StaticArrays", "Unitful"]
45+
test = ["Aqua", "LinearAlgebra", "Measurements", "Ratios", "SaferIntegers", "SafeTestsets", "ScientificTypes", "StaticArrays", "Test", "Unitful"]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module DynamicQuantitiesMeasurementsExt
2+
3+
using DynamicQuantities: AbstractQuantity, new_quantity, dimension, ustrip, DimensionError
4+
using Measurements: Measurements, measurement, value, uncertainty
5+
6+
function Measurements.measurement(a::Q, b::Q) where {Q<:AbstractQuantity}
7+
dimension(a) == dimension(b) || throw(DimensionError(a, b))
8+
raw_measurement = measurement(ustrip(a), ustrip(b))
9+
return new_quantity(Q, raw_measurement, dimension(a))
10+
end
11+
function Measurements.measurement(a::AbstractQuantity, b::AbstractQuantity)
12+
return measurement(promote(a, b)...)
13+
end
14+
15+
Measurements.value(q::Q) where {Q<:AbstractQuantity} = new_quantity(Q, value(ustrip(q)), dimension(q))
16+
Measurements.uncertainty(q::Q) where {Q<:AbstractQuantity} = new_quantity(Q, uncertainty(ustrip(q)), dimension(q))
17+
18+
end

ext/DynamicQuantitiesScientificTypesExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module DynamicQuantitiesScientificTypesExt
22

33
import DynamicQuantities: AbstractQuantity, ustrip
44
import ScientificTypes as ST
5-
import ScientificTypesBase as STB
5+
import ScientificTypes.ScientificTypesBase as STB
66

77
STB.scitype(x::AbstractQuantity, C::ST.DefaultConvention) = STB.scitype(ustrip(x), C)
88
STB.Scitype(::Type{<:AbstractQuantity{T}}, C::ST.DefaultConvention) where {T} = STB.Scitype(T, C)

src/DynamicQuantities.jl

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
module DynamicQuantities
22

3-
import TOML: parsefile
4-
5-
const PACKAGE_VERSION = try
6-
let project = parsefile(joinpath(pkgdir(@__MODULE__), "Project.toml"))
7-
VersionNumber(project["version"])
8-
end
9-
catch
10-
VersionNumber(0, 0, 0)
11-
end
12-
133
export Units, Constants
144
export AbstractQuantity, AbstractDimensions
155
export Quantity, Dimensions, SymbolicDimensions, QuantityArray, DimensionError

src/symbolic_dimensions.jl

Lines changed: 162 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import .Units: UNIT_SYMBOLS, UNIT_MAPPING, UNIT_VALUES
22
import .Constants: CONSTANT_SYMBOLS, CONSTANT_MAPPING, CONSTANT_VALUES
3-
import SparseArrays as SA
43

54
const SYMBOL_CONFLICTS = intersect(UNIT_SYMBOLS, CONSTANT_SYMBOLS)
65

6+
const INDEX_TYPE = UInt8
77
# Prefer units over constants:
88
# For example, this means we can't have a symbolic Planck's constant,
99
# as it is just "hours" (h), which is more common.
@@ -19,7 +19,7 @@ const ALL_VALUES = vcat(
1919
if k SYMBOL_CONFLICTS
2020
)...
2121
)
22-
const ALL_MAPPING = NamedTuple([s => i for (i, s) in enumerate(ALL_SYMBOLS)])
22+
const ALL_MAPPING = NamedTuple([s => INDEX_TYPE(i) for (i, s) in enumerate(ALL_SYMBOLS)])
2323

2424
"""
2525
SymbolicDimensions{R} <: AbstractDimensions{R}
@@ -35,49 +35,60 @@ to one which uses `Dimensions` as its dimensions (i.e., base SI units)
3535
`expand_units`.
3636
"""
3737
struct SymbolicDimensions{R} <: AbstractDimensions{R}
38-
_data::SA.SparseVector{R}
39-
40-
SymbolicDimensions(data::SA.SparseVector) = new{eltype(data)}(data)
41-
SymbolicDimensions{_R}(data::SA.SparseVector) where {_R} = new{_R}(data)
38+
nzdims::Vector{INDEX_TYPE}
39+
nzvals::Vector{R}
4240
end
4341

4442
static_fieldnames(::Type{<:SymbolicDimensions}) = ALL_SYMBOLS
45-
data(d::SymbolicDimensions) = getfield(d, :_data)
46-
Base.getproperty(d::SymbolicDimensions{R}, s::Symbol) where {R} = data(d)[ALL_MAPPING[s]]
47-
Base.getindex(d::SymbolicDimensions{R}, k::Symbol) where {R} = getproperty(d, k)
43+
function Base.getproperty(d::SymbolicDimensions{R}, s::Symbol) where {R}
44+
nzdims = getfield(d, :nzdims)
45+
i = get(ALL_MAPPING, s, INDEX_TYPE(0))
46+
iszero(i) && error("$s is not available as a symbol in SymbolicDimensions. Symbols available: $(ALL_SYMBOLS).")
47+
ii = searchsortedfirst(nzdims, i)
48+
if ii <= length(nzdims) && nzdims[ii] == i
49+
return getfield(d, :nzvals)[ii]
50+
else
51+
return zero(R)
52+
end
53+
end
54+
Base.propertynames(::SymbolicDimensions) = ALL_SYMBOLS
55+
Base.getindex(d::SymbolicDimensions, k::Symbol) = getproperty(d, k)
4856
constructor_of(::Type{<:SymbolicDimensions}) = SymbolicDimensions
4957

50-
SymbolicDimensions{R}(d::SymbolicDimensions) where {R} = SymbolicDimensions{R}(data(d))
51-
(::Type{D})(; kws...) where {D<:SymbolicDimensions} = D(DEFAULT_DIM_BASE_TYPE; kws...)
52-
(::Type{D})(::Type{R}; kws...) where {R,D<:SymbolicDimensions} =
53-
let constructor=constructor_of(D){R}
54-
length(kws) == 0 && return constructor(SA.spzeros(R, length(ALL_SYMBOLS)))
55-
I = [ALL_MAPPING[s] for s in keys(kws)]
56-
V = [tryrationalize(R, v) for v in values(kws)]
57-
data = SA.sparsevec(I, V, length(ALL_SYMBOLS))
58-
return constructor(data)
58+
SymbolicDimensions{R}(d::SymbolicDimensions) where {R} = SymbolicDimensions{R}(getfield(d, :nzdims), convert(Vector{R}, getfield(d, :nzvals)))
59+
SymbolicDimensions(; kws...) = SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}(; kws...)
60+
function SymbolicDimensions{R}(; kws...) where {R}
61+
if isempty(kws)
62+
return SymbolicDimensions{R}(Vector{INDEX_TYPE}(undef, 0), Vector{R}(undef, 0))
5963
end
64+
I = INDEX_TYPE[ALL_MAPPING[s] for s in keys(kws)]
65+
p = sortperm(I)
66+
V = R[tryrationalize(R, kws[i]) for i in p]
67+
return SymbolicDimensions{R}(permute!(I, p), V)
68+
end
69+
(::Type{<:SymbolicDimensions})(::Type{R}; kws...) where {R} = SymbolicDimensions{R}(; kws...)
6070

61-
function Base.convert(::Type{Qout}, q::Quantity{<:Any,<:Dimensions}) where {T,D<:SymbolicDimensions,Qout<:Quantity{T,D}}
62-
output = Qout(
63-
convert(T, ustrip(q)),
64-
D;
65-
m=ulength(q),
66-
kg=umass(q),
67-
s=utime(q),
68-
A=ucurrent(q),
69-
K=utemperature(q),
70-
cd=uluminosity(q),
71-
mol=uamount(q),
72-
)
73-
SA.dropzeros!(data(dimension(output)))
74-
return output
71+
function Base.convert(::Type{Quantity{T,SymbolicDimensions}}, q::Quantity{<:Any,<:Dimensions}) where {T}
72+
return convert(Quantity{T,SymbolicDimensions{DEFAULT_DIM_BASE_TYPE}}, q)
73+
end
74+
function Base.convert(::Type{Quantity{T,SymbolicDimensions{R}}}, q::Quantity{<:Any,<:Dimensions}) where {T,R}
75+
syms = (:m, :kg, :s, :A, :K, :cd, :mol)
76+
vals = (ulength(q), umass(q), utime(q), ucurrent(q), utemperature(q), uluminosity(q), uamount(q))
77+
I = INDEX_TYPE[ALL_MAPPING[s] for (s, v) in zip(syms, vals) if !iszero(v)]
78+
V = R[tryrationalize(R, v) for v in vals if !iszero(v)]
79+
p = sortperm(I)
80+
permute!(I, p)
81+
permute!(V, p)
82+
dims = SymbolicDimensions{R}(I, V)
83+
return Quantity(convert(T, ustrip(q)), dims)
7584
end
76-
function Base.convert(::Type{Q}, q::Quantity{<:Any,<:SymbolicDimensions}) where {T,D<:Dimensions,Q<:Quantity{T,D}}
77-
result = one(Q) * ustrip(q)
85+
function Base.convert(::Type{Quantity{T,D}}, q::Quantity{<:Any,<:SymbolicDimensions}) where {T,D<:Dimensions}
86+
result = Quantity(T(ustrip(q)), D())
7887
d = dimension(q)
79-
for (idx, value) in zip(SA.findnz(data(d))...)
80-
result = result * convert(Q, ALL_VALUES[idx]) ^ value
88+
for (idx, value) in zip(getfield(d, :nzdims), getfield(d, :nzvals))
89+
if !iszero(value)
90+
result = result * convert(Quantity{T,D}, ALL_VALUES[idx]) ^ value
91+
end
8192
end
8293
return result
8394
end
@@ -120,15 +131,122 @@ For string input, `qout` is created by parsing `ustr` as a symbolic unit, i.e. `
120131
as_u(qout::AbstractQuantity{<:Any, <:SymbolicDimensions}) = Base.Fix2(as_u, qout)
121132
as_u(ustr::String) = Base.Fix2(as_u, ustr)
122133

123-
Base.copy(d::SymbolicDimensions) = SymbolicDimensions(copy(data(d)))
124-
Base.:(==)(l::SymbolicDimensions, r::SymbolicDimensions) = data(l) == data(r)
125-
Base.iszero(d::SymbolicDimensions) = iszero(data(d))
126-
Base.:*(l::SymbolicDimensions, r::SymbolicDimensions) = SymbolicDimensions(data(l) + data(r))
127-
Base.:/(l::SymbolicDimensions, r::SymbolicDimensions) = SymbolicDimensions(data(l) - data(r))
128-
Base.inv(d::SymbolicDimensions) = SymbolicDimensions(-data(d))
129-
Base.:^(l::SymbolicDimensions{R}, r::Integer) where {R} = SymbolicDimensions(data(l) * r)
130-
Base.:^(l::SymbolicDimensions{R}, r::Number) where {R} = SymbolicDimensions(data(l) * tryrationalize(R, r))
134+
Base.copy(d::SymbolicDimensions) = SymbolicDimensions(copy(getfield(d, :nzdims)), copy(getfield(d, :nzvals)))
135+
function Base.:(==)(l::SymbolicDimensions, r::SymbolicDimensions)
136+
nzdims_l = getfield(l, :nzdims)
137+
nzvals_l = getfield(l, :nzvals)
138+
nzdims_r = getfield(r, :nzdims)
139+
nzvals_r = getfield(r, :nzvals)
140+
nl = length(nzdims_l)
141+
nr = length(nzdims_r)
142+
il = ir = 1
143+
while il <= nl && ir <= nr
144+
dim_l = nzdims_l[il]
145+
dim_r = nzdims_r[ir]
146+
if dim_l == dim_r
147+
if nzvals_l[il] != nzvals_r[ir]
148+
return false
149+
end
150+
il += 1
151+
ir += 1
152+
elseif dim_l < dim_r
153+
if !iszero(nzvals_l[il])
154+
return false
155+
end
156+
il += 1
157+
else
158+
if !iszero(nzvals_r[ir])
159+
return false
160+
end
161+
ir += 1
162+
end
163+
end
164+
165+
while il <= nl
166+
if !iszero(nzvals_l[il])
167+
return false
168+
end
169+
il += 1
170+
end
171+
172+
while ir <= nr
173+
if !iszero(nzvals_r[ir])
174+
return false
175+
end
176+
ir += 1
177+
end
178+
179+
return true
180+
end
181+
Base.iszero(d::SymbolicDimensions) = iszero(getfield(d, :nzvals))
131182

183+
# Defines `inv(::SymbolicDimensions)` and `^(::SymbolicDimensions, ::Number)`
184+
function map_dimensions(op::Function, d::SymbolicDimensions)
185+
return SymbolicDimensions(copy(getfield(d, :nzdims)), map(op, getfield(d, :nzvals)))
186+
end
187+
188+
# Defines `*(::SymbolicDimensions, ::SymbolicDimensions)` and `/(::SymbolicDimensions, ::SymbolicDimensions)`
189+
function map_dimensions(op::O, l::SymbolicDimensions{L}, r::SymbolicDimensions{R}) where {O<:Function,L,R}
190+
zero_L = zero(L)
191+
zero_R = zero(R)
192+
T = typeof(op(zero(L), zero(R)))
193+
I = Vector{INDEX_TYPE}(undef, 0)
194+
V = Vector{T}(undef, 0)
195+
nzdims_l = getfield(l, :nzdims)
196+
nzvals_l = getfield(l, :nzvals)
197+
nzdims_r = getfield(r, :nzdims)
198+
nzvals_r = getfield(r, :nzvals)
199+
nl = length(nzdims_l)
200+
nr = length(nzdims_r)
201+
il = ir = 1
202+
while il <= nl && ir <= nr
203+
dim_l = nzdims_l[il]
204+
dim_r = nzdims_r[ir]
205+
if dim_l == dim_r
206+
s = op(nzvals_l[il], nzvals_r[ir])
207+
if !iszero(s)
208+
push!(I, dim_l)
209+
push!(V, s)
210+
end
211+
il += 1
212+
ir += 1
213+
elseif dim_l < dim_r
214+
s = op(nzvals_l[il], zero_R)
215+
if !iszero(s)
216+
push!(I, dim_l)
217+
push!(V, s)
218+
end
219+
il += 1
220+
else
221+
s = op(zero_L, nzvals_r[ir])
222+
if !iszero(s)
223+
push!(I, dim_r)
224+
push!(V, s)
225+
end
226+
ir += 1
227+
end
228+
end
229+
230+
while il <= nl
231+
s = op(nzvals_l[il], zero_R)
232+
if !iszero(s)
233+
push!(I, nzdims_l[il])
234+
push!(V, s)
235+
end
236+
il += 1
237+
end
238+
239+
while ir <= nr
240+
s = op(zero_L, nzvals_r[ir])
241+
if !iszero(s)
242+
push!(I, nzdims_r[ir])
243+
push!(V, s)
244+
end
245+
ir += 1
246+
end
247+
248+
return SymbolicDimensions(I, V)
249+
end
132250

133251
"""
134252
SymbolicUnitsParse

src/utils.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ tryrationalize(::Type{R}, x) where {R} = isinteger(x) ? convert(R, round(Int, x)
176176

177177
Base.showerror(io::IO, e::DimensionError) = print(io, "DimensionError: ", e.q1, " and ", e.q2, " have incompatible dimensions")
178178

179+
# TODO: these are redundant with the constructors
179180
Base.convert(::Type{Q}, q::AbstractQuantity) where {Q<:AbstractQuantity} = q
180181
Base.convert(::Type{Q}, q::AbstractQuantity) where {T,Q<:AbstractQuantity{T}} = new_quantity(Q, convert(T, ustrip(q)), dimension(q))
181182
Base.convert(::Type{Q}, q::AbstractQuantity) where {T,D,Q<:AbstractQuantity{T,D}} = new_quantity(Q, convert(T, ustrip(q)), convert(D, dimension(q)))

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ else
1616
@safetestset "ScientificTypes.jl integration tests" begin
1717
include("test_scitypes.jl")
1818
end
19+
@safetestset "Measurements.jl integration tests" begin
20+
include("test_measurements.jl")
21+
end
1922
@safetestset "Unit tests" begin
2023
include("unittests.jl")
2124
end

test/test_measurements.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using DynamicQuantities
2+
using Measurements
3+
using Measurements: value, uncertainty
4+
5+
x = 1.0u"m/s" ± 0.1u"m/s"
6+
7+
@test ustrip(x^2) == ustrip(x)^2
8+
@test value(x) == 1.0u"m/s"
9+
@test uncertainty(x) == 0.1u"m/s"
10+
@test dimension(x)^2 == dimension(x^2)
11+
@test_throws DimensionError 0.5u"m" ± 0.1u"s"
12+
13+
# Mixed types:
14+
y = Quantity{Float16}(0.1u"m/s") ± Quantity{Float32}(0.1u"m/s")
15+
@test typeof(y) <: Quantity{Measurement{Float32}}

test/test_scitypes.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
using DynamicQuantities
22
using ScientificTypes
33
import ScientificTypes as ST
4-
import ScientificTypesBase as STB
54

65
x = 1.0u"m/s"
76

87
@test scitype(x) <: Continuous
98
@test scitype([x]) <: AbstractVector{<:Continuous}
109
@test scitype(Quantity{Int}(x)) <: Count
1110
@test scitype(randn(32) .* u"m/s") <: AbstractVector{<:Continuous}
12-
@test STB.Scitype(typeof(x), ST.DefaultConvention()) <: Continuous
11+
@test ST.ScientificTypesBase.Scitype(typeof(x), ST.DefaultConvention()) <: Continuous
1312

1413
X = (; x=randn(32) .* u"m/s")
1514

0 commit comments

Comments
 (0)