-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathabstract.jl
291 lines (228 loc) · 12.2 KB
/
abstract.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#---Abstract type for all Clifford numbers---------------------------------------------------------#
"""
AbstractCliffordNumber{Q,T} <: Number
An element of a Clifford algebra, often referred to as a multivector, with quadratic form `Q` and
element type `T`. These are statically size and therefore should be able to be stored inline in
arrays or other data structures.
# Interface
## Required implementation
All subtypes `C` of `AbstractCliffordNumber{Q}` must implement the following functions:
* `CliffordNumbers.similar_type(::Type{C}, ::Type{T}, ::Type{Q}) where {C,T,Q}` should construct a
new type similar to `C` which subtypes `AbstractCliffordNumber{Q,T}` that may serve as a
constructor.
* `Base.getindex(x::C, b::BitIndex{Q})` should allow one to recover the coefficients associated
with each basis blade represented by `C`.
* `nblades(::Type{C})` should be defined to return the number of basis blades represented by the
type. By default, `nblades(x::AbstractCliffordNumber) = nblades(typeof(x))`.
* `Base.Tuple(x::C)` should return the tuple used to construct `x`. The fallback is
`getfield(x, :data)::Tuple`, so any type declared with a `NTuple` field named `data` should have
this defined automatically.
"""
abstract type AbstractCliffordNumber{Q,T<:BaseNumber} <: Number
end
#---AbstractCliffordNumber passthrough constructors------------------------------------------------#
AbstractCliffordNumber(x::AbstractCliffordNumber) = x
function AbstractCliffordNumber(::BaseNumber)
throw(
ArgumentError(
"When constructing a CliffordNumber from a Real or Complex instance, " *
"the algebra type parameter must be specified."
)
)
end
function (::Type{<:AbstractCliffordNumber})(::Number)
throw(
ArgumentError(
"Clifford numbers can only be constructed from Real or Complex arguments.\n" *
"Support for other types may be provided by a package extension."
)
)
end
AbstractCliffordNumber{Q}(x::AbstractCliffordNumber{Q}) where Q = x
# UNDERSTAND: The type constraint on T is required. Why?
AbstractCliffordNumber{Q,T}(x::AbstractCliffordNumber{Q,T}) where {Q,T<:BaseNumber} = x
#---Default varargs constructors for types---------------------------------------------------------#
(::Type{T})(x::Vararg{BaseNumber}) where {Q,T<:AbstractCliffordNumber{Q}} = T(x)
#---Number of blades-------------------------------------------------------------------------------#
"""
nblades(::Type{<:Number}) -> Int
nblades(x::Number)
Returns the number of blades represented by a `Number` subtype or instance. For subtypes of `Number`
that are not `AbstractCliffordNumber`, this is always 1.
This function is separate from `Base.length` since `AbstractCliffordNumber` is a scalar type for
which `collect()` returns a zero-dimensional array. For consistency, `length(x)` should always equal
`length(collect(x))`.
"""
nblades(::Type{<:Number}) = 1
nblades(x::Number) = nblades(typeof(x))
#---Get type parameters----------------------------------------------------------------------------#
"""
signature(T::Type{<:AbstractCliffordNumber{Q}}) = Q
signature(x::AbstractCliffordNumber{Q}) = Q
Returns the metric signature object associated with an `AbstractCliffordNumber` `x` or its type `T`.
"""
signature(::Type{<:AbstractCliffordNumber{Q}}) where Q = Q
signature(::AbstractCliffordNumber{Q}) where Q = Q
"""
scalar_type(::Type{<:AbstractCliffordNumber{Q,T}}) = T
scalar_type(T::Type{<:Union{Real,Complex}}) = T
scalar_type(x) = scalar_type(typeof(x))
Returns the numeric type associated with an `AbstractCliffordNumber` instance. For subtypes of
`Real` and `Complex`, or their instances, this simply returns the input type or instance type.
# Why not define `eltype`?
`AbstractCliffordNumber` instances behave like numbers, not arrays. If `collect()` is called on a
Clifford number of type `T`, it should not construct a vector of coefficients; instead it should
return an `Array{T,0}`. Similarly, a broadcasted multiplication should return the same result as
normal multiplication, as is the case with complex numbers.
For subtypes `T` of `Number`, `eltype(T) === T`, and this is true for `AbstractCliffordNumber`.
"""
scalar_type(::Type) = BaseNumber
scalar_type(::Type{<:AbstractCliffordNumber{Q,T}}) where {Q,T} = T
scalar_type(T::Type{<:BaseNumber}) = T
scalar_type(x) = scalar_type(typeof(x))
#---Get underlying tuple---------------------------------------------------------------------------#
Base.Tuple(x::AbstractCliffordNumber) = getfield(x, :data)::Tuple
#---Additive and multiplicative identities---------------------------------------------------------#
"""
CliffordNumbers.zero_tuple(::Type{T}, ::Val{L}) -> NTuple{L,T}
Generates a `Tuple` of length `L` with all elements being `zero(T)`.
"""
zero_tuple(::Type{T}, ::Val{L}) where {T,L} = ntuple(Returns(zero(T)), Val(L))
"""
CliffordNumbers.zero_tuple(::Type{C<:AbstractCliffordNumber})
-> NTuple{nblades(C),scalar_type(C)}
Generates a `Tuple` that can be used to construct `zero(C)`.
"""
zero_tuple(::Type{C}) where C<:AbstractCliffordNumber = zero_tuple(scalar_type(C), Val(nblades(C)))
zero(::Type{C}) where C<:AbstractCliffordNumber = C(zero_tuple(Bool, Val(nblades(C))))
zero(x::AbstractCliffordNumber) = zero(typeof(x))
# The default defintion assumes oneunit(T) = T(one(x))
# But this doesn't work here, because T(one(x)) doesn't do any error checking
# Only the explicit conversion does the error checking
oneunit(::Union{T,Type{T}}) where T<:AbstractCliffordNumber = convert(T, one(T))
#---Construct similar types------------------------------------------------------------------------#
"""
CliffordNumbers.similar_type(
C::Type{<:AbstractCliffordNumber},
[N::Type{<:BaseNumber} = scalar_type(C)],
[Q::Val = Val(signature(C))]
) -> Type{<:AbstractCliffordNumber{Q,N}}
Constructs a type similar to `T` but with numeric type `N` and quadratic form `Q`. The quadratic
form must be wrapped in a `Val` to preserve type stability.
This function must be defined with all its arguments for each concrete type subtyping
`AbstractCliffordNumber`.
# Note on function export
This function is nearly identical in semantics to `StaticArraysCore.similar_type`. However, since
this package does not depend on `StaticArraysCore`, this function is not exported to avoid name
conflicts whenever any package exporting `StaticArraysCore.similar_type` is loaded.
If a package exports `StaticArraysCore.similar_type`, that function will have methods added which
match the methods in this package with the same signature. This may be triggered explicitly if
desired with a `using` or `import` directive.
"""
function similar_type(x::AbstractCliffordNumber, T::Type{<:BaseNumber}, Q::Val)
return similar_type(typeof(x), T, Q)
end
similar_type(x, T::Type{<:BaseNumber}) = similar_type(x, T, Val(signature(x)))
similar_type(x, Q::Val) = similar_type(x, scalar_type(x), Q)
similar(C::Type{<:AbstractCliffordNumber}, args...) = zero(similar_type(C, args...))
similar(x::AbstractCliffordNumber, args...) = zero(similar_type(x, args...))
# Perform conversion of the scalar type
# UNDERSTAND: The type constraint on T is required. Why?
function AbstractCliffordNumber{Q,T}(x::AbstractCliffordNumber{Q}) where {Q,T<:BaseNumber}
return similar_type(x, T)(x)
end
"""
CliffordNumbers.complement_type(C::Type{<:AbstractCliffordNumber})
CliffordNumbers.complement_type(x::AbstractCliffordNumber)
Constructs a type capable of storing the complementary grades of `x`. For the types provided by this
package:
* The complement type of `KVector{K,Q,T}` is `KVector{dimension(Q)-K,Q,T}`.
* The complement type of `EvenCliffordNumber{Q,T}` and `OddCliffordNumber{Q,T}` is the same type
if `dimension(Q)` is even, or the type of opposite grade parity if `dimension(Q)` is odd.
* All other subtypes of `AbstractCliffordNumber{Q,T}`, including `CliffordNumber{Q,T}`, have
complement type `CliffordNumber{Q,T}` (unless defined otherwise).
"""
complement_type(x::AbstractCliffordNumber) = complement_type(typeof(x))
#---real() and complex()---------------------------------------------------------------------------#
"""
real(x::AbstractCliffordNumber{Q,T})
Gets the real portion of each coefficient of `x`. For `T<:Real` this operation does nothing; for
`T<:Complex{S}` this an `AbstractCliffordNumber{Q,S}`.
Note that this does not return the scalar (grade 0) coefficient of `x`. Use `scalar(x)` to obtain
this result in general, or `real(scalar(x))` if only the real portion is desired.
"""
function Base.real(x::AbstractCliffordNumber)
T = similar_type(x, real(scalar_type(x)))
return T(real.(Tuple(x)))
end
Base.real(x::AbstractCliffordNumber{<:Any,<:Real}) = x
"""
complex(x::AbstractCliffordNumber, [y::AbstractCliffordNumber = zero(typeof(x))])
For a single argument `x`, converts the type of each coefficient to a suitable complex type.
For two arguments `x` and `y`, which are both real Clifford numbers, performs the sum `x + y*im`,
constructing a complex Clifford number.
Note that this operation does not isolate a scalar (grade 0) coefficient of `x` or `y`. Use
`complex(scalar(x), [scalar(y)])` to obtain this result.
"""
function Base.complex(x::AbstractCliffordNumber)
C = similar_type(x, complex(scalar_type(x)))
return C(complex.(Tuple(x)))
end
Base.complex(x::AbstractCliffordNumber{<:Any,<:Complex}) = x
function Base.complex(x::T, y::T) where T<:AbstractCliffordNumber{<:Any,<:Real}
C = similar_type(x, complex(scalar_type(x)))
return C(complex.(Tuple(x), Tuple(y)))
end
Base.complex(x::AbstractCliffordNumber, y::AbstractCliffordNumber) = complex(promote(x, y)...)
#---Error checking---------------------------------------------------------------------------------#
"""
CliffordNumbers.check_element_count(sz, [L], data)
Ensures that the number of elements in `data` is the same as the result of `f(Q)`, where `f` is a
function that generates the expected number of elements for the type. This function is used in the
inner constructors of subtypes of `AbstractCliffordNumber{Q}` to ensure that the input has the
correct length.
If provided, the length type parameter `L` can be included as an argument, and it will be checked
for type (must be an `Int`) and value (must be equal to `sz`).
This function returns nothing, but throws an `AssertionError` for failed checks.
"""
@inline function check_element_count(sz, data)
@assert length(data) == sz "Expected $sz scalars from input, got $(length(data))"
return nothing
end
@inline function check_element_count(sz, L, data)
@assert L isa Int "Length type parameter must be an Int (got $(typeof(L)))."
@assert L == sz "Length type parameter must equal $sz (got $L)."
check_element_count(sz, data)
end
#---Printed representations------------------------------------------------------------------------#
"""
CliffordNumbers.short_typename(T::Type{<:AbstractCliffordNumber})
CliffordNumbers.short_typename(x::AbstractCliffordNumber})
Returns a type with a shorter name than `T`, but still constructs an instance of `T`. This is
achieved by removing dependent type parameters; often this includes the length parameter.
"""
short_typename(::Type{C}) where C<:AbstractCliffordNumber = C
short_typename(x::AbstractCliffordNumber) = short_typename(typeof(x))
function show(io::IO, x::AbstractCliffordNumber)
print(io, short_typename(x), (isone(nblades(x)) ? string('(', only(Tuple(x)), ')') : Tuple(x)))
end
function summary(io::IO, x::AbstractCliffordNumber)
println(io, nblades(x), "-element ", short_typename(x), ":")
end
#---Algebra mismatch errors------------------------------------------------------------------------#
"""
CliffordNumbers.AlgebraMismatch(f, args::Tuple)
The arguments to function `f` expected all of it arguments `args` to be part of the same algebra.
"""
struct AlgebraMismatch <: Exception
f::Any
args::Tuple{Vararg{Any}}
end
function Base.showerror(io::IO, ex::AlgebraMismatch)
print(io, "Arguments to ", ex.f, " were not Clifford numbers of the same algebra.")
for (n,x) in enumerate(ex.args)
if x isa AbstractCliffordNumber
print(io, "\n Argument ", n, " has signature parameter ", signature(x))
end
end
end