Skip to content

Commit ecaf969

Browse files
authored
Merge pull request #106 from SymbolicML/fix-precomp
Improve `@u_str` implementation to fix downstream precompilation
2 parents 479f663 + 0636506 commit ecaf969

File tree

9 files changed

+381
-30
lines changed

9 files changed

+381
-30
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ julia> room_temp = 100kPa
8989
100000.0 m⁻¹ kg s⁻²
9090
```
9191

92+
Note that `Units` is an exported submodule, so you can
93+
also access this as `Units.kPa`. You may like to define
94+
95+
```julia
96+
julia> const U = Units
97+
```
98+
99+
so that you can simply write, say, `U.kPa` or `C.m_e`.
100+
92101
This supports a wide range of SI base and derived units, with common
93102
prefixes.
94103

@@ -169,13 +178,25 @@ julia> Constants.c
169178
2.99792458e8 m s⁻¹
170179
```
171180

181+
which you may like to define as
182+
183+
```julia
184+
julia> const C = Constants
185+
```
186+
172187
These can also be used inside the `u"..."` macro:
173188

174189
```julia
175190
julia> u"Constants.c * Hz"
176191
2.99792458e8 m s⁻²
177192
```
178193

194+
Similarly, you can just import each individual constant:
195+
196+
```julia
197+
julia> using DynamicQuantities.Constants: h
198+
```
199+
179200
For the full list, see the [docs](https://symbolicml.org/DynamicQuantities.jl/dev/constants/).
180201

181202

@@ -220,6 +241,22 @@ julia> uconvert(us"nm", 5e-9u"m") # can also write 5e-9u"m" |> uconvert(us"nm")
220241
5.0 nm
221242
```
222243

244+
Finally, you can also import these directly:
245+
246+
```julia
247+
julia> using DynamicQuantities.SymbolicUnits: cm
248+
```
249+
250+
or constants:
251+
252+
```julia
253+
julia> using DynamicQuantities.SymbolicConstants: h
254+
```
255+
256+
Note that `SymbolicUnits` and `SymbolicConstants` are exported,
257+
so you can simply access these as `SymbolicUnits.cm` and `SymbolicConstants.h`,
258+
respectively.
259+
223260
### Arrays
224261

225262
For working with an array of quantities that have the same dimensions,

docs/src/types.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ Another type which subtypes `AbstractDimensions` is `SymbolicDimensions`:
2525
SymbolicDimensions
2626
```
2727

28+
Just note that all of the symbolic units and constants are stored using the
29+
immutable `SymbolicDimensionsSingleton`, which shares the same
30+
supertype `AbstractSymbolicDimensions <: AbstractDimensions`. These get immediately
31+
converted to the mutable `SymbolicDimensions` when used in any
32+
calculation.
33+
34+
```@docs
35+
SymbolicDimensionsSingleton
36+
AbstractSymbolicDimensions
37+
```
38+
2839
## Arrays
2940

3041
```@docs
@@ -40,6 +51,19 @@ which is subtyped to `Any`.
4051
```@docs
4152
GenericQuantity
4253
AbstractGenericQuantity
54+
```
55+
56+
In the other direction, there is also `RealQuantity`,
57+
which is subtyped to `Real`.
58+
59+
```@docs
60+
RealQuantity
61+
AbstractRealQuantity
62+
```
63+
64+
More general, these are each contained in the following:
65+
66+
```@docs
4367
UnionAbstractQuantity
4468
DynamicQuantities.ABSTRACT_QUANTITY_TYPES
4569
```

ext/DynamicQuantitiesUnitfulExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ for (_, _, Q) in ABSTRACT_QUANTITY_TYPES
3030
validate_upreferred()
3131
cumulator = DynamicQuantities.ustrip(x)
3232
dims = DynamicQuantities.dimension(x)
33-
if dims isa DynamicQuantities.SymbolicDimensions
33+
if dims isa DynamicQuantities.AbstractSymbolicDimensions
3434
throw(ArgumentError("Conversion of a `DynamicQuantities." * string($Q) * "` to a `Unitful.Quantity` is not defined with dimensions of type `SymbolicDimensions`. Instead, you can first use the `uexpand` function to convert the dimensions to their base SI form of type `Dimensions`, then convert this quantity to a `Unitful.Quantity`."))
3535
end
3636
equiv = unitful_equivalences()

src/DynamicQuantities.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
module DynamicQuantities
22

33
export Units, Constants, SymbolicUnits, SymbolicConstants
4-
export AbstractDimensions, AbstractQuantity, AbstractGenericQuantity, AbstractRealQuantity, UnionAbstractQuantity
5-
export Quantity, GenericQuantity, RealQuantity, Dimensions, SymbolicDimensions, QuantityArray, DimensionError
4+
export AbstractQuantity, AbstractGenericQuantity, AbstractRealQuantity, UnionAbstractQuantity
5+
export Quantity, GenericQuantity, RealQuantity
6+
export AbstractDimensions, Dimensions, NoDims
7+
export AbstractSymbolicDimensions, SymbolicDimensions, SymbolicDimensionsSingleton
8+
export QuantityArray
9+
export DimensionError
610
export ustrip, dimension
711
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
812
export uparse, @u_str, sym_uparse, @us_str, uexpand, uconvert

src/symbolic_dimensions.jl

Lines changed: 101 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,16 @@ dimension_names(::Type{<:AbstractSymbolicDimensions}) = ALL_SYMBOLS
9999
Base.propertynames(::AbstractSymbolicDimensions) = ALL_SYMBOLS
100100
Base.getindex(d::AbstractSymbolicDimensions, k::Symbol) = getproperty(d, k)
101101
constructorof(::Type{<:SymbolicDimensions}) = SymbolicDimensions
102-
constructorof(::Type{<:SymbolicDimensionsSingleton{R}}) where {R} = SymbolicDimensionsSingleton{R}
102+
constructorof(::Type{<:SymbolicDimensionsSingleton}) = SymbolicDimensionsSingleton
103103
with_type_parameters(::Type{<:SymbolicDimensions}, ::Type{R}) where {R} = SymbolicDimensions{R}
104104
with_type_parameters(::Type{<:SymbolicDimensionsSingleton}, ::Type{R}) where {R} = SymbolicDimensionsSingleton{R}
105105
nzdims(d::SymbolicDimensions) = getfield(d, :nzdims)
106106
nzdims(d::SymbolicDimensionsSingleton) = (getfield(d, :dim),)
107107
nzvals(d::SymbolicDimensions) = getfield(d, :nzvals)
108108
nzvals(::SymbolicDimensionsSingleton{R}) where {R} = (one(R),)
109-
Base.eltype(::AbstractSymbolicDimensions{R}) where {R} = R
110-
Base.eltype(::Type{<:AbstractSymbolicDimensions{R}}) where {R} = R
109+
110+
# Need to construct with `R` if available, as can't figure it out otherwise:
111+
constructorof(::Type{<:SymbolicDimensionsSingleton{R}}) where {R} = SymbolicDimensionsSingleton{R}
111112

112113
# Conversion:
113114
function SymbolicDimensions(d::SymbolicDimensionsSingleton{R}) where {R}
@@ -170,23 +171,55 @@ uexpand(q::QuantityArray) = uexpand.(q)
170171
Convert a quantity `q` with base SI units to the symbolic units of `qout`, for `q` and `qout` with compatible units.
171172
Mathematically, the result has value `q / uexpand(qout)` and units `dimension(qout)`.
172173
"""
173-
function uconvert(qout::UnionAbstractQuantity{<:Any, <:AbstractSymbolicDimensions}, q::UnionAbstractQuantity{<:Any, <:Dimensions})
174+
function uconvert(qout::UnionAbstractQuantity{<:Any, <:SymbolicDimensions}, q::UnionAbstractQuantity{<:Any, <:Dimensions})
174175
@assert isone(ustrip(qout)) "You passed a quantity with a non-unit value to uconvert."
175176
qout_expanded = uexpand(qout)
176177
dimension(q) == dimension(qout_expanded) || throw(DimensionError(q, qout_expanded))
177178
new_val = ustrip(q) / ustrip(qout_expanded)
178179
new_dim = dimension(qout)
179180
return new_quantity(typeof(q), new_val, new_dim)
180181
end
181-
function uconvert(qout::UnionAbstractQuantity{<:Any,<:AbstractSymbolicDimensions}, q::QuantityArray{<:Any,<:Any,<:Dimensions})
182+
function uconvert(qout::UnionAbstractQuantity{<:Any,<:SymbolicDimensions}, q::QuantityArray{<:Any,<:Any,<:Dimensions})
182183
@assert isone(ustrip(qout)) "You passed a quantity with a non-unit value to uconvert."
183184
qout_expanded = uexpand(qout)
184185
dimension(q) == dimension(qout_expanded) || throw(DimensionError(q, qout_expanded))
185186
new_array = ustrip(q) ./ ustrip(qout_expanded)
186187
new_dim = dimension(qout)
187188
return QuantityArray(new_array, new_dim, quantity_type(q))
188189
end
189-
# TODO: Method for converting SymbolicDimensions -> SymbolicDimensions
190+
191+
# Ensure we always do operations with SymbolicDimensions:
192+
function uconvert(
193+
qout::UnionAbstractQuantity{T,<:SymbolicDimensionsSingleton{R}},
194+
q::Union{
195+
<:UnionAbstractQuantity{<:Any,<:Dimensions},
196+
<:QuantityArray{<:Any,<:Any,<:Dimensions},
197+
},
198+
) where {T,R}
199+
return uconvert(
200+
convert(
201+
with_type_parameters(
202+
typeof(qout),
203+
T,
204+
with_type_parameters(SymbolicDimensions, R),
205+
),
206+
qout,
207+
),
208+
q,
209+
)
210+
end
211+
212+
# Allow user to convert SymbolicDimensions -> SymbolicDimensions
213+
function uconvert(
214+
qout::UnionAbstractQuantity{<:Any,<:AbstractSymbolicDimensions{R}},
215+
q::Union{
216+
<:UnionAbstractQuantity{<:Any,<:AbstractSymbolicDimensions},
217+
<:QuantityArray{<:Any,<:Any,<:AbstractSymbolicDimensions},
218+
},
219+
) where {R}
220+
return uconvert(qout, uexpand(q))
221+
end
222+
190223

191224
"""
192225
uconvert(qout::UnionAbstractQuantity{<:Any, <:AbstractSymbolicDimensions})
@@ -197,7 +230,7 @@ a function equivalent to `q -> uconvert(qout, q)`.
197230
uconvert(qout::UnionAbstractQuantity{<:Any,<:AbstractSymbolicDimensions}) = Base.Fix1(uconvert, qout)
198231

199232
Base.copy(d::SymbolicDimensions) = SymbolicDimensions(copy(nzdims(d)), copy(nzvals(d)))
200-
Base.copy(d::SymbolicDimensionsSingleton) = constructorof(d)(getfield(d, :dim))
233+
Base.copy(d::SymbolicDimensionsSingleton) = constructorof(typeof(d))(getfield(d, :dim))
201234

202235
function Base.:(==)(l::AbstractSymbolicDimensions, r::AbstractSymbolicDimensions)
203236
nzdims_l = nzdims(l)
@@ -336,6 +369,7 @@ to enable pretty-printing of units.
336369
module SymbolicUnits
337370

338371
import ..UNIT_SYMBOLS
372+
import ..CONSTANT_SYMBOLS
339373
import ..SymbolicDimensionsSingleton
340374
import ...constructorof
341375
import ...DEFAULT_SYMBOLIC_QUANTITY_TYPE
@@ -353,22 +387,34 @@ module SymbolicUnits
353387
import ....DEFAULT_VALUE_TYPE
354388
import ....DEFAULT_DIM_BASE_TYPE
355389

390+
const _SYMBOLIC_CONSTANT_VALUES = DEFAULT_SYMBOLIC_QUANTITY_TYPE[]
391+
356392
for unit in CONSTANT_SYMBOLS
357-
@eval const $unit = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
358-
DEFAULT_VALUE_TYPE(1.0),
359-
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}($(QuoteNode(disambiguate_symbol(unit))))
360-
)
393+
@eval begin
394+
const $unit = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
395+
DEFAULT_VALUE_TYPE(1.0),
396+
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}($(QuoteNode(disambiguate_symbol(unit))))
397+
)
398+
push!(_SYMBOLIC_CONSTANT_VALUES, $unit)
399+
end
361400
end
401+
const SYMBOLIC_CONSTANT_VALUES = Tuple(_SYMBOLIC_CONSTANT_VALUES)
362402
end
363403
import .Constants
364404
import .Constants as SymbolicConstants
405+
import .Constants: SYMBOLIC_CONSTANT_VALUES
365406

407+
const _SYMBOLIC_UNIT_VALUES = DEFAULT_SYMBOLIC_QUANTITY_TYPE[]
366408
for unit in UNIT_SYMBOLS
367-
@eval const $unit = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
368-
DEFAULT_VALUE_TYPE(1.0),
369-
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}($(QuoteNode(unit)))
370-
)
409+
@eval begin
410+
const $unit = constructorof(DEFAULT_SYMBOLIC_QUANTITY_TYPE)(
411+
DEFAULT_VALUE_TYPE(1.0),
412+
SymbolicDimensionsSingleton{DEFAULT_DIM_BASE_TYPE}($(QuoteNode(unit)))
413+
)
414+
push!(_SYMBOLIC_UNIT_VALUES, $unit)
415+
end
371416
end
417+
const SYMBOLIC_UNIT_VALUES = Tuple(_SYMBOLIC_UNIT_VALUES)
372418

373419

374420
"""
@@ -387,18 +433,50 @@ module SymbolicUnits
387433
`Quantity(1.0, SymbolicDimensions, c=2, Hz=2)`. However, note that due to
388434
namespace collisions, a few physical constants are automatically converted.
389435
"""
390-
function sym_uparse(raw_string::AbstractString)
391-
raw_result = eval(Meta.parse(raw_string))
392-
return copy(as_quantity(raw_result))::DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
436+
function sym_uparse(s::AbstractString)
437+
ex = map_to_scope(Meta.parse(s))
438+
ex = :($as_quantity($ex))
439+
return copy(eval(ex))::DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE
393440
end
394441

395442
as_quantity(q::DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE) = q
396443
as_quantity(x::Number) = convert(DEFAULT_SYMBOLIC_QUANTITY_OUTPUT_TYPE, x)
397444
as_quantity(x) = error("Unexpected type evaluated: $(typeof(x))")
445+
446+
function map_to_scope(ex::Expr)
447+
if ex.head == :call
448+
ex.args[2:end] = map(map_to_scope, ex.args[2:end])
449+
return ex
450+
elseif ex.head == :. && ex.args[1] == :Constants
451+
@assert ex.args[2] isa QuoteNode
452+
return lookup_constant(ex.args[2].value)
453+
else
454+
throw(ArgumentError("Unexpected expression: $ex. Only `:call` and `:.` (for `SymbolicConstants`) are expected."))
455+
end
456+
end
457+
function map_to_scope(sym::Symbol)
458+
if sym in UNIT_SYMBOLS
459+
return lookup_unit(sym)
460+
elseif sym in CONSTANT_SYMBOLS
461+
throw(ArgumentError("Symbol $sym found in `SymbolicConstants` but not `SymbolicUnits`. Please access the `SymbolicConstants` module. For example, `u\"SymbolicConstants.$sym\"`."))
462+
else
463+
throw(ArgumentError("Symbol $sym not found in `SymbolicUnits` or `SymbolicConstants`."))
464+
end
465+
end
466+
function map_to_scope(ex)
467+
return ex
468+
end
469+
function lookup_unit(ex::Symbol)
470+
i = findfirst(==(ex), UNIT_SYMBOLS)::Int
471+
return as_quantity(SYMBOLIC_UNIT_VALUES[i])
472+
end
473+
function lookup_constant(ex::Symbol)
474+
i = findfirst(==(ex), CONSTANT_SYMBOLS)::Int
475+
return as_quantity(SYMBOLIC_CONSTANT_VALUES[i])
476+
end
398477
end
399478

