Skip to content

Commit

Permalink
Define helpers for unitful interfaces.
Browse files Browse the repository at this point in the history
This commit adds two simple functions `similar_dims` (and `similar_units`)
that return a `Quantity` type with the dimensions (and units)
constrained to those of the parameters

I found myself trying to write simulation code with strongly typed
interfaces, i.e. including information about the units. Initially I
wrote my interfaces like so:

```julia
const Meters = typeof(1.0m);
circumference_of_circle(r::Meters) = pi*r^2
```

However, when trying to autodiff through this code, I run into a
problem, because `Meters` has the numerical type `Float64` baked in, and
autodiff evaluates on a type `Quantity{Dual{Float64}}` (roughly).

We can instead define `Meters` like so:

```julia
const Meters{T<:Real} = Quantity{T, dimension(1.0m), unit(1.0m)}
circumference_of_circle(r::Meters{T}) where {T} = pi*r^2
circumference_of_circle(r::Quantity{T, dimension(1.0m), unit(1.0m)}) where {T} = pi*r^2
```

but I thought a better approach would be to provide some syntactic sugar
to this "unit constraint".

With this PR, we can write

```julia
circumference_of_circle(r::similar_dims(u"m")) where {T} = pi*r^2
circumference_of_circle(r::similar_units(u"m")) where {T} = pi*r^2
```

The difference is that the first one only constrains the dimension, and
the latter constrains both dimension and unit (i.e. doesn't allow e.g. `km`).

I'm happy to receive any feedback on the idea and the naming.
Other names could be e.g. `quantity_with_dims` (but too long for my
taste), or `dims_as` etc., but `similar` is already Julia lingo and
feels appropriate in this context.
  • Loading branch information
RomeoV committed Nov 20, 2023
1 parent a24bc69 commit 55a5d13
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Unitful.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import LinearAlgebra: istril, istriu, norm
import Random

export logunit, unit, absoluteunit, dimension, uconvert, ustrip, upreferred
export similar_units, similar_dims
export @dimension, @derived_dimension, @refunit, @unit, @affineunit, @u_str
export Quantity, DimensionlessQuantity, NoUnits, NoDims

Expand Down
43 changes: 43 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,49 @@ struct DimensionError <: Exception
y
end

"""
similar_dims(q::Quantity)
similar_dims(u::Units)
Returns a type of [`Unitful.Quantity`](@ref) with the dimensions contrained to the
dimension of `q` or `u`.
Useful to build unitful interfaces that don't contrain the numeric type of the specific unit.
Examples:
```jldoctest
julia> circumference_of_square(side::similar_dims(u"m")) = 4*side;
julia> circumference_of_square((1//2)m) # works
2//1 m
julia> circumference_of_square((1//2)km) # also works
2//1 km
```
See also [`Unitful.similar_units`](@ref).
"""
similar_dims(q::Quantity) = Quantity{T, dimension(q), U} where {T<:Real, U<:Unitlike}
similar_dims(u::Units) = Quantity{T, dimension(u), U} where {T<:Real, U<:Unitlike}

"""
similar_units(q::Quantity)
similar_units(u::Units)
Returns a type of [`Unitful.Quantity`](@ref) with the dimensions and units contrained to the
dimension and units of `q` or `u`.
Useful to build unitful interfaces that don't contrain the numeric type.
Examples:
```jldoctest
julia> circumference_of_square(side::similar_units(u"m")) = 4*side;
julia> circumference_of_square((1//2)m) # works
2//1 m
julia> # circumference_of_square((1//2)km) # doesn't work, constrained to exactly meters
```
See also [`Unitful.similar_dims`](@ref).
"""
similar_units(q::Quantity) = Quantity{T, dimension(q), unit(q)} where {T<:Real}
similar_units(u::Units) = Quantity{T, dimension(u), typeof(u)} where {T<:Real}

Base.showerror(io::IO, e::DimensionError) =
print(io, "DimensionError: $(e.x) and $(e.y) are not dimensionally compatible.");

Expand Down

0 comments on commit 55a5d13

Please sign in to comment.