Skip to content

Commit 54ee0f8

Browse files
committed
feat: add WriteOnceReadMany and utils
`UNIT_SYMBOLS`, `UNIT_VALUES`, `UNIT_MAPPING`, `ALL_VALUES`,`ALL_SYMBOLS`, ALL_MAPPING`, `SYMBOLIC_UNIT_VALUES` are instances of this collection type. With this data type only a certain set of operations are permitted on these collections.
1 parent 2c869e3 commit 54ee0f8

File tree

6 files changed

+85
-31
lines changed

6 files changed

+85
-31
lines changed

src/DynamicQuantities.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export ustrip, dimension
1111
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
1212
export uparse, @u_str, sym_uparse, @us_str, uexpand, uconvert
1313

14+
const INDEX_TYPE = UInt16
15+
1416
include("internal_utils.jl")
1517
include("fixed_rational.jl")
1618
include("types.jl")
@@ -39,7 +41,7 @@ using .Units: UNIT_SYMBOLS
3941
let _units_import_expr = :(using .Units: m, g)
4042
append!(
4143
_units_import_expr.args[1].args,
42-
map(s -> Expr(:(.), s), filter(s -> s (:m, :g), UNIT_SYMBOLS))
44+
Expr(:(.), s) for s in UNIT_SYMBOLS if s (:m, :g)
4345
)
4446
eval(_units_import_expr)
4547
end

src/register_units.jl

+23-16
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import .SymbolicUnits:
33
SymbolicDimensionsSingleton, SYMBOLIC_UNIT_VALUES, update_symbolic_unit_values!
44

55
# Update the unit collections
6-
function update_unit_mapping(name, value, unit_mapping::Dict{Symbol,Int} = UNIT_MAPPING)
7-
unit_mapping[name] = length(unit_mapping) + 1
8-
end
6+
const UNIT_UPDATE_LOCK = Threads.SpinLock()
97

108
function update_all_values(name_symbol, unit)
11-
push!(ALL_SYMBOLS, name_symbol)
12-
push!(ALL_VALUES, unit)
13-
ALL_MAPPING[name_symbol] = INDEX_TYPE(length(ALL_MAPPING) + 1)
9+
lock(UNIT_UPDATE_LOCK) do
10+
push!(ALL_SYMBOLS, name_symbol)
11+
push!(ALL_VALUES, unit)
12+
i = lastindex(ALL_VALUES)
13+
ALL_MAPPING[name_symbol] = i
14+
UNIT_MAPPING[name_symbol] = i
15+
update_symbolic_unit_values!(name_symbol)
16+
end
1417
end
1518

1619
# Register
@@ -20,16 +23,20 @@ end
2023

2124
function _register_unit(name::Symbol, value)
2225
name_symbol = Meta.quot(name)
23-
reg_expr = _lazy_register_unit(name, value)
24-
push!(reg_expr.args,
25-
quote
26-
$update_unit_mapping($name_symbol, $value)
26+
index = get(ALL_MAPPING, name, INDEX_TYPE(0))
27+
if iszero(index)
28+
reg_expr = _lazy_register_unit(name, value)
29+
push!(reg_expr.args, quote
2730
$update_all_values($name_symbol, $value)
28-
$update_symbolic_unit_values!($name_symbol)
29-
# suppress the print of `SYMBOLIC_UNIT_VALUES`
3031
nothing
31-
end
32-
)
33-
return reg_expr
32+
end)
33+
return reg_expr
34+
else
35+
unit = ALL_VALUES[index]
36+
# When a utility function to expand `value` to its final form becomes
37+
# available, enable the following check. This will avoid throwing an error
38+
# if user is trying to register an existing unit with matching values.
39+
# unit.value != value && throw("Unit $name is already defined as $unit")
40+
throw("Unit $name is already defined as $unit")
41+
end
3442
end
35-

src/symbolic_dimensions.jl

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import ..WriteOnceReadMany
2+
import ..INDEX_TYPE
13
import .Units: UNIT_SYMBOLS, UNIT_MAPPING, UNIT_VALUES
24
import .Constants: CONSTANT_SYMBOLS, CONSTANT_MAPPING, CONSTANT_VALUES
35

@@ -8,13 +10,9 @@ disambiguate_symbol(s) = s in SYMBOL_CONFLICTS ? Symbol(s, :_constant) : s
810
# Prefer units over constants:
911
# For example, this means we can't have a symbolic Planck's constant,
1012
# as it is just "hours" (h), which is more common.
11-
const INDEX_TYPE = UInt16
12-
# Prefer units over constants:
13-
# For example, this means we can't have a symbolic Planck's constant,
14-
# as it is just "hours" (h), which is more common.
15-
const ALL_SYMBOLS = [UNIT_SYMBOLS..., disambiguate_symbol.(CONSTANT_SYMBOLS)...]
16-
const ALL_VALUES = [UNIT_VALUES..., CONSTANT_VALUES...]
17-
const ALL_MAPPING = Dict(ALL_SYMBOLS .=> (INDEX_TYPE(1):INDEX_TYPE(length(ALL_SYMBOLS))))
13+
const ALL_SYMBOLS = WriteOnceReadMany([UNIT_SYMBOLS..., disambiguate_symbol.(CONSTANT_SYMBOLS)...])
14+
const ALL_VALUES = WriteOnceReadMany([UNIT_VALUES..., CONSTANT_VALUES...])
15+
const ALL_MAPPING = WriteOnceReadMany(Dict(s => INDEX_TYPE(i) for (i, s) in enumerate(ALL_SYMBOLS)))
1816

1917
"""
2018
AbstractSymbolicDimensions{R} <: AbstractDimensions{R}
@@ -375,6 +373,7 @@ module SymbolicUnits
375373
import ..DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
376374
import ..DEFAULT_VALUE_TYPE
377375
import ..DEFAULT_DIM_BASE_TYPE
376+
import ..WriteOnceReadMany
378377

379378
# Lazily create unit symbols (since there are so many)
380379
module Constants
@@ -403,7 +402,7 @@ module SymbolicUnits
403402
import .Constants as SymbolicConstants
404403
import .Constants: SYMBOLIC_CONSTANT_VALUES
405404

406-
const SYMBOLIC_UNIT_VALUES = DEFAULT_SYMBOLIC_QUANTITY_TYPE[]
405+
const SYMBOLIC_UNIT_VALUES = WriteOnceReadMany{Vector{DEFAULT_SYMBOLIC_QUANTITY_TYPE}}()
407406

408407
function update_symbolic_unit_values!(unit, symbolic_unit_values = SYMBOLIC_UNIT_VALUES)
409408
@eval begin
@@ -415,7 +414,8 @@ module SymbolicUnits
415414
end
416415
end
417416

418-
update_symbolic_unit_values!.(UNIT_SYMBOLS)
417+
update_symbolic_unit_values!(w::WriteOnceReadMany) = update_symbolic_unit_values!.(w._raw_data)
418+
update_symbolic_unit_values!(UNIT_SYMBOLS)
419419

420420
"""
421421
sym_uparse(raw_string::AbstractString)

src/types.jl

+13
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,16 @@ struct DimensionError{Q1,Q2} <: Exception
304304
DimensionError(q1, q2) = new{typeof(q1),typeof(q2)}(q1, q2)
305305
DimensionError(q1) = DimensionError(q1, nothing)
306306
end
307+
308+
309+
"""
310+
WriteOnceReadMany()
311+
312+
Used for storing units, values, symbolic-units.
313+
"""
314+
struct WriteOnceReadMany{V}
315+
_raw_data::V
316+
317+
WriteOnceReadMany(_raw_data) = new{typeof(_raw_data)}(_raw_data)
318+
WriteOnceReadMany{T}() where T = WriteOnceReadMany(T())
319+
end

src/units.jl

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
module Units
22

3+
import ..WriteOnceReadMany
34
import ..DEFAULT_DIM_TYPE
45
import ..DEFAULT_VALUE_TYPE
56
import ..DEFAULT_QUANTITY_TYPE
67

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

9-
const UNIT_SYMBOLS = Symbol[]
10-
const UNIT_VALUES = DEFAULT_QUANTITY_TYPE[]
11-
const UNIT_MAPPING = Dict{Symbol,Int}()
10+
const UNIT_SYMBOLS = WriteOnceReadMany{Vector{Symbol}}()
11+
const UNIT_VALUES = WriteOnceReadMany{Vector{DEFAULT_QUANTITY_TYPE}}()
1212

1313
macro _lazy_register_unit(name, value)
1414
return esc(_lazy_register_unit(name, value))
@@ -22,7 +22,6 @@ end
2222
function _lazy_register_unit(name::Symbol, value)
2323
name_symbol = Meta.quot(name)
2424
quote
25-
haskey($UNIT_MAPPING, $name_symbol) && throw("Unit $($name_symbol) already exists.")
2625
const $name = $value
2726
push!($UNIT_SYMBOLS, $name_symbol)
2827
push!($UNIT_VALUES, $name)
@@ -207,6 +206,6 @@ end
207206
# The user should define these instead.
208207

209208
# Update `UNIT_MAPPING` with all internally defined unit symbols.
210-
merge!(UNIT_MAPPING, Dict(UNIT_SYMBOLS .=> 1:lastindex(UNIT_SYMBOLS)))
209+
const UNIT_MAPPING = WriteOnceReadMany(Dict(s => i for (i, s) in enumerate(UNIT_SYMBOLS)))
211210

212211
end

src/utils.jl

+33
Original file line numberDiff line numberDiff line change
@@ -428,3 +428,36 @@ Get the amount dimension of a quantity (e.g., mol^(uamount)).
428428
"""
429429
uamount(q::UnionAbstractQuantity) = uamount(dimension(q))
430430
uamount(d::AbstractDimensions) = d.amount
431+
432+
"""
433+
Utility functions for `WriteOnceReadMany`
434+
"""
435+
436+
for f in (:enumerate, :length, :lastindex)
437+
@eval begin
438+
Base.$f(w::WriteOnceReadMany) = $f(w._raw_data)
439+
end
440+
end
441+
442+
Base.getindex(w::WriteOnceReadMany, i::Union{Int, INDEX_TYPE, Symbol}) = getindex(w._raw_data, i)
443+
444+
Base.iterate(w::WriteOnceReadMany) = iterate(w._raw_data)
445+
Base.iterate(w::WriteOnceReadMany, i::Int) = iterate(w._raw_data, i)
446+
447+
Base.intersect(w::WriteOnceReadMany, v::AbstractSet) = intersect(w._raw_data, v)
448+
Base.intersect(v::AbstractSet, w::WriteOnceReadMany) = intersect(v, w._raw_data)
449+
450+
Base.push!(w::WriteOnceReadMany, val...) = push!(w._raw_data, val...)
451+
452+
for f in (:findfirst, :filter)
453+
@eval begin
454+
Base.$f(val::Function, w::WriteOnceReadMany) = $f(val, w._raw_data)
455+
end
456+
end
457+
458+
function Base.setindex!(w::DynamicQuantities.WriteOnceReadMany{Dict{Symbol, T}}, i::T, s::Symbol) where T <: Union{Int, INDEX_TYPE}
459+
haskey(w._raw_data, s) && throw("Unit $s already exists at index $(w[s])")
460+
setindex!(w._raw_data, i, s)
461+
end
462+
463+
Base.get(w::WriteOnceReadMany{Dict{Symbol, INDEX_TYPE}}, a, b) = get(w._raw_data, a, b)

0 commit comments

Comments
 (0)