# Indexing

Indexing is a critical operation in linear algebra: it would be difficult to imagine defining operations on vectors, matrices, and arrays without some way of determining the coefficient at a particular location in the object. Similarly, we often want to extract coefficients from multivectors to perform operations.

However, the philosophy of this package – treating multivectors a number system on the same footing as that of complex numbers or quaternions – means that the `AbstractCliffordNumber`

type foregoes array semantics. All `AbstractCliffordNumber`

instances broadcast as scalars: for two instances `x`

and `y`

, `x * y`

is identical to `x .* y`

, both of which calculate the geometric product.

On top of this, the grade representation of multivectors makes it difficult to relate the indices of the backing `Tuple`

for each type to the blades represented by the multivector for anything other than the dense `CliffordNumber`

. To solve this issue, this package provides types specifically intended for indexing `AbstractCliffordNumber`

subtypes: `BitIndex{Q}`

and the subtypes of `AbstractBitIndices{Q,C}`

, `BitIndices{Q,C}`

and `TransformedBitIndices{Q,C}`

.

`BitIndex{Q}`

The `BitIndex{Q}`

type is the index type for `AbstractCliffordNumber{Q}`

. This types wraps a `UInt`

: the first `dimension(Q)`

bits represent the presence or absence of the basis vectors associated with each dimension that are used to construct the indexed blade. The most significant bit is a sign bit: this is needed to represent the parity associated with the order of wedge products used to construct the blade.

Regardless of what position in the underlying `Tuple`

a coefficient may be, if `x == y`

, then the same `BitIndex`

`b`

will index `x`

and `y`

identically:

```
julia> k = KVector{1,VGA(3)}(4,2,0)
3-element KVector{1, VGA(3), Int64}:
4e₁ + 2e₂
julia> b = BitIndex(k, 1)
BitIndex(Val(VGA(3)), 1)
julia> k == OddCliffordNumber(k)
true
julia> k[b] == OddCliffordNumber(k)[b]
true
```

It should also be noted that indexing an `AbstractCliffordNumber`

at an index which is not explicitly represented by the type returns zero:

```
julia> k[BitIndex(k, 1, 2)]
0
```

### Construction

The internal constructor `BitIndex{Q}(signbit::Bool, blade::Unsigned)`

converts `blade`

to a `UInt`

and changes the most significant bit to match `signbit`

. However, in many cases, constructing a `BitIndex{Q}`

directly from the sign bit and representation of the blade as an unsigned integer is inconvenient.

For this reason, we define two constructors, the first being `BitIndex(::Val{Q}, i::Integer...)`

, which takes the algebra `Q`

wrapped in `Val`

(for reasons of type stability) and the integers `i`

corresponding to basis blade indices defined in `Q`

. The second constructor, `BitIndex(x, i::Integer...)`

calls `BitIndex(Val(signature(x))), i...)`

, automatically determining `Q`

.

In both cases, the parity of the permutation of indices is determined automatically from the integer arguments, so this bit is automatically assigned. The presence of a parity bit allows for correct indexing with non-lexicographic conventions: in some literature, $e_3 e_1$ is used instead of $e_1 e_3$ for one of the bivector components of APS and STA.

```
julia> l = KVector{2,VGA(3)}(0,6,9)
3-element KVector{2, VGA(3), Int64}:
6e₁e₃ + 9e₂e₃
julia> l[BitIndex(l, 1, 3)]
6
julia> l[BitIndex(l, 3, 1)]
-6
```

### Tuples of `BitIndex{Q}`

Julia `AbstractArray`

instances can be indexed not just with integers, but with arrays of integers or special objects representing iterable ranges, such as `:`

. Perhaps surprisingly, tuples containing integers (or any other valid index object) are not valid indices of `AbstractArray`

:

```
julia> (1:10)[(2, 3, 4)]
ERROR: ArgumentError: invalid index: (2, 3, 4) of type Tuple{Int64, Int64, Int64}
```

In the case of `AbstractCliffordNumber`

, we have a compelling reason to use tuples of `BitIndex{Q}`

objects for indexing: since the length of a `Tuple`

is known statically, we can use that information to construct a new `Tuple`

of coefficients with statically known length, which may be useful if we want to leverage indexing to convert types. Therefore, indexing an `AbstractCliffordNumber{Q,T}`

