Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/DynamicQuantities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ include("uparse.jl")
include("symbolic_dimensions.jl")
include("complex.jl")
include("disambiguities.jl")

include("register_units.jl")
include("deprecated.jl")
export expand_units
export @register_unit

import PackageExtensionCompat: @require_extensions
import .Units
Expand All @@ -43,7 +44,6 @@ let _units_import_expr = :(using .Units: m, g)
eval(_units_import_expr)
end


function __init__()
@require_extensions
end
Expand Down
35 changes: 35 additions & 0 deletions src/register_units.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import .Units: UNIT_MAPPING, UNIT_SYMBOLS, UNIT_VALUES, _lazy_register_unit
import .SymbolicUnits:
SymbolicDimensionsSingleton, SYMBOLIC_UNIT_VALUES, update_symbolic_unit_values!

# Update the unit collections
function update_unit_mapping(name, value, unit_mapping::Dict{Symbol,Int} = UNIT_MAPPING)
unit_mapping[name] = length(unit_mapping) + 1
end

function update_all_values(name_symbol, unit)
push!(ALL_SYMBOLS, name_symbol)
push!(ALL_VALUES, unit)
ALL_MAPPING[name_symbol] = INDEX_TYPE(length(ALL_MAPPING) + 1)
end

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

function _register_unit(name::Symbol, value)
name_symbol = Meta.quot(name)
reg_expr = _lazy_register_unit(name, value)
push!(reg_expr.args,
quote
$update_unit_mapping($name_symbol, $value)
$update_all_values($name_symbol, $value)
$update_symbolic_unit_values!($name_symbol)
# suppress the print of `SYMBOLIC_UNIT_VALUES`
nothing
end
)
return reg_expr
end

44 changes: 22 additions & 22 deletions src/symbolic_dimensions.jl
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import .Units: UNIT_SYMBOLS, UNIT_MAPPING, UNIT_VALUES
import .Constants: CONSTANT_SYMBOLS, CONSTANT_MAPPING, CONSTANT_VALUES


const SYMBOL_CONFLICTS = intersect(UNIT_SYMBOLS, CONSTANT_SYMBOLS)

disambiguate_symbol(s) = s in SYMBOL_CONFLICTS ? Symbol(s, :_constant) : s

const INDEX_TYPE = UInt8
# Prefer units over constants:
# For example, this means we can't have a symbolic Planck's constant,
# as it is just "hours" (h), which is more common.
const ALL_SYMBOLS = (
UNIT_SYMBOLS...,
disambiguate_symbol.(CONSTANT_SYMBOLS)...
)
const ALL_VALUES = (UNIT_VALUES..., CONSTANT_VALUES...)
const ALL_MAPPING = NamedTuple{ALL_SYMBOLS}(INDEX_TYPE(1):INDEX_TYPE(length(ALL_SYMBOLS)))
const INDEX_TYPE = UInt16
# Prefer units over constants:
# For example, this means we can't have a symbolic Planck's constant,
# as it is just "hours" (h), which is more common.
const ALL_SYMBOLS = [UNIT_SYMBOLS..., disambiguate_symbol.(CONSTANT_SYMBOLS)...]
const ALL_VALUES = [UNIT_VALUES..., CONSTANT_VALUES...]
const ALL_MAPPING = Dict(ALL_SYMBOLS .=> (INDEX_TYPE(1):INDEX_TYPE(length(ALL_SYMBOLS))))

"""
AbstractSymbolicDimensions{R} <: AbstractDimensions{R}
Expand Down Expand Up @@ -169,7 +168,7 @@ uexpand(q::QuantityArray) = uexpand.(q)
uconvert(qout::UnionAbstractQuantity{<:Any, <:AbstractSymbolicDimensions}, q::UnionAbstractQuantity{<:Any, <:Dimensions})

Convert a quantity `q` with base SI units to the symbolic units of `qout`, for `q` and `qout` with compatible units.
Mathematically, the result has value `q / uexpand(qout)` and units `dimension(qout)`.
Mathematically, the result has value `q / uexpand(qout)` and units `dimension(qout)`.
"""
function uconvert(qout::UnionAbstractQuantity{<:Any, <:SymbolicDimensions}, q::UnionAbstractQuantity{<:Any, <:Dimensions})
@assert isone(ustrip(qout)) "You passed a quantity with a non-unit value to uconvert."
Expand Down Expand Up @@ -224,7 +223,7 @@ end
"""
uconvert(qout::UnionAbstractQuantity{<:Any, <:AbstractSymbolicDimensions})

Create a function that converts an input quantity `q` with base SI units to the symbolic units of `qout`, i.e
Create a function that converts an input quantity `q` with base SI units to the symbolic units of `qout`, i.e
a function equivalent to `q -> uconvert(qout, q)`.
"""
uconvert(qout::UnionAbstractQuantity{<:Any,<:AbstractSymbolicDimensions}) = Base.Fix1(uconvert, qout)
Expand Down Expand Up @@ -371,21 +370,21 @@ module SymbolicUnits
import ..UNIT_SYMBOLS
import ..CONSTANT_SYMBOLS
import ..SymbolicDimensionsSingleton
import ...constructorof
import ...DEFAULT_SYMBOLIC_QUANTITY_TYPE
import ...DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
import ...DEFAULT_VALUE_TYPE
import ...DEFAULT_DIM_BASE_TYPE
import ..DEFAULT_SYMBOLIC_QUANTITY_TYPE
import ..constructorof
import ..DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
import ..DEFAULT_VALUE_TYPE
import ..DEFAULT_DIM_BASE_TYPE

