55[ ![ Dev] ( https://img.shields.io/badge/docs-dev-blue.svg )] ( https://symbolicml.org/DynamicQuantities.jl/dev/ )
66[ ![ Build Status] ( https://github.com/SymbolicML/DynamicQuantities.jl/actions/workflows/CI.yml/badge.svg?branch=main )] ( https://github.com/SymbolicML/DynamicQuantities.jl/actions/workflows/CI.yml?query=branch%3Amain )
77[ ![ Coverage] ( https://coveralls.io/repos/github/SymbolicML/DynamicQuantities.jl/badge.svg?branch=main )] ( https://coveralls.io/github/SymbolicML/DynamicQuantities.jl?branch=main )
8+ [ ![ Aqua QA] ( https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg )] ( https://github.com/JuliaTesting/Aqua.jl )
89
910</div >
1011
1112DynamicQuantities defines a simple statically-typed ` Quantity ` type for Julia.
1213Physical dimensions are stored as a * value* , as opposed to a parametric type, as in [ Unitful.jl] ( https://github.com/PainterQubits/Unitful.jl ) .
13- This is done to allow for calculations where physical dimensions are not known at compile time .
14+ This can greatly improve both runtime performance, by avoiding type instabilities, and startup time, as it avoids overspecializing methods .
1415
1516- [ Performance] ( #performance )
1617- [ Usage] ( #usage )
18+ - [ Constants] ( #constants )
19+ - [ Symbolic Units] ( #symbolic-units )
20+ - [ Arrays] ( #arrays )
21+ - [ Unitful] ( #unitful )
1722- [ Types] ( #types )
1823- [ Vectors] ( #vectors )
1924
@@ -25,22 +30,22 @@ when the compiler cannot infer dimensions in a function:
2530``` julia
2631julia> using BenchmarkTools, DynamicQuantities; import Unitful
2732
28- julia> dyn_uni = 0.2 u " m^0.5 * kg * mol^3 "
29- 0.2 m¹ᐟ² kg mol³
33+ julia> dyn_uni = 0.2 u " m/s "
34+ 0.2 m s⁻¹
3035
3136julia> unitful = convert (Unitful. Quantity, dyn_uni)
32- 0.2 kg m¹ᐟ² mol³
37+ 0.2 m s⁻¹
3338
3439julia> f (x, i) = x ^ i * 0.3 ;
3540
3641julia> @btime f ($ dyn_uni, 1 );
37- 8.759 ns (0 allocations: 0 bytes)
42+ 2.708 ns (0 allocations: 0 bytes)
3843
3944julia> @btime f ($ unitful, 1 );
40- 30.083 μs (42 allocations: 1.91 KiB)
45+ 2.597 μs (30 allocations: 1.33 KiB)
4146```
4247
43- ** ( Note the μ and n.) **
48+ ** Note the μ and n: this is a 1000x speedup! **
4449Here, the DynamicQuantities quantity object allows the compiler to build a function that is type stable,
4550while the Unitful quantity object, which stores its dimensions in the type, requires type inference at runtime.
4651
@@ -51,10 +56,10 @@ then you can get better speeds with Unitful:
5156julia> g (x) = x ^ 2 * 0.3 ;
5257
5358julia> @btime g ($ dyn_uni);
54- 10.051 ns (0 allocations: 0 bytes)
59+ 1.791 ns (0 allocations: 0 bytes)
5560
5661julia> @btime g ($ unitful);
57- 2.000 ns (0 allocations: 0 bytes)
62+ 1.500 ns (0 allocations: 0 bytes)
5863```
5964
6065While both of these are type stable,
@@ -147,16 +152,113 @@ julia> ustrip(x)
1471520.2
148153```
149154
150- ### Unitful
155+ ### Constants
156+
157+ There are a variety of physical constants accessible
158+ via the ` Constants ` submodule:
159+
160+ ``` julia
161+ julia> Constants. c
162+ 2.99792458e8 m s⁻¹
163+ ```
164+
165+ These can also be used inside the ` u"..." ` macro:
166+
167+ ``` julia
168+ julia> u " Constants.c * Hz"
169+ 2.99792458e8 m s⁻²
170+ ```
171+
172+ For the full list, see the [ docs] ( https://symbolicml.org/DynamicQuantities.jl/dev/constants/ ) .
173+
174+
175+ ### Symbolic Units
176+
177+ You can also choose to not eagerly convert to SI base units,
178+ instead leaving the units as the user had written them.
179+ For example:
180+
181+ ``` julia
182+ julia> q = 100 us " cm * kPa"
183+ 100.0 cm kPa
184+
185+ julia> q^ 2
186+ 10000.0 cm² kPa²
187+ ```
188+
189+ You can convert to regular SI base units with
190+ ` uexpand ` :
191+
192+ ``` julia
193+ julia> uexpand (q^ 2 )
194+ 1.0e6 kg² s⁻⁴
195+ ```
196+
197+ This also works with constants:
198+
199+ ``` julia
200+ julia> x = us " Constants.c * Hz"
201+ 1.0 Hz c
202+
203+ julia> x^ 2
204+ 1.0 Hz² c²
205+
206+ julia> uexpand (x^ 2 )
207+ 8.987551787368176e16 m² s⁻⁴
208+ ```
209+
210+ You can also convert a quantity in regular base SI units to symbolic units with ` uconvert ` :
211+ ``` julia
212+ julia> uconvert (us " nm" , 5e-9 u " m" ) # can also write 5e-9u"m" |> uconvert(us"nm")
213+ 5.0 nm
214+ ```
215+
216+ ### Arrays
151217
152- DynamicQuantities works with quantities that are exclusively
153- represented by their SI base units. This gives us type stability
154- and greatly improves performance.
218+ For working with an array of quantities that have the same dimensions,
219+ you can use a ` QuantityArray ` :
220+
221+ ``` julia
222+ julia> ar = QuantityArray (rand (3 ), u " m/s" )
223+ 3 - element QuantityArray (:: Vector{Float64} , :: Quantity{Float64, Dimensions{DynamicQuantities.FixedRational{Int32, 25200}}} ):
224+ 0.2729202669351497 m s⁻¹
225+ 0.992546340360901 m s⁻¹
226+ 0.16863543422972482 m s⁻¹
227+ ```
155228
156- However, performing calculations with physical dimensions
157- is actually equivalent to working with a standardized unit system.
158- Thus, you can use Unitful to parse units,
159- and then use the DynamicQuantities->Unitful extension for conversion:
229+ This ` QuantityArray ` is a subtype ` <:AbstractArray{Quantity{Float64,Dimensions{...}},1} ` ,
230+ meaning that indexing a specific element will return a ` Quantity ` :
231+
232+ ``` julia
233+ julia> ar[2 ]
234+ 0.992546340360901 m s⁻¹
235+
236+ julia> ar[2 ] *= 2
237+ 1.985092680721802 m s⁻¹
238+
239+ julia> ar[2 ] += 0.5 u " m/s"
240+ 2.485092680721802 m s⁻¹
241+ ```
242+
243+ This also has a custom broadcasting interface which
244+ allows the compiler to avoid redundant dimension calculations,
245+ relative to if you had simply used an array of quantities:
246+
247+ ``` julia
248+ julia> f (v) = v^ 2 * 1.5 ;
249+
250+ julia> @btime $ f .(xa) setup= (xa = randn (100000 ) .* u " km/s" );
251+ 109.500 μs (2 allocations: 3.81 MiB)
252+
253+ julia> @btime $ f .(qa) setup= (xa = randn (100000 ) .* u " km/s" ; qa = QuantityArray (xa));
254+ 50.917 μs (3 allocations: 781.34 KiB)
255+ ```
256+
257+ So we can see the ` QuantityArray ` version saves on both time and memory.
258+
259+ ### Unitful
260+
261+ DynamicQuantities allows you to convert back and forth from Unitful.jl:
160262
161263``` julia
162264julia> using Unitful: Unitful, @u_str ; import DynamicQuantities
@@ -180,28 +282,28 @@ true
180282## Types
181283
182284Both a ` Quantity ` 's values and dimensions are of arbitrary type.
183- By default, dimensions are stored as a ` DynamicQuantities. FixedRational{Int32,C}`
184- object, which represents a rational number
285+ By default, dimensions are stored as a ` Dimensions{ FixedRational{Int32,C} }`
286+ object, whose exponents are stored as rational numbers
185287with a fixed denominator ` C ` . This is much faster than ` Rational ` .
186288
187289``` julia
188290julia> typeof (0.5 u " kg" )
189- Quantity{Float64, FixedRational{Int32, 25200 }
291+ Quantity{Float64, Dimensions{ FixedRational{Int32, 25200 }} }
190292```
191293
192294You can change the type of the value field by initializing with a value
193295explicitly of the desired type.
194296
195297``` julia
196298julia> typeof (Quantity (Float16 (0.5 ), mass= 1 , length= 1 ))
197- Quantity{Float16, FixedRational{Int32, 25200 }}
299+ Quantity{Float16, Dimensions{ FixedRational{Int32, 25200 } }}
198300```
199301
200302or by conversion:
201303
202304``` julia
203305julia> typeof (convert (Quantity{Float16}, 0.5 u " m/s" ))
204- Quantity{Float16, DynamicQuantities . FixedRational{Int32, 25200 }}
306+ Quantity{Float16, Dimensions{ FixedRational{Int32, 25200 } }}
205307```
206308
207309For many applications, ` FixedRational{Int8,6} ` will suffice,
@@ -213,9 +315,9 @@ the type you wish to use as the second argument to `Quantity`:
213315``` julia
214316julia> using DynamicQuantities
215317
216- julia> R8 = DynamicQuantities. FixedRational{Int8,6 };
318+ julia> R8 = Dimensions{ DynamicQuantities. FixedRational{Int8,6 } };
217319
218- julia> R32 = DynamicQuantities. FixedRational{Int32,2 ^ 4 * 3 ^ 2 * 5 ^ 2 * 7 }; # Default
320+ julia> R32 = Dimensions{ DynamicQuantities. FixedRational{Int32,2 ^ 4 * 3 ^ 2 * 5 ^ 2 * 7 } }; # Default
219321
220322julia> q8 = [Quantity (randn (), R8, length= rand (- 2 : 2 )) for i in 1 : 1000 ];
221323
@@ -229,30 +331,3 @@ julia> @btime f($q8);
229331julia> @btime f ($ q32);
230332 8.417 μs (2 allocations: 39.11 KiB)
231333```
232-
233- ## Vectors
234-
235- There is not a separate class for vectors, but you can create units
236- like so:
237-
238- ``` julia
239- julia> randn (5 ) .* u " m/s"
240- 5 - element Vector{Quantity{Float64, DynamicQuantities. FixedRational{Int32, 25200 }}}:
241- 1.1762086954956399 m s⁻¹
242- 1.320811324040591 m s⁻¹
243- 0.6519033652437799 m s⁻¹
244- 0.7424822374423569 m s⁻¹
245- 0.33536928068133726 m s⁻¹
246- ```
247-
248- Because it is type stable, you can have mixed units in a vector too:
249-
250- ``` julia
251- julia> v = [Quantity (randn (), mass= rand (0 : 5 ), length= rand (0 : 5 )) for _= 1 : 5 ]
252- 5 - element Vector{Quantity{Float64, DynamicQuantities. FixedRational{Int32, 25200 }}}:
253- 0.4309293892461158 kg⁵
254- 1.415520139801276
255- 1.2179414706524276 m³ kg⁴
256- - 0.18804207255117408 m³ kg⁵
257- 0.52123911329638 m³ kg²
258- ```
0 commit comments