Skip to content

Commit 8520362

Browse files
authored
Merge branch 'main' into safe-precompile
2 parents cc9694c + cd30223 commit 8520362

22 files changed

+1341
-517
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DynamicQuantities"
22
uuid = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
33
authors = ["MilesCranmer <[email protected]> and contributors"]
4-
version = "0.7.5"
4+
version = "0.8.2"
55

66
[deps]
77
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using DynamicQuantities
2-
import DynamicQuantities.Units
2+
using DynamicQuantities.Units
3+
using DynamicQuantities: constructorof, with_type_parameters, dimension_names
34
using Documenter
45

56
DocMeta.setdocmeta!(DynamicQuantities, :DocTestSetup, :(using DynamicQuantities); recursive=true)

docs/src/examples.md

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ On your chemistry homework, you are faced with the following problem on the phot
1010
> The energy of the incident UV light is ``7.2 \cdot 10^{-19} \mathrm{J}`` per photon. Calculate the wavelength of the ejected electrons, in nanometers.
1111
1212
Let's solve this problem with `DynamicQuantities.jl`!
13+
1314
```jldoctest examples
1415
julia> using DynamicQuantities
1516
@@ -30,6 +31,356 @@ julia> λ = h / p # wavelength of ejected electrons
3031
julia> uconvert(us"nm", λ) # return answer in nanometers
3132
3.0294912478780556 nm
3233
```
34+
3335
Since units are automatically propagated, we can verify the dimension of our answer and all intermediates.
3436
Also, using `DynamicQuantities.Constants`, we were able to obtain the (dimensionful!) values of all necessary constants without typing them ourselves.
3537

