Skip to content

Commit c08be49

Browse files
committed
feat: allow registering units externally
- By interpolating `Units.UNIT_SYMBOLS` while registering units, new units can be registered even from outside the `Units` module - `_register_function` now accepts `lazy` which defaults to `false`. It controls updating the `UNIT_MAPPING`. - `@_lazy_register_units` is for internally registering units lazily. The `UNIT_MAPPING` is updated at once at the end.
1 parent 756836e commit c08be49

File tree

2 files changed

+74
-43
lines changed

2 files changed

+74
-43
lines changed

src/units.jl

+49-40
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,35 @@ import ..DEFAULT_QUANTITY_TYPE
66

77
@assert DEFAULT_VALUE_TYPE == Float64 "`units.jl` must be updated to support a different default value type."
88

9-
const _UNIT_SYMBOLS = Symbol[]
10-
const _UNIT_VALUES = DEFAULT_QUANTITY_TYPE[]
9+
const UNIT_SYMBOLS = Symbol[]
10+
const UNIT_VALUES = DEFAULT_QUANTITY_TYPE[]
1111

1212
macro register_unit(name, value)
1313
return esc(_register_unit(name, value))
1414
end
1515

16+
macro _lazy_register_unit(name, value)
17+
return esc(_register_unit(name, value; lazy = true))
18+
end
19+
1620
macro add_prefixes(base_unit, prefixes)
1721
@assert prefixes.head == :tuple
1822
return esc(_add_prefixes(base_unit, prefixes.args, _register_unit))
1923
end
2024

21-
function _register_unit(name::Symbol, value)
22-
s = string(name)
25+
const UNIT_MAPPING = Dict{Symbol,Int}()
26+
function update_unit_mapping(name, value, unit_mapping::Dict{Symbol, Int} = UNIT_MAPPING)
27+
unit_mapping[name] = length(unit_mapping) + 1
28+
end
29+
30+
function _register_unit(name::Symbol, value; lazy = false)
31+
name_symbol = Meta.quot(name)
2332
return quote
33+
haskey($UNIT_MAPPING, $name_symbol) && throw("Unit $name_symbol already exists.")
2434
const $name = $value
25-
push!(_UNIT_SYMBOLS, Symbol($s))
26-
push!(_UNIT_VALUES, $name)
35+
push!($UNIT_SYMBOLS, $name_symbol)
36+
push!($UNIT_VALUES, $name)
37+
!$lazy && $update_unit_mapping($name_symbol, $value)
2738
end
2839
end
2940

@@ -42,13 +53,13 @@ function _add_prefixes(base_unit::Symbol, prefixes, register_function)
4253
end
4354

4455
# SI base units
45-
@register_unit m DEFAULT_QUANTITY_TYPE(1.0, length=1)
46-
@register_unit g DEFAULT_QUANTITY_TYPE(1e-3, mass=1)
47-
@register_unit s DEFAULT_QUANTITY_TYPE(1.0, time=1)
48-
@register_unit A DEFAULT_QUANTITY_TYPE(1.0, current=1)
49-
@register_unit K DEFAULT_QUANTITY_TYPE(1.0, temperature=1)
50-
@register_unit cd DEFAULT_QUANTITY_TYPE(1.0, luminosity=1)
51-
@register_unit mol DEFAULT_QUANTITY_TYPE(1.0, amount=1)
56+
@_lazy_register_unit m DEFAULT_QUANTITY_TYPE(1.0, length = 1)
57+
@_lazy_register_unit g DEFAULT_QUANTITY_TYPE(1e-3, mass = 1)
58+
@_lazy_register_unit s DEFAULT_QUANTITY_TYPE(1.0, time = 1)
59+
@_lazy_register_unit A DEFAULT_QUANTITY_TYPE(1.0, current = 1)
60+
@_lazy_register_unit K DEFAULT_QUANTITY_TYPE(1.0, temperature = 1)
61+
@_lazy_register_unit cd DEFAULT_QUANTITY_TYPE(1.0, luminosity = 1)
62+
@_lazy_register_unit mol DEFAULT_QUANTITY_TYPE(1.0, amount = 1)
5263

5364
@add_prefixes m (f, p, n, μ, u, c, d, m, k, M, G)
5465
@add_prefixes g (p, n, μ, u, m, k)
@@ -88,17 +99,17 @@ end
8899
)
89100

90101
# SI derived units
91-
@register_unit Hz inv(s)
92-
@register_unit N kg * m / s^2
93-
@register_unit Pa N / m^2
94-
@register_unit J N * m
95-
@register_unit W J / s
96-
@register_unit C A * s
97-
@register_unit V W / A
98-
@register_unit F C / V
99-
@register_unit Ω V / A
100-
@register_unit ohm Ω
101-
@register_unit T N / (A * m)
102+
@_lazy_register_unit Hz inv(s)
103+
@_lazy_register_unit N kg * m / s^2
104+
@_lazy_register_unit Pa N / m^2
105+
@_lazy_register_unit J N * m
106+
@_lazy_register_unit W J / s
107+
@_lazy_register_unit C A * s
108+
@_lazy_register_unit V W / A
109+
@_lazy_register_unit F C / V
110+
@_lazy_register_unit Ω V / A
111+
@_lazy_register_unit ohm Ω
112+
@_lazy_register_unit T N / (A * m)
102113

