@@ -99,15 +99,16 @@ dimension_names(::Type{<:AbstractSymbolicDimensions}) = ALL_SYMBOLS
9999Base. propertynames (:: AbstractSymbolicDimensions ) = ALL_SYMBOLS
100100Base. getindex (d:: AbstractSymbolicDimensions , k:: Symbol ) = getproperty (d, k)
101101constructorof (:: Type{<:SymbolicDimensions} ) = SymbolicDimensions
102- constructorof (:: Type{<:SymbolicDimensionsSingleton{R}} ) where {R} = SymbolicDimensionsSingleton{R}
102+ constructorof (:: Type{<:SymbolicDimensionsSingleton} ) = SymbolicDimensionsSingleton
103103with_type_parameters (:: Type{<:SymbolicDimensions} , :: Type{R} ) where {R} = SymbolicDimensions{R}
104104with_type_parameters (:: Type{<:SymbolicDimensionsSingleton} , :: Type{R} ) where {R} = SymbolicDimensionsSingleton{R}
105105nzdims (d:: SymbolicDimensions ) = getfield (d, :nzdims )
106106nzdims (d:: SymbolicDimensionsSingleton ) = (getfield (d, :dim ),)
107107nzvals (d:: SymbolicDimensions ) = getfield (d, :nzvals )
108108nzvals (:: 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:
113114function SymbolicDimensions (d:: SymbolicDimensionsSingleton{R} ) where {R}
@@ -170,23 +171,55 @@ uexpand(q::QuantityArray) = uexpand.(q)
170171Convert a quantity `q` with base SI units to the symbolic units of `qout`, for `q` and `qout` with compatible units.
171172Mathematically, 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)
180181end
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))
188189end
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)`.
197230uconvert (qout:: UnionAbstractQuantity{<:Any,<:AbstractSymbolicDimensions} ) = Base. Fix1 (uconvert, qout)
198231
199232Base. 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
202235function Base.:(== )(l:: AbstractSymbolicDimensions , r:: AbstractSymbolicDimensions )
203236 nzdims_l = nzdims (l)
@@ -336,6 +369,7 @@ to enable pretty-printing of units.
336369module 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
398477end
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
416494namespace collisions, a few physical constants are automatically converted.
417495"""
418496macro 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)
420500end
421501
422502function Base. promote_rule (:: Type{SymbolicDimensionsSingleton{R1}} , :: Type{SymbolicDimensionsSingleton{R2}} ) where {R1,R2}
0 commit comments