Skip to content

Commit edca829

Browse files
authored
Merge pull request #22 from SymbolicML/units
Create in-house units module
2 parents f755052 + 37de814 commit edca829

File tree

11 files changed

+444
-73
lines changed

11 files changed

+444
-73
lines changed

README.md

Lines changed: 65 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ This is done to allow for calculations where physical dimensions are not known a
1414

1515
- [Performance](#performance)
1616
- [Usage](#usage)
17-
- [Units](#units)
1817
- [Types](#types)
1918
- [Vectors](#vectors)
2019

@@ -26,8 +25,8 @@ when the compiler cannot infer dimensions in a function:
2625
```julia
2726
julia> using BenchmarkTools, DynamicQuantities; import Unitful
2827

29-
julia> dyn_uni = Quantity(0.2, mass=1, length=0.5, amount=3)
30-
0.2 𝐋 ¹ᐟ² 𝐌 ¹ 𝐍 ³
28+
julia> dyn_uni = 0.2u"m^0.5 * kg * mol^3"
29+
0.2 m¹ᐟ² kg mol³
3130

3231
julia> unitful = convert(Unitful.Quantity, dyn_uni)
3332
0.2 kg m¹ᐟ² mol³
@@ -64,56 +63,71 @@ to units and the compiler can optimize away units from the code.
6463

6564
## Usage
6665

67-
You can create a `Quantity` object with a value and keyword arguments for the powers of the physical dimensions
68-
(`mass`, `length`, `time`, `current`, `temperature`, `luminosity`, `amount`):
66+
You can create a `Quantity` object
67+
by using the convenience macro `u"..."`:
6968

7069
```julia
71-
julia> x = Quantity(0.3, mass=1, length=0.5)
72-
0.3 𝐋 ¹ᐟ² 𝐌 ¹
70+
julia> x = 0.3u"km/s"
71+
300.0 m s⁻¹
72+
73+
julia> y = 42 * u"kg"
74+
42.0 kg
75+
76+
julia> room_temp = 100u"kPa"
77+
100000.0 m⁻¹ kg s⁻²
78+
```
79+
80+
This supports a wide range of SI base and derived units, with common
81+
prefixes.
82+
83+
You can also construct values explicitly with the `Quantity` type,
84+
with a value and keyword arguments for the powers of the physical dimensions
85+
(`mass`, `length`, `time`, `current`, `temperature`, `luminosity`, `amount`):
7386

74-
julia> y = Quantity(10.2, mass=2, time=-2)
75-
10.2 𝐌 ² 𝐓 ⁻²
87+
```julia
88+
julia> x = Quantity(300.0, length=1, time=-1)
89+
300.0 m s⁻¹
7690
```
7791

78-
Elementary calculations with `+, -, *, /, ^, sqrt, cbrt` are supported:
92+
Elementary calculations with `+, -, *, /, ^, sqrt, cbrt, abs` are supported:
7993

8094
```julia
8195
julia> x * y
82-
3.0599999999999996 𝐋 ¹ᐟ² 𝐌 ³ 𝐓 ⁻²
96+
12600.0 m kg s⁻¹
8397

8498
julia> x / y
85-
0.029411764705882353 𝐋 ¹ᐟ² 𝐌 ⁻¹ 𝐓 ²
99+
7.142857142857143 m kg⁻¹ s⁻¹
86100

87101
julia> x ^ 3
88-
0.027 𝐋 ³ᐟ² 𝐌 ³
102+
2.7e7 m³ s⁻³
89103

90104
julia> x ^ -1
91-
3.3333333333333335 𝐋 ⁻¹ᐟ² 𝐌 ⁻¹
105+
0.0033333333333333335 m⁻¹ s
92106

93107
julia> sqrt(x)
94-
0.5477225575051661 𝐋 ¹ᐟ⁴ 𝐌 ¹ᐟ²
108+
17.320508075688775 m¹ᐟ² s⁻¹ᐟ²
95109

96110
julia> x ^ 1.5
97-
0.1643167672515498 𝐋 ³ᐟ⁴ 𝐌 ³ᐟ²
111+
5196.152422706632 m³ᐟ² s⁻³ᐟ²
98112
```
99113

100-
Each of these values has the same type, thus obviating the need for type inference at runtime.
114+
Each of these values has the same type, which means we don't need to perform type inference at runtime.
101115

102116
Furthermore, we can do dimensional analysis by detecting `DimensionError`:
103117

104118
```julia
105119
julia> x + 3 * x
106-
1.2 𝐋 ¹ᐟ² 𝐌 ¹
120+
1.2 m¹ᐟ² kg
107121

108122
julia> x + y
109-
ERROR: DimensionError: 0.3 𝐋 ¹ᐟ² 𝐌 ¹ and 10.2 𝐌 ² 𝐓 ⁻² have different dimensions
123+
ERROR: DimensionError: 0.3 m¹ᐟ² kg and 10.2 kg² s⁻² have incompatible dimensions
110124
```
111125

112126
The dimensions of a `Quantity` can be accessed either with `dimension(quantity)` for the entire `Dimensions` object:
113127

114128
```julia
115129
julia> dimension(x)
116-
𝐋 ¹ᐟ² 𝐌 ¹
130+
m¹ᐟ² kg
117131
```
118132

119133
or with `umass`, `ulength`, etc., for the various dimensions:
@@ -133,26 +147,28 @@ julia> ustrip(x)
133147
0.2
134148
```
135149

136-
## Units
150+
### Unitful
151+
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.
137155

138-
DynamicQuantities works with quantities which store physical dimensions and a value,
139-
and does not directly provide a unit system.
140156
However, performing calculations with physical dimensions
141157
is actually equivalent to working with a standardized unit system.
142158
Thus, you can use Unitful to parse units,
143159
and then use the DynamicQuantities->Unitful extension for conversion:
144160

145161
```julia
146-
julia> using Unitful: Unitful, @u_str
162+
julia> using Unitful: Unitful, @u_str; import DynamicQuantities
147163

148164
julia> x = 0.5u"km/s"
149165
0.5 km s⁻¹
150166

151167
julia> y = convert(DynamicQuantities.Quantity, x)
152-
500.0 𝐋 ¹ 𝐓 ⁻¹
168+
500.0 m s⁻¹
153169

154170
julia> y2 = y^2 * 0.3
155-
75000.0 𝐋 ² 𝐓 ⁻²
171+
75000.0 m² s⁻²
156172

157173
julia> x2 = convert(Unitful.Quantity, y2)
158174
75000.0 m² s⁻²
@@ -163,24 +179,31 @@ true
163179

164180
## Types
165181

166-
Both the `Quantity`'s values and dimensions are of arbitrary type.
182+
Both a `Quantity`'s values and dimensions are of arbitrary type.
167183
By default, dimensions are stored as a `DynamicQuantities.FixedRational{Int32,C}`
168184
object, which represents a rational number
169185
with a fixed denominator `C`. This is much faster than `Rational`.
170186

171187
```julia
172-
julia> typeof(Quantity(0.5, mass=1))
188+
julia> typeof(0.5u"kg")
173189
Quantity{Float64, FixedRational{Int32, 25200}
174190
```
175191
176192
You can change the type of the value field by initializing with a value
177-
of the desired type.
193+
explicitly of the desired type.
178194
179195
```julia
180196
julia> typeof(Quantity(Float16(0.5), mass=1, length=1))
181197
Quantity{Float16, FixedRational{Int32, 25200}}
182198
```
183199
200+
or by conversion:
201+
202+
```julia
203+
julia> typeof(convert(Quantity{Float16}, 0.5u"m/s"))
204+
Quantity{Float16, DynamicQuantities.FixedRational{Int32, 25200}}
205+
```
206+
184207
For many applications, `FixedRational{Int8,6}` will suffice,
185208
and can be faster as it means the entire `Dimensions`
186209
struct will fit into 64 bits.
@@ -213,23 +236,23 @@ There is not a separate class for vectors, but you can create units
213236
like so:
214237
215238
```julia
216-
julia> randn(5) .* Dimensions(mass=2/5, length=2)
217-
5-element Vector{Quantity{Float64, FixedRational{Int32, 25200}}}:
218-
-0.6450221578668845 𝐋 ² 𝐌 ²ᐟ⁵
219-
0.4024829670050946 𝐋 ² 𝐌 ²ᐟ⁵
220-
0.21478863605789672 𝐋 ² 𝐌 ²ᐟ⁵
221-
0.0719774550969669 𝐋 ² 𝐌 ²ᐟ⁵
222-
-1.4231241943420674 𝐋 ² 𝐌 ²ᐟ⁵
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⁻¹
223246
```
224247
225248
Because it is type stable, you can have mixed units in a vector too:
226249
227250
```julia
228251
julia> v = [Quantity(randn(), mass=rand(0:5), length=rand(0:5)) for _=1:5]
229-
5-element Vector{Quantity{Float64, FixedRational{Int32, 25200}}}:
230-
2.2054411324716865 𝐌 ³
231-
-0.01603602425887379 𝐋 ⁴ 𝐌 ³
232-
1.4388184352393647
233-
2.382303019892503 𝐋 ² 𝐌 ¹
234-
0.6071392594021706 𝐋 ⁴ 𝐌 ⁴
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²
235258
```

docs/Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3-
DynamicQuantities = "06fc5a27-2a28-4c7c-a15d-362465fb6821"

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using DynamicQuantities
2+
import DynamicQuantities.Units
23
using Documenter
34

45
DocMeta.setdocmeta!(DynamicQuantities, :DocTestSetup, :(using DynamicQuantities); recursive=true)
@@ -26,7 +27,7 @@ open(dirname(@__FILE__) * "/src/index.md", "w") do io
2627
end
2728

2829
makedocs(;
29-
modules=[DynamicQuantities],
30+
modules=[DynamicQuantities, DynamicQuantities.Units],
3031
authors="MilesCranmer <miles.cranmer@gmail.com> and contributors",
3132
repo="https://github.com/SymbolicML/DynamicQuantities.jl/blob/{commit}{path}#{line}",
3233
sitename="DynamicQuantities.jl",

docs/src/api.md

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,91 @@
1-
```@meta
2-
CurrentModule = DynamicQuantities
1+
# Usage
2+
3+
## Types
4+
5+
```@docs
6+
Quantity
7+
Dimensions
8+
```
9+
10+
## Utilities
11+
12+
The two main general utilities for working
13+
with quantities are `ustrip` and `dimension`:
14+
15+
```@docs
16+
ustrip
17+
dimension
318
```
419

5-
# API Reference
20+
### Accessing dimensions
621

7-
API Reference for [DynamicQuantities](https://github.com/SymbolicML/DynamicQuantities.jl).
22+
Utility functions to extract specific dimensions are as follows:
823

9-
```@index
24+
```@docs
25+
ulength
26+
umass
27+
utime
28+
ucurrent
29+
utemperature
30+
uluminosity
31+
uamount
1032
```
1133

1234
```@autodocs
1335
Modules = [DynamicQuantities]
14-
Order = [:type, :function]
15-
```
36+
Pages = ["utils.jl"]
37+
Filter = t -> !(t in [ustrip, dimension, ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount])
38+
```
39+
40+
## Units
41+
42+
The two main functions for working with units are `uparse` and `u_str`:
43+
44+
```@docs
45+
@u_str
46+
uparse
47+
```
48+
49+
### Available units
50+
51+
The base SI units are as follows.
52+
Instead of calling directly, it is recommended to access them via
53+
the `@u_str` macro, which evaluates the expression
54+
in a namespace with all the units available.
55+
56+
```@docs
57+
Units.m
58+
Units.g
59+
Units.s
60+
Units.A
61+
Units.K
62+
Units.cd
63+
Units.mol
64+
```
65+
66+
Several derived SI units are available as well:
67+
68+
```@docs
69+
Units.Hz
70+
Units.N
71+
Units.Pa
72+
Units.J
73+
Units.W
74+
Units.C
75+
Units.V
76+
Units.F
77+
Units.Ω
78+
Units.T
79+
Units.L
80+
Units.bar
81+
```
82+
83+
## Internals
84+
85+
### FixedRational
86+
87+
```@docs
88+
DynamicQuantities.FixedRational
89+
DynamicQuantities.denom
90+
```
91+

src/DynamicQuantities.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ module DynamicQuantities
22

33
export Quantity, Dimensions, DimensionError, ustrip, dimension, valid
44
export ulength, umass, utime, ucurrent, utemperature, uluminosity, uamount
5+
export uparse, @u_str
56

67
include("fixed_rational.jl")
78
include("types.jl")
89
include("utils.jl")
910
include("math.jl")
11+
include("units.jl")
1012

1113
import Requires: @init, @require
14+
import .Units: uparse, @u_str
1215

1316
if !isdefined(Base, :get_extension)
1417
@init @require Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" include("../ext/DynamicQuantitiesUnitfulExt.jl")

src/fixed_rational.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
A rational number with a fixed denominator. Significantly
55
faster than `Rational{T}`, as it never needs to compute
66
the `gcd` apart from when printing.
7+
Access the denominator with `denom(F)` (which converts to `T`).
8+
9+
# Fields
10+
11+
- `num`: numerator of type `T`. The denominator is fixed to the type parameter `den`.
712
"""
813
struct FixedRational{T<:Integer,den} <: Real
914
num::T
@@ -35,14 +40,18 @@ Base.inv(x::F) where {F<:FixedRational} = unsafe_fixed_rational(widemul(denom(F)
3540

3641
Base.:(==)(x::F, y::F) where {F<:FixedRational} = x.num == y.num
3742
Base.iszero(x::FixedRational) = iszero(x.num)
43+
Base.isone(x::F) where {F<:FixedRational} = x.num == denom(F)
3844
Base.isinteger(x::F) where {F<:FixedRational} = iszero(x.num % denom(F))
3945
Base.convert(::Type{F}, x::Integer) where {F<:FixedRational} = unsafe_fixed_rational(x * denom(F), eltype(F), val_denom(F))
4046
Base.convert(::Type{F}, x::Rational) where {F<:FixedRational} = F(x)
47+
Base.convert(::Type{Rational{R}}, x::F) where {R,F<:FixedRational} = Rational{R}(x.num, denom(F))
4148
Base.convert(::Type{Rational}, x::F) where {F<:FixedRational} = Rational{eltype(F)}(x.num, denom(F))
4249
Base.convert(::Type{AF}, x::F) where {AF<:AbstractFloat,F<:FixedRational} = convert(AF, x.num) / convert(AF, denom(F))
4350
Base.round(::Type{T}, x::F) where {T,F<:FixedRational} = div(convert(T, x.num), convert(T, denom(F)), RoundNearest)
51+
Base.promote(x::Integer, y::F) where {F<:FixedRational} = (F(x), y)
52+
Base.promote(x::F, y::Integer) where {F<:FixedRational} = reverse(promote(y, x))
4453
Base.promote(x, y::F) where {F<:FixedRational} = promote(x, convert(Rational, y))
45-
Base.promote(x::F, y) where {F<:FixedRational} = promote(convert(Rational, x), y)
54+
Base.promote(x::F, y) where {F<:FixedRational} = reverse(promote(y, x))
4655
Base.show(io::IO, x::F) where {F<:FixedRational} = show(io, convert(Rational, x))
4756
Base.zero(::Type{F}) where {F<:FixedRational} = unsafe_fixed_rational(0, eltype(F), val_denom(F))
4857

src/math.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ Base.:/(l::Number, r::Dimensions) = Quantity(l, inv(r))
1919
Base.:+(l::Quantity, r::Quantity) = dimension(l) == dimension(r) ? Quantity(l.value + r.value, l.dimensions) : throw(DimensionError(l, r))
2020
Base.:-(l::Quantity, r::Quantity) = dimension(l) == dimension(r) ? Quantity(l.value - r.value, l.dimensions) : throw(DimensionError(l, r))
2121

22-
_pow(l::Dimensions{R}, r::R) where {R} = @map_dimensions(Base.Fix1(*, r), l)
23-
_pow(l::Quantity{T,R}, r::R) where {T,R} = Quantity(l.value^convert(T, r), _pow(l.dimensions, r))
22+
_pow(l::Dimensions, r) = @map_dimensions(Base.Fix1(*, r), l)
23+
_pow(l::Quantity{T}, r) where {T} = Quantity(l.value^r, _pow(l.dimensions, r))
24+
_pow_as_T(l::Quantity{T}, r) where {T} = Quantity(l.value^convert(T, r), _pow(l.dimensions, r))
25+
Base.:^(l::Dimensions{R}, r::Integer) where {R} = _pow(l, r)
2426
Base.:^(l::Dimensions{R}, r::Number) where {R} = _pow(l, tryrationalize(R, r))
25-
Base.:^(l::Quantity{T,R}, r::Number) where {T,R} = _pow(l, tryrationalize(R, r))
27+
Base.:^(l::Quantity{T,R}, r::Integer) where {T,R} = _pow(l, r)
28+
Base.:^(l::Quantity{T,R}, r::Number) where {T,R} = _pow_as_T(l, tryrationalize(R, r))
2629

2730
Base.inv(d::Dimensions) = @map_dimensions(-, d)
2831
Base.inv(q::Quantity) = Quantity(inv(q.value), inv(q.dimensions))

0 commit comments

Comments
 (0)