Skip to content

Commit 967d58f

Browse files
committed
Merge tag 'v0.7.4' into weak-floats-2
[Diff since v0.7.3](v0.7.3...v0.7.4) **Merged pull requests:** - Add `uconvert` and chemistry example (#48) (@gaurav-arya) - Add `uconvert` method for `QuantityArray` (#61) (@MilesCranmer) - Deprecate `expand_units` -> `uexpand` (#62) (@MilesCranmer)
2 parents a1cfbbb + e5f4305 commit 967d58f

27 files changed

+1431
-456
lines changed

.github/workflows/CI.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ jobs:
3535
- name: "Run tests"
3636
shell: bash
3737
run: |
38-
julia --color=yes --project=. -e 'import Pkg; Pkg.add("Coverage")'
39-
julia --color=yes --threads=auto --check-bounds=yes --depwarn=yes --code-coverage=user --project=. -e 'import Pkg; Pkg.test(coverage=true)'
40-
DQ_TEST_UPREFERRED=true julia --color=yes --threads=auto --check-bounds=yes --depwarn=yes --code-coverage=user --project=. -e 'import Pkg; Pkg.test(coverage=true)'
41-
julia --color=yes --project=. coverage.jl
38+
julia --color=yes -e 'import Pkg; Pkg.add("Coverage")'
39+
julia --color=yes --threads=auto --check-bounds=yes --depwarn=yes --code-coverage=user -e 'import Coverage; import Pkg; Pkg.activate("."); Pkg.test(coverage=true)'
40+
DQ_TEST_UPREFERRED=true julia --color=yes --threads=auto --check-bounds=yes --depwarn=yes --code-coverage=user -e 'import Coverage; import Pkg; Pkg.activate("."); Pkg.test(coverage=true)'
41+
julia --color=yes coverage.jl
4242
- name: "Coveralls"
4343
uses: coverallsapp/github-action@v2
4444
if: matrix.version == '1'

Project.toml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,45 @@
11
name = "DynamicQuantities"
22
uuid = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
33
authors = ["MilesCranmer <miles.cranmer@gmail.com> and contributors"]
4-
version = "0.6.0"
4+
version = "0.7.4"
55

66
[deps]
7-
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
8-
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
7+
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
8+
PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930"
99
Tricks = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775"
1010

1111
[weakdeps]
12+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
13+
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
14+
ScientificTypes = "321657f4-b219-11e9-178b-2701a2544e81"
1215
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
1316

1417
[extensions]
18+
DynamicQuantitiesLinearAlgebraExt = "LinearAlgebra"
19+
DynamicQuantitiesMeasurementsExt = "Measurements"
20+
DynamicQuantitiesScientificTypesExt = "ScientificTypes"
1521
DynamicQuantitiesUnitfulExt = "Unitful"
1622

1723
[compat]
18-
Requires = "1"
24+
Compat = "3.42, 4"
25+
Measurements = "2"
26+
PackageExtensionCompat = "1.0.2"
27+
ScientificTypes = "3"
1928
Tricks = "0.1"
2029
Unitful = "1"
2130
julia = "1.6"
2231

2332
[extras]
33+
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
34+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
35+
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
2436
Ratios = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
2537
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
2638
SaferIntegers = "88634af6-177f-5301-88b8-7819386cfa38"
39+
ScientificTypes = "321657f4-b219-11e9-178b-2701a2544e81"
40+
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2741
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2842
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
2943

3044
[targets]
31-
test = ["Test", "Ratios", "SaferIntegers", "SafeTestsets", "Unitful"]
45+
test = ["Aqua", "LinearAlgebra", "Measurements", "Ratios", "SaferIntegers", "SafeTestsets", "ScientificTypes", "StaticArrays", "Test", "Unitful"]

README.md

Lines changed: 126 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@
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

1112
DynamicQuantities defines a simple statically-typed `Quantity` type for Julia.
1213
Physical 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
2631
julia> using BenchmarkTools, DynamicQuantities; import Unitful
2732

28-
julia> dyn_uni = 0.2u"m^0.5 * kg * mol^3"
29-
0.2 m¹ᐟ² kg mol³
33+
julia> dyn_uni = 0.2u"m/s"
34+
0.2 m s⁻¹
3035

3136
julia> unitful = convert(Unitful.Quantity, dyn_uni)
32-
0.2 kg m¹ᐟ² mol³
37+
0.2 m s⁻¹
3338

3439
julia> f(x, i) = x ^ i * 0.3;
3540

3641
julia> @btime f($dyn_uni, 1);
37-
8.759 ns (0 allocations: 0 bytes)
42+
2.708 ns (0 allocations: 0 bytes)
3843

3944
julia> @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!**
4449
Here, the DynamicQuantities quantity object allows the compiler to build a function that is type stable,
4550
while 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:
5156
julia> g(x) = x ^ 2 * 0.3;
5257

5358
julia> @btime g($dyn_uni);
54-
10.051 ns (0 allocations: 0 bytes)
59+
1.791 ns (0 allocations: 0 bytes)
5560

5661
julia> @btime g($unitful);
57-
2.000 ns (0 allocations: 0 bytes)
62+
1.500 ns (0 allocations: 0 bytes)
5863
```
5964

6065
While both of these are type stable,
@@ -147,16 +152,113 @@ julia> ustrip(x)
147152
0.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 = 100us"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-9u"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.5u"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
162264
julia> using Unitful: Unitful, @u_str; import DynamicQuantities
@@ -180,28 +282,28 @@ true
180282
## Types
181283

182284
Both 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
185287
with a fixed denominator `C`. This is much faster than `Rational`.
186288

187289
```julia
188290
julia> typeof(0.5u"kg")
189-
Quantity{Float64, FixedRational{Int32, 25200}
291+
Quantity{Float64, Dimensions{FixedRational{Int32, 25200}}}
190292
```
191293

192294
You can change the type of the value field by initializing with a value
193295
explicitly of the desired type.
194296

195297
```julia
196298
julia> typeof(Quantity(Float16(0.5), mass=1, length=1))
197-
Quantity{Float16, FixedRational{Int32, 25200}}
299+
Quantity{Float16, Dimensions{FixedRational{Int32, 25200}}}
198300
```
199301

200302
or by conversion:
201303

202304
```julia
203305
julia> typeof(convert(Quantity{Float16}, 0.5u"m/s"))
204-
Quantity{Float16, DynamicQuantities.FixedRational{Int32, 25200}}
306+
Quantity{Float16, Dimensions{FixedRational{Int32, 25200}}}
205307
```
206308

207309
For 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
214316
julia> 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

220322
julia> q8 = [Quantity(randn(), R8, length=rand(-2:2)) for i in 1:1000];
221323

@@ -229,30 +331,3 @@ julia> @btime f($q8);
229331
julia> @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-
```

benchmark/benchmarks.jl

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ using DynamicQuantities
33

44
const SUITE = BenchmarkGroup()
55

6-
SUITE["creation"] = let s = BenchmarkGroup()
6+
SUITE["Quantity"] = BenchmarkGroup()
7+
8+
SUITE["Quantity"]["creation"] = let s = BenchmarkGroup()
79
s["Quantity(x)"] = @benchmarkable Quantity(x) setup = (x = randn()) evals = 1000
810
s["Quantity(x, length=y)"] = @benchmarkable Quantity(x, length=y) setup = (x = randn(); y = rand(1:5)) evals = 1000
911
s
1012
end
1113

1214
default() = Quantity(rand(), length=rand(1:5), mass=rand(1:5) // 2)
1315

14-
SUITE["with_numbers"] = let s = BenchmarkGroup()
16+
SUITE["Quantity"]["with_numbers"] = let s = BenchmarkGroup()
1517
f1(x, i) = x^i
1618
s["^int"] = @benchmarkable $f1(x, i) setup = (x = default(); i = rand(1:5)) evals = 1000
1719
f2(x, y) = x * y
@@ -21,7 +23,7 @@ SUITE["with_numbers"] = let s = BenchmarkGroup()
2123
s
2224
end
2325

24-
SUITE["with_self"] = let s = BenchmarkGroup()
26+
SUITE["Quantity"]["with_self"] = let s = BenchmarkGroup()
2527
f4(x) = inv(x)
2628
s["inv"] = @benchmarkable $f4(x) setup = (x = default()) evals = 1000
2729
f7(x) = ustrip(x)
@@ -31,10 +33,31 @@ SUITE["with_self"] = let s = BenchmarkGroup()
3133
s
3234
end
3335

34-
SUITE["with_quantity"] = let s = BenchmarkGroup()
36+
SUITE["Quantity"]["with_quantity"] = let s = BenchmarkGroup()
3537
f5(x, y) = x / y
3638
s["/y"] = @benchmarkable $f5(x, y) setup = (x = default(); y = default()) evals = 1000
3739
f6(x, y) = x + y
3840
s["+y"] = @benchmarkable $f6(x, y) setup = (x = default(); y = x + rand() * x) evals = 1000
3941
s
4042
end
43+
44+
if @isdefined QuantityArray
45+
SUITE["QuantityArray"] = BenchmarkGroup()
46+
47+
SUITE["QuantityArray"]["broadcasting"] = let s = BenchmarkGroup()
48+
N = 10000
49+
f9(x) = x^2
50+
s["x^2_normal_array"] = @benchmarkable $f9.(arr) setup = (arr = randn($N))
51+
s["x^2_quantity_array"] = @benchmarkable $f9.(arr) setup = (arr = QuantityArray(randn($N), u"km/s"))
52+
s["x^2_array_of_quantities"] = @benchmarkable $f9.(arr) setup = (arr = randn($N) .* u"km/s")
53+
f10(x) = x^4
54+
s["x^4_normal_array"] = @benchmarkable $f10.(arr) setup = (arr = randn($N))
55+
s["x^4_quantity_array"] = @benchmarkable $f10.(arr) setup = (arr = QuantityArray(randn($N), u"km/s"))
56+
s["x^4_array_of_quantities"] = @benchmarkable $f10.(arr) setup = (arr = randn($N) .* u"km/s")
57+
f11(x) = x^4 * 0.9 - x * x / 0.3 * x * 0.9 * x
58+
s["multi_normal_array"] = @benchmarkable $f11.(arr) setup = (arr = randn($N))
59+
s["multi_quantity_array"] = @benchmarkable $f11.(arr) setup = (arr = QuantityArray(randn($N), u"km/s"))
60+
s["multi_array_of_quantities"] = @benchmarkable $f11.(arr) setup = (arr = randn($N) .* u"km/s")
61+
s
62+
end
63+
end

0 commit comments

Comments
 (0)