Skip to content

Commit

Permalink
feat: allow registering units externally
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
ven-k committed Jan 17, 2024
1 parent 756836e commit 220d923
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 43 deletions.
89 changes: 49 additions & 40 deletions src/units.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,35 @@ import ..DEFAULT_QUANTITY_TYPE

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

const _UNIT_SYMBOLS = Symbol[]
const _UNIT_VALUES = DEFAULT_QUANTITY_TYPE[]
const UNIT_SYMBOLS = Symbol[]
const UNIT_VALUES = DEFAULT_QUANTITY_TYPE[]

macro register_unit(name, value)
return esc(_register_unit(name, value))
end

macro _lazy_register_unit(name, value)
return esc(_register_unit(name, value; lazy = true))
end

macro add_prefixes(base_unit, prefixes)
@assert prefixes.head == :tuple
return esc(_add_prefixes(base_unit, prefixes.args, _register_unit))
end

function _register_unit(name::Symbol, value)
s = string(name)
const UNIT_MAPPING = Dict{Symbol,Int}()
function update_unit_mapping(name, value, unit_mapping::Dict{Symbol, Int} = UNIT_MAPPING)
unit_mapping[name] = length(unit_mapping) + 1
end

function _register_unit(name::Symbol, value; lazy = false)
name_symbol = Meta.quot(name)
return quote
haskey($UNIT_MAPPING, $name_symbol) && throw("Unit $name_symbol already exists.")
const $name = $value
push!(_UNIT_SYMBOLS, Symbol($s))
push!(_UNIT_VALUES, $name)
push!($UNIT_SYMBOLS, $name_symbol)
push!($UNIT_VALUES, $name)
!$lazy && $update_unit_mapping($name_symbol, $value)
end
end

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

# SI base units
@register_unit m DEFAULT_QUANTITY_TYPE(1.0, length=1)
@register_unit g DEFAULT_QUANTITY_TYPE(1e-3, mass=1)
@register_unit s DEFAULT_QUANTITY_TYPE(1.0, time=1)
@register_unit A DEFAULT_QUANTITY_TYPE(1.0, current=1)
@register_unit K DEFAULT_QUANTITY_TYPE(1.0, temperature=1)
@register_unit cd DEFAULT_QUANTITY_TYPE(1.0, luminosity=1)
@register_unit mol DEFAULT_QUANTITY_TYPE(1.0, amount=1)
@_lazy_register_unit m DEFAULT_QUANTITY_TYPE(1.0, length = 1)
@_lazy_register_unit g DEFAULT_QUANTITY_TYPE(1e-3, mass = 1)
@_lazy_register_unit s DEFAULT_QUANTITY_TYPE(1.0, time = 1)
@_lazy_register_unit A DEFAULT_QUANTITY_TYPE(1.0, current = 1)
@_lazy_register_unit K DEFAULT_QUANTITY_TYPE(1.0, temperature = 1)
@_lazy_register_unit cd DEFAULT_QUANTITY_TYPE(1.0, luminosity = 1)
@_lazy_register_unit mol DEFAULT_QUANTITY_TYPE(1.0, amount = 1)

@add_prefixes m (f, p, n, μ, u, c, d, m, k, M, G)
@add_prefixes g (p, n, μ, u, m, k)
Expand Down Expand Up @@ -88,17 +99,17 @@ end
)

# SI derived units
@register_unit Hz inv(s)
@register_unit N kg * m / s^2
@register_unit Pa N / m^2
@register_unit J N * m
@register_unit W J / s
@register_unit C A * s
@register_unit V W / A
@register_unit F C / V
@register_unit Ω V / A
@register_unit ohm Ω
@register_unit T N / (A * m)
@_lazy_register_unit Hz inv(s)
@_lazy_register_unit N kg * m / s^2
@_lazy_register_unit Pa N / m^2
@_lazy_register_unit J N * m
@_lazy_register_unit W J / s
@_lazy_register_unit C A * s
@_lazy_register_unit V W / A
@_lazy_register_unit F C / V
@_lazy_register_unit Ω V / A
@_lazy_register_unit ohm Ω
@_lazy_register_unit T N / (A * m)

@add_prefixes Hz (n, μ, u, m, k, M, G)
@add_prefixes N ()
Expand Down Expand Up @@ -156,17 +167,17 @@ end

# Common assorted units
## Time
@register_unit min 60 * s
@register_unit minute min
@register_unit h 60 * min
@register_unit hr h
@register_unit day 24 * h
@register_unit d day
@register_unit wk 7 * day
@register_unit yr 365.25 * day
@register_unit inch 2.54 * cm
@register_unit ft 12 * inch
@register_unit mi 5280 * ft
@_lazy_register_unit min 60 * s
@_lazy_register_unit minute min
@_lazy_register_unit h 60 * min
@_lazy_register_unit hr h
@_lazy_register_unit day 24 * h
@_lazy_register_unit d day
@_lazy_register_unit wk 7 * day
@_lazy_register_unit yr 365.25 * day
@_lazy_register_unit inch 2.54 * cm
@_lazy_register_unit ft 12 * inch
@_lazy_register_unit mi 5280 * ft

@add_prefixes min ()
@add_prefixes minute ()
Expand All @@ -178,7 +189,7 @@ end
@add_prefixes yr (k, M, G)

## Volume
@register_unit L dm^3
@_lazy_register_unit L dm^3

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

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

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

end
28 changes: 25 additions & 3 deletions test/unittests.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
using DynamicQuantities
using DynamicQuantities: FixedRational, NoDims, AbstractSymbolicDimensions
using DynamicQuantities: DEFAULT_QUANTITY_TYPE, DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
using DynamicQuantities:
DEFAULT_QUANTITY_TYPE, DEFAULT_DIM_BASE_TYPE, DEFAULT_DIM_TYPE, DEFAULT_VALUE_TYPE
using DynamicQuantities: array_type, value_type, dim_type, quantity_type
using DynamicQuantities: GenericQuantity, with_type_parameters, constructorof
using DynamicQuantities: promote_quantity_on_quantity, promote_quantity_on_value
using DynamicQuantities.Units: @register_unit, UNIT_VALUES, UNIT_MAPPING
using DynamicQuantities: map_dimensions
using Ratios: SimpleRatio
using SaferIntegers: SafeInt16
using StaticArrays: SArray, MArray
using LinearAlgebra: norm
using Test

#=
function record_show(s, f=show)
io = IOBuffer()
f(io, s)
Expand Down Expand Up @@ -1721,7 +1723,7 @@ end
) isa SymbolicDimensions{Int32}
@test copy(km) == km
# Any operation should immediately convert it:
@test km ^ -1 isa Quantity{T,DynamicQuantities.SymbolicDimensions{R}} where {T,R}
Expand Down Expand Up @@ -1842,3 +1844,23 @@ end
y = Quantity(2.0im, mass=1)
@test_throws DimensionError x^y
end
=#

# `@testset` rewrites the test block with a `let...end`, resulting in an invalid
# local `const` (ref: src/units.jl:26). To avoid it, register units outside the
# test block.
map_count_before_registering = length(UNIT_MAPPING)
@register_unit MyV u"V"
@register_unit MySV us"V"

@testset "Register Unit" begin
@test MyV === u"V"
@test MyV == us"V"
@test MySV == us"V"

@test length(UNIT_MAPPING) == map_count_before_registering + 2

for my_unit in (MySV, MyV)
@test my_unit in UNIT_VALUES
end
end

0 comments on commit 220d923

Please sign in to comment.