with an `NTuple{N,BitIndex{Q}}`

returns an `NTuple{N,T}`

, which can be fed into the constructor for a different type, and this is what the package uses internally to perform conversion.

### Operations

`BitIndex{Q}`

supports a variety of unary and binary operations, many of which are used internally for tasks like calculating geometric products. Many of these operations are also supported for `AbstractCliffordNumber{Q}`

instances, such as negation (`-`

) and the geometric product (`*`

).

`AbstractBitIndices{Q,C}`

Considering that the indices of an `AbstractCliffordNumber`

provided by this package are known from the type, it makes sense to define a type which represents all indices of a subtype or instance of `AbstractCliffordNumber`

. This package defines the supertype `AbstractBitIndices{Q,C}`

and concrete types `BitIndices{Q,C}`

and `TransformedBitIndices{Q,C,F}`

that allow for easy operation across all valid indices of a type `C`

.

`BitIndices{Q,C}`

The `BitIndices`

object for an `AbstractCliffordNumber`

subtype `C`

or instance `x`

can be constructed with `BitIndices(C)`

or `BitIndices(x)`

. This is a singleton type which is also returned by `eachindex(x)`

.

The type information of the `BitIndices`

object is used to determine the type of the result of indexing. Conveniently, you can index one Clifford number `x`

with the indices of another Clifford number `y`

, and this converts `x`

to the a type similar to that of `y`

, but retaining the element type of `x`

:

```
julia> k = KVector{1,VGA(3)}(4,2,0) # eltype Int64
3-element KVector{1, VGA(3), Int64}:
4e₁ + 2e₂
julia> z = zero(CliffordNumber{VGA(3),Float64}) # eltype Float64
8-element CliffordNumber{VGA(3), Float64}:
0.0
julia> k[BitIndices(ans)]
8-element CliffordNumber{VGA(3), Int64}:
4e₁ + 2e₂
```

`BitIndices{Q,C}`

subtypes `AbstractBitIndices{Q,C}`

, which subtypes `AbstractVector{BitIndex{Q}}`

, meaning that the objects can be indexed with integers. This indexing is one-based to match the indices of the `Tuple`

backing a `CliffordNumber`

, but this is subject to change in the future.

One fundamental issue with `BitIndices{Q,C}`

is that `C`

is only constrained to be a subtype of `AbstractCliffordNumber{Q}`

. However, `C`

may have other type parameters that vary, but produce objects that index equivalently and are identical elementwise:

```
julia> BitIndices{STA,CliffordNumber{STA}}() == BitIndices{STA,CliffordNumber{STA,Float32}}()
true
julia> BitIndices{STA,CliffordNumber{STA}}() == BitIndices{STA,CliffordNumber{STA,Float32,8}}()
true
```

For this reason, you should call `BitIndices(x)`

or `BitIndices(C)`

instead of directly constructing `BitIndices{Q,C}()`

.

`TransformedBitIndices{Q,C,F}`

If we want to perform a transformation on all elements of a `BitIndices`

instance, we may use dot syntax and broadcast an operation: for instance, `reverse.(BitIndices(x))`

to obtain the reverse of all indices. Without any explicit specification of behavior, this will return a `Vector`

, and for the sake of performance we'd like to avoid returning types without statically known lengths.

We could solve this by converting `BitIndices`

to a `Tuple`

internally as part of the broadcasting implementation, but doing this will cause its own set of problems: indexing an `AbstractCliffordNumber`

with a `Tuple`

returns a `Tuple`

. This may be fine internally, if we know to call the constructor, but this is potentially confusing for new users. For this reason, we provide `TransformedBitIndices{Q,C,F}`

, which is a lazy representation of a function applied to every element of `BitIndices(C)`

.

For convenience, we provide a few aliases for operations which are commonly used with `BitIndices`

:

`ReversedBitIndices{Q,C}`

is the type of`reverse.(::BitIndices{Q,C})`

.`GradeInvolutedBitIndices{Q,C}`

is the type of`grade_involution.(::BitIndices{Q,C})`

.`ConjugatedBitIndices{Q,C}`

is the type of`conj.(::BitIndices{Q,C})`

.

In the future, we may override some `broadcast`

implementations to ensure that all of these types are interconvertible with each other and with `BitIndices{Q,C}`

.