Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package extension for Unitful.jl #28

Merged
merged 9 commits into from
Oct 20, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]

### Added
- A package extension for interoperability with [Unitful.jl] (only supported on julia 1.9 and up).
It provides support for wedge products for `Quantity`, including `Quantity{<:Real}` and
`Quantity{<:AbstractCliffordNumber}` (geometric products were already supported).
- `nonzero_grades(::Complex)` is defined and returns `0:0`, like `nonzero_grades(::Real)`.
- `print(::IO, ::AbstractCliffordNumber)` shows a prettier (but not parseable) representation.

Expand Down Expand Up @@ -128,3 +131,4 @@ Initial release of CliffordNumbers.jl
[0.1.2]: https://github.com/brainandforce/CliffordNumbers.jl/releases/tag/v0.1.2
[0.1.1]: https://github.com/brainandforce/CliffordNumbers.jl/releases/tag/v0.1.1
[0.1.0]: https://github.com/brainandforce/CliffordNumbers.jl/releases/tag/v0.1.0
[Unitful.jl]: https://github.com/PainterQubits/Unitful.jl
10 changes: 9 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ uuid = "3998ac73-6bd4-4031-8035-f167dd3ed523"
authors = ["Brandon Flores"]
version = "0.1.9-dev"

[weakdeps]
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[extensions]
CliffordNumbersUnitfulExt = "Unitful"