400-
import .SymbolicUnits: sym_uparse
401-
import .SymbolicUnits: SymbolicConstants
479+
import .SymbolicUnits: as_quantity, sym_uparse, SymbolicConstants, map_to_scope
402480

403481
"""
404482
us"[unit expression]"
@@ -416,7 +494,9 @@ module. So, for example, `us"Constants.c^2 * Hz^2"` would evaluate to
416494
namespace collisions, a few physical constants are automatically converted.
417495
"""
418496
macro us_str(s)
419-
return esc(SymbolicUnits.sym_uparse(s))
497+
ex = map_to_scope(Meta.parse(s))
498+
ex = :($as_quantity($ex))
499+
return esc(ex)
420500
end
421501

422502
function Base.promote_rule(::Type{SymbolicDimensionsSingleton{R1}}, ::Type{SymbolicDimensionsSingleton{R2}}) where {R1,R2}

src/types.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,20 @@ end
122122

123123
const DEFAULT_DIM_TYPE = Dimensions{DEFAULT_DIM_BASE_TYPE}
124124

125+
"""
126+
NoDims{R}
127+
128+
A type representing the dimensions of a non-quantity.
129+
130+
For any `getproperty` call on this type, the result is `zero(R)`.
131+
"""
132+
struct NoDims{R<:Real} <: AbstractDimensions{R}
133+
end
134+
135+
Base.getproperty(::NoDims{R}, ::Symbol) where {R} = zero(R)
136+
137+
const DEFAULT_DIMENSIONLESS_TYPE = NoDims{DEFAULT_DIM_BASE_TYPE}
138+
125139
"""
126140
Quantity{T<:Number,D<:AbstractDimensions} <: AbstractQuantity{T,D} <: Number
127141

0 commit comments

Comments
 (0)