# Lazily create unit symbols (since there are so many)
module Constants
import ...CONSTANT_SYMBOLS
import ...SymbolicDimensionsSingleton
import ...constructorof
import ...disambiguate_symbol
import ....DEFAULT_SYMBOLIC_QUANTITY_TYPE
import ....DEFAULT_VALUE_TYPE
import ....DEFAULT_DIM_BASE_TYPE
import ...DEFAULT_SYMBOLIC_QUANTITY_TYPE
import ...DEFAULT_VALUE_TYPE
import ...DEFAULT_DIM_BASE_TYPE

const _SYMBOLIC_CONSTANT_VALUES = DEFAULT_SYMBOLIC_QUANTITY_TYPE[]

Expand All @@ -404,18 +403,19 @@ module SymbolicUnits
import .Constants as SymbolicConstants
import .Constants: SYMBOLIC_CONSTANT_VALUES

const _SYMBOLIC_UNIT_VALUES = DEFAULT_SYMBOLIC_QUANTITY_TYPE[]
for unit in UNIT_SYMBOLS
const SYMBOLIC_UNIT_VALUES = DEFAULT_SYMBOLIC_QUANTITY_TYPE[]

function update_symbolic_unit_values!(unit, symbolic_unit_values = SYMBOLIC_UNIT_VALUES)
@eval begin
const $unit = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
DEFAULT_VALUE_TYPE(1.0),
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}($(QuoteNode(unit)))
)
push!(_SYMBOLIC_UNIT_VALUES, $unit)
push!($symbolic_unit_values, $unit)
end
end
const SYMBOLIC_UNIT_VALUES = Tuple(_SYMBOLIC_UNIT_VALUES)

update_symbolic_unit_values!.(UNIT_SYMBOLS)

"""
sym_uparse(raw_string::AbstractString)
Expand Down
91 changes: 46 additions & 45 deletions src/units.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,30 @@ 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[]
const UNIT_MAPPING = Dict{Symbol,Int}()

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

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

function _register_unit(name::Symbol, value)
s = string(name)
return quote
function _lazy_register_unit(name::Symbol, value)
name_symbol = Meta.quot(name)
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)
end
end


function _add_prefixes(base_unit::Symbol, prefixes, register_function)
all_prefixes = (
f=1e-15, p=1e-12, n=1e-9, μ=1e-6, u=1e-6, m=1e-3, c=1e-2, d=1e-1,
Expand All @@ -42,13 +45,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 +91,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 +159,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 +181,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 @@ -188,7 +191,7 @@ end
)

## Pressure
@register_unit bar 100 * kPa
@_lazy_register_unit bar 100 * kPa

@add_prefixes bar (m,)

Expand All @@ -203,9 +206,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
36 changes: 34 additions & 2 deletions test/unittests.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
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: UNIT_VALUES, UNIT_MAPPING, UNIT_SYMBOLS, ALL_MAPPING, ALL_SYMBOLS, ALL_VALUES
using DynamicQuantities.SymbolicUnits: SYMBOLIC_UNIT_VALUES
using DynamicQuantities: map_dimensions
using Ratios: SimpleRatio
using SaferIntegers: SafeInt16
Expand Down Expand Up @@ -1727,7 +1730,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 @@ -1848,3 +1851,32 @@ 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)
all_map_count_before_registering = length(ALL_MAPPING)
@register_unit MyV u"V"
@register_unit MySV us"V"
@register_unit MySV2 us"km/h"

@testset "Register Unit" begin
@test MyV === u"V"
@test MyV == us"V"
@test MySV == us"V"
@test MySV2 == us"km/h"

@test length(UNIT_MAPPING) == map_count_before_registering + 3
@test length(ALL_MAPPING) == all_map_count_before_registering + 3

for my_unit in (MySV, MyV)
@test my_unit in UNIT_VALUES
@test my_unit in ALL_VALUES
@test my_unit in SYMBOLIC_UNIT_VALUES
end
for my_unit in (:MySV, :MyV)
@test my_unit in UNIT_SYMBOLS
@test my_unit in ALL_SYMBOLS
end
end