[compat]
Aqua = "0.8"
Test = "1"
Unitful = "1"
julia = "1.8"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[targets]
test = ["Aqua", "Test"]
test = ["Aqua", "Test", "Unitful"]
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ makedocs(;
"Indexing" => "indexing.md",
"Operations" => "operations.md",
"Performance tips" => "performance.md",
"Extensions" => "extensions.md",
"API" => Any[
"CliffordNumbers" => "api/clifford.md",
"Indexing" => "api/indexing.md",
Expand Down
37 changes: 37 additions & 0 deletions docs/src/extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Extensions

CliffordNumbers.jl provides some extensions to allow for interoperability with other packages which
we anticipate to be commonly used alongside it.

!!! note
Extensions require Julia 1.9; support for previous versions will be dropped with the 0.2.0
release of CliffordNumbers.jl.

## [Unitful.jl]

[Unitful.jl] provides support for quantities with associated units.

To make a Clifford number into a unitful quantity, simply multiply it by a unit or other unitful
quantity:
```julia-repl
julia> KVector{1, VGA(3)}(1, 2, 3)u"m"
(1e₁ + 2e₂ + 3e₃) m

julia> KVector{1, VGA(3)}(1, 2, 3) * 1.5u"m"
(1.5e₁ + 3.0e₂ + 4.5e₃) m
```

!!! warn "Constructing Clifford numbers from `Quantity` coefficients fails"
Attempts to construct a Clifford number from `Quantity` objects will fail, because `Quantity`
does not subtype `Real` or `Complex`:
```julia-repl
julia> KVector{1,VGA(3)}(1u"m", 2u"m", 3u"m")
ERROR: ArgumentError:
...
```
For this reason, Clifford numbers with mixed units cannot be constructed, and this is
intentional: `AbstractCliffordNumber` assumes an orthonormal basis.

Supported operations include the geometric product and wedge product.

[Unitful.jl]: https://github.com/PainterQubits/Unitful.jl
74 changes: 74 additions & 0 deletions ext/CliffordNumbersUnitfulExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module CliffordNumbersUnitfulExt

using CliffordNumbers
using Unitful

import CliffordNumbers: ∧, ∨, ⨼, ⨽, dot, ×, ⨰
import Unitful: AbstractQuantity, AffineError, AffineQuantity

#---Constructors-----------------------------------------------------------------------------------#

# Error thrown when units are incommensurate
function (::Type{<:AbstractCliffordNumber})(::Tuple{Vararg{Quantity}})
throw(

Check warning on line 13 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L12-L13

Added lines #L12 - L13 were not covered by tests
ArgumentError(
"\n$(Quantity) does not subtype $(Real) or $(Complex), so it cannot be used as the " *
"scalar type for $(AbstractCliffordNumber) instances.\n" *
"Additionally, mixed units are disallowed because $(AbstractCliffordNumber) " *
"currently assumes an orthonormal basis."
)
)
end

# Error thrown when units are commensurate
function (::Type{C})(x::Tuple{Vararg{Quantity{<:Number,D,U}}}) where {C<:AbstractCliffordNumber,D,U}
unit_string = "u\"" * replace(repr(unit(first(x))), " " => " * ") * "\""
multivector_string = "$C$(ustrip.(x))"
throw(

Check warning on line 27 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L24-L27

Added lines #L24 - L27 were not covered by tests
ArgumentError(
"\n$(Quantity) does not subtype $(Real) or $(Complex), so it cannot be used as the " *
"scalar type for $(AbstractCliffordNumber) instances.\n" *
"Instead, construct a $(Quantity{<:C}) by multiplying a $C by a unit:\n" *
"\n " * multivector_string * unit_string * "\n"
)
)
end

(::Type{C})(x::Quantity...) where C<:AbstractCliffordNumber = C(x)

Check warning on line 37 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L37

Added line #L37 was not covered by tests

#---Fixing printing methods------------------------------------------------------------------------#

# Probably not needed, but included anyway
Unitful.BracketStyle(::Type{<:AbstractCliffordNumber}) = Unitful.RoundBrackets()

Check warning on line 42 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L42

Added line #L42 was not covered by tests

function Base.print(io::IO, x::AbstractQuantity{<:AbstractCliffordNumber})

Check warning on line 44 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L44

Added line #L44 was not covered by tests
# Functions with qualified names are part of the internal Unitful API.
# This should be fine for now, but may break unexpectedly.
if unit(x) isa Unitful.Units{()}
print(io, x.val, " (no units)")

Check warning on line 48 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L47-L48

Added lines #L47 - L48 were not covered by tests
else
print(io, '(', x.val, ')')

Check warning on line 50 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L50

Added line #L50 was not covered by tests
# This one is based on SI convention and should not change. I hope.
Unitful.has_unit_spacing(unit(x)) && print(io, ' ')
print(io, unit(x))

Check warning on line 53 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L52-L53

Added lines #L52 - L53 were not covered by tests
end
end

Base.show(io::IO, ::MIME"text/plain", x::Quantity{<:AbstractCliffordNumber}) = print(io, x)

Check warning on line 57 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L57

Added line #L57 was not covered by tests

#---Definitions for different products-------------------------------------------------------------#
∧(x::AbstractQuantity, y::AbstractQuantity) = Quantity(x.val ∧ y.val, unit(x) * unit(y))

Check warning on line 60 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L60

Added line #L60 was not covered by tests

function ∧(x::Number, y::AbstractQuantity)
y isa AffineQuantity &&

Check warning on line 63 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L62-L63

Added lines #L62 - L63 were not covered by tests
throw(AffineError("an invalid operation was attempted with affine quantities: $x ∧ $y"))
return Quantity(x ∧ y.val, unit(y))

Check warning on line 65 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L65

Added line #L65 was not covered by tests
end

function ∧(x::AbstractQuantity, y::Number)
x isa AffineQuantity &&

Check warning on line 69 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L68-L69

Added lines #L68 - L69 were not covered by tests
throw(AffineError("an invalid operation was attempted with affine quantities: $x ∧ $y"))
return Quantity(x.val ∧ y, unit(x))

Check warning on line 71 in ext/CliffordNumbersUnitfulExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/CliffordNumbersUnitfulExt.jl#L71

Added line #L71 was not covered by tests
end

end
20 changes: 20 additions & 0 deletions test/ext/Unitful.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@testset "Unitful extension" begin
x = KVector{1,VGA(3)}(1, 0, 0)
y = KVector{1,VGA(3)}(0, 1, 0)
@test_throws ArgumentError KVector{1,VGA(3)}(1u"m", 0u"m", 0u"m")
@test_throws ArgumentError KVector{1,VGA(3)}(0u"m", 1u"m/s", 0u"K")
xu = x*u"m"
yu = y*u"m"
@test xu ∧ yu === KVector{2,VGA(3)}(1, 0, 0) * u"m^2"
@test 2 ∧ xu === KVector{1,VGA(3)}(2, 0, 0) * u"m"
@test yu ∧ 2 === KVector{1,VGA(3)}(0, 2, 0) * u"m"
@test_throws Unitful.AffineError 2 ∧ 2u"°F"
@test_throws Unitful.AffineError 2u"°F" ∧ 2
# Printing
@test Unitful.BracketStyle(x) === Unitful.RoundBrackets()
@test string(xu) == "(1e₁) m"
@test string(xu ∧ yu) == "(1e₁e₂) m^2"
unitless = Quantity{KVector{1,VGA(3),Int,3}, NoDims, Unitful.FreeUnits{(), NoDims, nothing}}(y)
@test string(unitless) == "1e₂ (no units)"
@test repr("text/plain", unitless) == string(unitless)
end
5 changes: 4 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using CliffordNumbers
using Aqua, Test
using Aqua, Test, Unitful

Aqua.test_all(CliffordNumbers; unbound_args = false)

Expand All @@ -15,4 +15,7 @@ Aqua.test_all(CliffordNumbers; unbound_args = false)
include("indexing.jl")
include("conversion.jl")
include("operations.jl")
if VERSION >= v"1.9.0"
include("ext/Unitful.jl")
end
end
Loading