Quaternions.jl icon indicating copy to clipboard operation
Quaternions.jl copied to clipboard

convenience variables for quaternions friendly suggestion

Open alanedelman opened this issue 1 year ago • 2 comments

iq = Quaternion(0,1,0,0)
jq = Quaternion(0,0,1,0)
kq = Quaternion(0,0,0,1)

alanedelman avatar Dec 17 '24 15:12 alanedelman

Thank you for the suggestion! There was a related issue (#50).

I also think the constants add convenience. To implement the constants, we need to decide the following:

  • Which symbols should we use?
  • Should we update the Base.show(::IO, q::Quaternion)?
    • Yeah, the Base docs indicate that im is convenient syntactic sugar that is probably best used for convenience in the REPL or for defining constants in the code but in general should not be used.

      • https://github.com/JuliaGeometry/Quaternions.jl/issues/50#issuecomment-1046042942
    • I couldn't find documentation explicitly discouraging the use of im. Also, some comments in #50 are outdated. (e.g. Octanion and DualQuaternion are no longer provided in this package.)

hyrodium avatar Jan 07 '25 10:01 hyrodium

I agree that most of the arguments against including these convenience variables no longer apply, so it's worth reconsidering this.

From https://docs.julialang.org/en/v1/manual/complex-and-rational-numbers/#Complex-Numbers:

The [literal numeric coefficient notation](@ref man-numeric-literal-coefficients) does not work when constructing a complex number
from variables. Instead, the multiplication must be explicitly written out:

```jldoctest
julia> a = 1; b = 2; a + b*im
1 + 2im
```

However, this is *not* recommended. Instead, use the more efficient [`complex`](@ref) function to construct
a complex value directly from its real and imaginary parts:

```jldoctest
julia> a = 1; b = 2; complex(a, b)
1 + 2im
```

This construction avoids the multiplication and addition operations.

i.e. it's safe to use im when a and b are constants, e.g. in the REPL or when defining a constant within a function. But otherwise complex should be used.

Which symbols should we use?

* `iq, jq, kq`

* [`im_i, im_j, im_k`](https://github.com/JuliaGeometry/Quaternions.jl/issues/50#issuecomment-1045786440)

* [`imx, imy, imz`](https://moble.github.io/Quaternionic.jl/stable/manual/#Quaternionic.imx)

* etc.

I don't have a strong preference here. Base uses im instead of i, since i is commonly an index. So I don't think we need to use a variant of im, just something that is unlikely to clash with another namespace. We don't use xyz anywhere else, and this is uncommon with quaternions, so I'd say no. Underscores are insufficiently compact. iq, jq, etc is fine. My only annoyance is that we use v1, v2, etc in the fields of Quaternion. I wouldn't like iq1, iq2, etc though.

The following show implementation is based on Complex's. Because the strings are much longer than Complex's, I think it looks nicer to render the bases as bold (while still keeping the unicode as non-bold):

# use Bool to avoid promoting integer types e.g. Int16 to Int64
const iq = Quaternion(false, true, false, false)
const jq = Quaternion(false, false, true, false)
const kq = Quaternion(false, false, false, true)

function Base.show(io::IO, q::Quaternion)
    compact = get(io, :compact, false)::Bool
    show(io, q.s)
    for (v, sym) in zip((q.v1, q.v2, q.v3), (:iq, :jq, :kq))
        if signbit(v) && !isnan(v)
            print(io, compact ? "-" : " - ")
            if isa(v, Signed) && !isa(v, BigInt) && v == typemin(typeof(v))
                show(io, -widen(v))
            else
                show(io, -v)
            end
        else
            print(io, compact ? "+" : " + ")
            show(io, v)
        end
        printstyled(io, sym; bold=true)
    end
end

function  Base.show(io::IO, q::Quaternion{Bool})
    name = findfirst(Base.Fix2(===, q), (; iq, jq, kq))
    if name === nothing
        print(io, "Quaternion(", q.s, ',', q.v1, ',', q.v2, ',', q.v3, ")")
    else
        printstyled(io, name; bold=true)
    end
end
julia> iq
iq

julia> jq
jq

julia> kq
kq

julia> 1+3jq
1 + 0iq + 3jq + 0kq

julia> Quaternion(true, true, false, false)
Quaternion(true,true,false,false)

julia> randn(QuaternionF64, 3)
3-element Vector{QuaternionF64}:
 0.14029180109916273 - 1.1540872532752005iq + 0.1906721473746647jq + 1.2743183122684032kq
  -1.030315293683978 + 0.2523419854373315iq - 0.38537355553418795jq - 1.089803948247486kq
 -1.1036492479688396 - 0.3894607415241461iq - 0.1328394303475304jq - 0.5361253909830082kq

julia> randn(QuaternionF64, 3, 2)
3×2 Matrix{QuaternionF64}:
   0.389246-0.606827iq+0.704692jq+0.205152kq  0.346844+0.412816iq-0.409325jq+0.0666423kq
   0.796425-0.147743iq+0.495668jq-0.237316kq   -1.03524+0.672756iq-0.149115jq-0.199047kq
 -0.326256+0.346335iq-0.165236jq-0.0462912kq  -0.432593+0.138248iq-0.400217jq-0.221615kq

sethaxen avatar Jan 08 '25 08:01 sethaxen