103114
@add_prefixes Hz (n, μ, u, m, k, M, G)
104115
@add_prefixes N ()
@@ -156,17 +167,17 @@ end
156167

157168
# Common assorted units
158169
## Time
159-
@register_unit min 60 * s
160-
@register_unit minute min
161-
@register_unit h 60 * min
162-
@register_unit hr h
163-
@register_unit day 24 * h
164-
@register_unit d day
165-
@register_unit wk 7 * day
166-
@register_unit yr 365.25 * day
167-
@register_unit inch 2.54 * cm
168-
@register_unit ft 12 * inch
169-
@register_unit mi 5280 * ft
170+
@_lazy_register_unit min 60 * s
171+
@_lazy_register_unit minute min
172+
@_lazy_register_unit h 60 * min
173+
@_lazy_register_unit hr h
174+
@_lazy_register_unit day 24 * h
175+
@_lazy_register_unit d day
176+
@_lazy_register_unit wk 7 * day
177+
@_lazy_register_unit yr 365.25 * day
178+
@_lazy_register_unit inch 2.54 * cm
179+
@_lazy_register_unit ft 12 * inch
180+
@_lazy_register_unit mi 5280 * ft
170181

171182
@add_prefixes min ()
172183
@add_prefixes minute ()
@@ -178,7 +189,7 @@ end
178189
@add_prefixes yr (k, M, G)
179190

180191
## Volume
181-
@register_unit L dm^3
192+
@_lazy_register_unit L dm^3
182193

183194
@add_prefixes L (μ, u, m, c, d)
184195

@@ -203,9 +214,7 @@ end
203214
# Do not wish to define physical constants, as the number of symbols might lead to ambiguity.
204215
# The user should define these instead.
205216

206-
"""A tuple of all possible unit symbols."""
207-
const UNIT_SYMBOLS = Tuple(_UNIT_SYMBOLS)
208-
const UNIT_VALUES = Tuple(_UNIT_VALUES)
209-
const UNIT_MAPPING = NamedTuple([s => i for (i, s) in enumerate(UNIT_SYMBOLS)])
217+
"""Update `UNIT_MAPPING` with all internally defined unit symbols."""
218+
merge!(UNIT_MAPPING, Dict(UNIT_SYMBOLS .=> 1:lastindex(UNIT_SYMBOLS)))
210219

211220
end

test/unittests.jl

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
using DynamicQuantities
22
using DynamicQuantities: FixedRational, NoDims, AbstractSymbolicDimensions
3-
using DynamicQuantities: DEFAULT_QUANTITY_TYPE, DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
3+
using DynamicQuantities:
4+
DEFAULT_QUANTITY_TYPE, DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
45
using DynamicQuantities: array_type, value_type, dim_type, quantity_type
56
using DynamicQuantities: GenericQuantity, with_type_parameters, constructorof
67
using DynamicQuantities: promote_quantity_on_quantity, promote_quantity_on_value
8+
using DynamicQuantities.Units: @register_unit, UNIT_VALUES, UNIT_MAPPING
79
using DynamicQuantities: map_dimensions
810
using Ratios: SimpleRatio
911
using SaferIntegers: SafeInt16
1012
using StaticArrays: SArray, MArray
1113
using LinearAlgebra: norm
1214
using Test
13-
15+
#=
1416
function record_show(s, f=show)
1517
io = IOBuffer()
1618
f(io, s)
@@ -1721,7 +1723,7 @@ end
17211723
) isa SymbolicDimensions{Int32}
17221724
17231725
@test copy(km) == km
1724-
1726+
17251727
# Any operation should immediately convert it:
17261728
@test km ^ -1 isa Quantity{T,DynamicQuantities.SymbolicDimensions{R}} where {T,R}
17271729
@@ -1842,3 +1844,23 @@ end
18421844
y = Quantity(2.0im, mass=1)
18431845
@test_throws DimensionError x^y
18441846
end
1847+
=#
1848+
1849+
# `@testset` rewrites the test block with a `let...end`, resulting in an invalid
1850+
# local `const` (ref: src/units.jl:26). To avoid it, register units outside the
1851+
# test block.
1852+
map_count_before_registering = length(UNIT_MAPPING)
1853+
@register_unit MyV u"V"
1854+
@register_unit MySV us"V"
1855+
1856+
@testset "Register Unit" begin
1857+
@test MyV === u"V"
1858+
@test MyV == us"V"
1859+
@test MySV == us"V"
1860+
1861+
@test length(UNIT_MAPPING) == map_count_before_registering + 2
1862+
1863+
for my_unit in (MySV, MyV)
1864+
@test my_unit in UNIT_VALUES
1865+
end
1866+
end

0 commit comments

Comments
 (0)