38+
## 2. Projectile motion
39+
40+
Let's solve a simple projectile motion problem.
41+
First load the `DynamicQuantities` module:
42+
43+
```julia
44+
using DynamicQuantities
45+
```
46+
47+
Set up initial conditions as quantities:
48+
49+
```julia
50+
y0 = 10u"km"
51+
v0 = 250u"m/s"
52+
θ = deg2rad(60)
53+
g = 9.81u"m/s^2"
54+
```
55+
56+
Next, we use trig functions to calculate x and y components of initial velocity.
57+
`vx0` is the x component and
58+
`vy0` is the y component:
59+
60+
```julia
61+
vx0 = v0 * cos(θ)
62+
vy0 = v0 * sin(θ)
63+
```
64+
65+
Next, let's create a time vector from 0 seconds to 1.3 minutes.
66+
Note that these are the same dimension (time), so it's fine to treat
67+
them as dimensionally equivalent!
68+
69+
```julia
70+
t = range(0u"s", 1.3u"min", length=100)
71+
```
72+
73+
Next, use kinematic equations to calculate x and y as a function of time.
74+
`x(t)` is the x position at time t, and
75+
`y(t)` is the y position:
76+
77+
```julia
78+
x(t) = vx0*t
79+
y(t) = vy0*t - 0.5*g*t^2 + y0
80+
```
81+
82+
These are functions, so let's evaluate them:
83+
84+
```julia
85+
x_si = x.(t)
86+
y_si = y.(t)
87+
```
88+
89+
These are regular vectors of quantities
90+
with `Dimensions` for physical dimensions.
91+
92+
Next, let's plot the trajectory.
93+
First convert to km and strip units:
94+
95+
```julia
96+
x_km = ustrip.(uconvert(us"km").(x_si))
97+
y_km = ustrip.(uconvert(us"km").(y_si))
98+
```
99+
100+
Now, we plot:
101+
102+
```julia
103+
plot(x_km, y_km, label="Trajectory", xlabel="x [km]", ylabel="y [km]")
104+
```
105+
106+
## 3. Various Simple Examples
107+
108+
This section demonstrates miscellaneous examples of using `DynamicQuantities.jl`.
109+
110+
### Conversion
111+
112+
Convert a quantity to have a new type for the value:
113+
114+
```julia
115+
quantity = 1.5u"m"
116+
117+
convert_q = Quantity{Float32}(quantity)
118+
119+
println("Converted Quantity to Float32: ", convert_q)
120+
```
121+
122+
### Array basics
123+
124+
Create a `QuantityArray` (an array of quantities with
125+
the same dimension) by passing an array and a single quantity:
126+
127+
```julia
128+
x = QuantityArray(randn(32), u"km/s")
129+
```
130+
131+
or, by passing an array of individual quantities:
132+
133+
```julia
134+
y = randn(32)
135+
y_q = QuantityArray(y .* u"m * cd / s")
136+
```
137+
138+
We can take advantage of this being `<:AbstractArray`:
139+
140+
```julia
141+
println("Sum x: ", sum(x))
142+
```
143+
144+
We can also do things like setting a particular element:
145+
146+
```julia
147+
y_q[5] = Quantity(5, length=1, luminosity=1, time=-1)
148+
println("5th element of y_q: ", y_q[5])
149+
```
150+
151+
We can get back the original array with `ustrip`:
152+
153+
```julia
154+
println("Stripped y_q: ", ustrip(y_q))
155+
```
156+
157+
This `QuantityArray` is useful for broadcasting:
158+
159+
```julia
160+
f_square(v) = v^2 * 1.5 - v^2
161+
println("Applying function to y_q: ", sum(f_square.(y_q)))
162+
```
163+
164+
### Fill
165+
166+
We can also make `QuantityArray` using `fill`:
167+
168+
```julia
169+
filled_q = fill(u"m/s", 10)
170+
println("Filled QuantityArray: ", filled_q)
171+
```
172+
173+
`fill` works for 0 dimensional `QuantityArray`s as well:
174+
175+
```julia
176+
empty_q = fill(u"m/s", ())
177+
println("0 dimensional QuantityArray: ", empty_q)
178+
```
179+
180+
### Similar
181+
182+
Likewise, we can create a `QuantityArray` with the same properties as another `QuantityArray`:
183+
184+
```julia
185+
qa = QuantityArray(rand(3, 4), u"m")
186+
187+
new_qa = similar(qa)
188+
189+
println("Similar qa: ", new_qa)
190+
```
191+
192+
### Promotion
193+
194+
Promotion rules are defined for `QuantityArray`s:
195+
196+
```julia
197+
qarr1 = QuantityArray(randn(32), convert(Dimensions{Rational{Int32}}, dimension(u"km/s")))
198+
qarr2 = QuantityArray(randn(Float16, 32), convert(Dimensions{Rational{Int64}}, dimension(u"km/s")))
199+
```
200+
201+
See what type they promote to:
202+
203+
```julia
204+
println("Promoted type: ", typeof(promote(qarr1, qarr2)))
205+
```
206+
207+
### Array Concatenation
208+
209+
Likewise, we can take advantage of array concatenation,
210+
which will ensure we have the same dimensions:
211+
212+
```julia
213+
qarr1 = QuantityArray(randn(3) .* u"km/s")
214+
qarr2 = QuantityArray(randn(3) .* u"km/s")
215+
```
216+
217+
Concatenate them:
218+
219+
```julia
220+
concat_qarr = hcat(qarr1, qarr2)
221+
println("Concatenated QuantityArray: ", concat_qarr)
222+
```
223+
224+
### Symbolic Units
225+
226+
We can use arbitrary `AbstractQuantity` and `AbstractDimensions`
227+
in a `QuantityArray`, including `SymbolicDimensions`:
228+
229+
```julia
230+
z_ar = randn(32)
231+
z = QuantityArray(z_ar, us"Constants.M_sun * km/s")
232+
```
233+
234+
Expand to standard units:
235+
236+
```julia
237+
z_expanded = uexpand(z)
238+
println("Expanded z: ", z_expanded)
239+
```
240+
241+
242+
### GenericQuantity Construction
243+
244+
In addition to `Quantity`, we can also use `GenericQuantity`:
245+
246+
247+
```julia
248+
x = GenericQuantity(1.5)
249+
y = GenericQuantity(0.2u"km")
250+
println(y)
251+
```
252+
253+
This `GenericQuantity` is subtyped to `Any`,
254+
rather than `Number`, and thus can also store
255+
custom non-scalar types.
256+
257+
For example, we can work with `Coords`, and
258+
wrap it in a single `GenericQuantity` type:
259+
260+
```julia
261+
struct Coords
262+
x::Float64
263+
y::Float64
264+
end
265+
266+
# Define arithmetic operations on Coords
267+
Base.:+(a::Coords, b::Coords) = Coords(a.x + b.x, a.y + b.y)
268+
Base.:-(a::Coords, b::Coords) = Coords(a.x - b.x, a.y - b.y)
269+
Base.:*(a::Coords, b::Number) = Coords(a.x * b, a.y * b)
270+
Base.:*(a::Number, b::Coords) = Coords(a * b.x, a * b.y)
271+
Base.:/(a::Coords, b::Number) = Coords(a.x / b, a.y / b)
272+
```
273+
274+
We can then build a `GenericQuantity` out of this:
275+
276+
```julia
277+
coord1 = GenericQuantity(Coords(0.3, 0.9), length=1)
278+
coord2 = GenericQuantity(Coords(0.2, -0.1), length=1)
279+
```
280+
281+
and perform operations on these:
282+
283+
```julia
284+
coord1 + coord2 |> uconvert(us"cm")
285+
# (Coords(50.0, 80.0)) cm
286+
```
287+
288+
The nice part about this is it only stores a single Dimensions
289+
(or `SymbolicDimensions`) for the entire struct!
290+
291+
### GenericQuantity and Quantity Promotion
292+
293+
When we combine a `GenericQuantity` and a `Quantity`,
294+
the result is another `GenericQuantity`:
295+
296+
```julia
297+
x = GenericQuantity(1.5f0)
298+
y = Quantity(1.5, length=1)
299+
println("Promoted type of x and y: ", typeof(x * y))
300+
```
301+
302+
### Custom Dimensions
303+
304+
We can create custom dimensions by subtyping to
305+
`AbstractDimensions`:
306+
307+
```julia
308+
struct MyDimensions{R} <: AbstractDimensions{R}
309+
cookie::R
310+
milk::R
311+
end
312+
```
313+
314+
Many constructors and functions are defined on `AbstractDimensions`,
315+
so this can be used out-of-the-box.
316+
We can then use this in a `Quantity`, and all operations will work as expected:
317+
318+
```julia
319+
x = Quantity(1.5, MyDimensions(cookie=1, milk=-1))
320+
y = Quantity(2.0, MyDimensions(milk=1))
321+
322+
x * y
323+
```
324+
325+
which gives us `3.0 cookie` computed from a rate of `1.5 cookie milk⁻¹` multiplied
326+
by `2.0 milk`. Likewise, we can use these in a `QuantityArray`:
327+
328+
```julia
329+
x_qa = QuantityArray(randn(32), MyDimensions(cookie=1, milk=-1))
330+
331+
x_qa .^ 2
332+
```
333+
334+
### Custom Quantities
335+
336+
We can also create custom dimensions by subtyping
337+
to either `AbstractQuantity` (for `<:Number`) or
338+
`AbstractGenericQuantity` (for `<:Any`):
339+
340+
```julia
341+
struct MyQuantity{T,D} <: AbstractQuantity{T,D}
342+
value::T
343+
dimensions::D
344+
end
345+
```
346+
347+
Since `AbstractQuantity <: Number`, this will also be a number.
348+
Keep in mind that you must call these fields `value` and `dimensions`
349+
for `ustrip(...)` and `dimension(...)` to work. Otherwise, simply
350+
redefine those.
351+
352+
We can use this custom quantity just like we would use `Quantity`:
353+
354+
```julia
355+
q1 = MyQuantity(1.2, Dimensions(length=-2))
356+
# prints as `1.2 m⁻²`
357+
358+
q2 = MyQuantity(1.5, MyDimensions(cookie=1))
359+
# prints as `1.5 cookie`
360+
```
361+
362+
Including mathematical operations:
363+
364+
```julia
365+
q2 ^ 2
366+
# `2.25 cookie²`
367+
```
368+
369+
The main reason you would use a custom quantity is if you want
370+
to change built-in behavior, or maybe have special methods for
371+
different types of quantities.
372+
373+
Note that you can declare a method on `AbstractQuantity`, or
374+
`AbstractGenericQuantity` to allow their respective inputs.
375+
376+
**Note**: In general, you should probably
377+
specialize on `UnionAbstractQuantity` which is
378+
the union of these two abstract quantities, _as well as any other future abstract quantity types_,
379+
such as the planned `AbstractRealQuantity`.
380+
381+
```julia
382+
function my_func(x::UnionAbstractQuantity{T,D}) where {T,D}
383+
# value has type T and dimensions has type D
384+
return x / ustrip(x)
385+
end
386+
```

0 commit comments

Comments
 (0)