---
# System prepended metadata

title: Linear algebra roadmap

---

# Linear algebra roadmap

Linear algebra in Oscar land needs some love. Many conventions have been taken from flint almost verbatim, but they make little sense mathematically. For example, `rref(A::ZZMatrix)` does not return a row reduced echelon form (Hermite form), but a row reduced echelon form of $A$ considered as a rational matrix.

Or, the `rref` also returns the rank at the moment (which makes little sense SB: Why? It was quite useful for me). TH: My main point would be, that it is unexpected and not what the name `rref` (or the new new `echelon_form`) suggests.
CF: also a proper rref has exactly rank many rows which are all non-zero. This is a flint artifact which computes rref via inplace LU and returns a matrix of the original size.. SB: Then to find out the rank? I would have to manually check how many nonzero rows there are? I can ofcourse ask for rank explicitly ... but that could be inefficient since the rank function does not know the matrix is rref and will recompute it.

| Topic | Design decision | Implementation
----|----|----
Matrix normal forms | yes | yes 
Rank | yes | ? 
Kernel  | yes | yes 
Solving  | yes| yes
Solving with same left/right side | yes | yes 
Indexing  | yes | yes 
Iteration | (yes) | no change :( 
"Rational" | ? | ?
Misc | ? | ?
Renaming | ? | ?

CF: rational: I'd also like two general features:
 - solve/ kernel/ ... over a different ring (Z->Q, Z -> Fp). Converting explicitly (map_entries) is e.g. for Z->Q bad and for Z-> Fp badly implemented



 - some general feature to solve over the quotient field and getting a rational solution OR a pair: integral solution and common denominator. Here, I'd favour to either not promise anything or return a simplified "fraction"

<b>TH: Is this something that is already there and needs to be adjusted? Or can this be added later?</b>

 
## User API

### Matrix normal forms

#### Fields:

| Function | Output |
|----------|--------|
| `echelon_form(A::MatElem; reduced::Bool = true, shape = :upper, trim = false)` | An upper resp. lower row echelon form of `A`. If `trim` is `true`, then zero rows are removed.
| `echelon_form_with_transformation(A; reduced, shape)` | Same as above wihout the trim argument. Also return $U$ such that $UA$ is the thing. |

#### Principal ideal domains:

| Function | Output |
|----------|--------|
| `hermite_form(A::MatElem; reduced::true, shape = :upper, trim = false, modulus)` | An upper resp. lower Hermite normal form of `A`. If `trim` is `true`, then zero rows are removed.
| `hermite_form_with_transformation(A; reduced, shape)` | Same as above wihout the trim argument. Also return $U$ such that $UA$ is the thing. |

#### Howell normal form

For principal ideal rings.

`howell_form` etc.

#### Popov normal form

For polynomial rings over fields.

`popov_form` etc.

#### Continuation of echelon forms:

<b>TH: Claus, can you specify the input/ouput?: Either `echelon_form(A; first_rows_are_echelonform = n)` or `echelonize_context(A)` and then `push!(...)` and `finish(A)`. </b>

Anyway, this is not too critical, as this can be added post 1.0. (We don't have it at the moment.)

#### Open question:
- allow rref and ref as the second r can dominate the runtime
  <b>TH: Claus, do you want it as part of the name or not? That is do you want `echelon_form(;reduced::Bool)` or `echelon_form`/`reduced_echelon_form`? And for HNF? `hermite_form(; reduced::Bool)` or `hermite_form`/`reduced_hermite_form`/`weak_hermite_form`?
</b>
We go with the `reduced` keyword argument.

- maybe an interface to the "rational" version (for ZZ and similar)
  <b>TH: Claus, What does that mean?</b> (Postponed)

- is this matrices or modules, ie. how do we sell the pseudo_ stuff of Cohen? TH: Only matrices. No modules and no pseudo stuff.

### Rank

Implement only for PIDs/Euclidean domains? 


### Kernel

Function | Output |
---------|--------|
`kernel(A::MatElem)` | Returns a matrix with rows a generating set of the left kernel of $A$. If the ring is a domain, it should be minimal. |

Comment:
- I write minimal generating set (and not basis), because I also want it to make sense over say $\mathbf Z/n\mathbf Z$.
- CF I do not know if even for domains minimality can be guaranteed/ tested/ assured in all cases (MPoly)
- It should not contain zero rows.
- SB: "it should be minimal"  at least for fields and PIDs we could promise minimality? TH: Yes, good idea. Let's promise minimality in those cases. We can think about the rest later.
- a second interface returning spaces? Which would also allow matrices over maximal orders or polynomial rings?
- or do we allow also PMats (or matrices over maximal orders): a non-minal generating set is "easy": TH: No, this is about matrices.

### Solving

Function | Output |
---------|--------|
`solve(A::MatElem, b::Vector; side = :left)` | Solve $Ax = b$ for $x$. |
`can_solve(A::MatElem, b::Vector; side)` | Return whether $Ax = b$ has a solution |
`can_solve_with_solution(A::MatElem, b::Vector; side)` | Return `fl, b` with `fl` the flag `b` a solution |
`can_solve_with_solution_and_kernel(A::MatElem, b::Vector; side)` | Return `fl, b, K`, with `K` the right resp. left kernel |

Then the same functions also with `b` of type `MatElem`.

Open question:
- What should we do if the user wants to supply more information like uniqueness of the solution, or that the matrix has full rank? Should these be keyword arguments? Or `solve_full_rank` etc? (Postponed)

- there is also the fun with the triu, tril, right/ left versions.
  they are neccessary for the implementation and efficient use of the inplace lu stuff...
  <b>TH: Yes, but what do you suggest? Keyword arguments or seperate functions (`can_solve_with_solution_lower_triangular` etc)?</b> I misunderstood, this is about the weird functions that only take the triangular part of the input matrix and violate `Ax = b`.
  
### Solving with same left/right side

We need an interface for the situation, where one wants to solve `Ax = b` for `A` fixed and `b` popping up later.

| Function | Output |
|-|-|
| `C = solve_context(A)` | Dummy objects to keep track of things |
| `solve(C, b)` | Solve `xA = b` |
| `can_solve(C, b)` | ... |
| `can_solve_with_solutition(C, b)` | ... |
| `can_solve_with_solution_and_kernel(C, b)` | ... |
| `kernel(C)` | ... |

Open question:
- Better name than `solve_context`? CF: solve_init, returning a SolveCtx? <b>TH: `solve_init` it is.</b>
- Shoud the `side` be part of the context `C`? Or should we allow both `solve(C, x; side = :left)` *and* `solve(C, x; side = :right)`? CF:No. In general, this cannot be done efficiently. You need a different normal form for right/ left solve <b>TH: Yes, but the context can compute and store them on demand. So this would not be a problem for efficiency. So what do you prefer if it could be possible to do `solve(C, b, :right)` and `solve(C, b, :left)` with the same context? Or add the flag in the init phase?</b> It will be lazy, so the side can be a flag.

Comment:
- We already have a prototype of this in Hecke.

### Indexing

We support many of the standard operations already:
```
A[i, j]
A[i] # if A is a row or a vector
A[i:j, k:l]
A[i, :]
A[:, j]
```
Open question:
- `A[i, :]` returns something of the same type as `A`. Should it return a `Vector`? TH: I would like it to return a `Vector`. SB: Returning sometimes a Julia vector and sometimes an OSCAR matrix seems inconsitent to me.
- SB: should we support `rows(A)` and `columns(A)`? They would return a vector of row/column matrices. CF??? lists of rows or columns? An iterator for rows/ columns as vectors(?)
- I dislike the vector interface immensly, but it should be consistent. Well, it's more a case of anything that currently works should continue to work. What about vcat(M[i, :] for i = [1,3,5]) or similar? To me itis annoying that I cannot predict when a Vector behaves like anything 1-dim that makes sense, vs. it really is a "n x 1" object. It is (maybe) useful, and people where asking for it, to support Vectors as 1-dim, regardless of orientation, however it has limits (try vcat/ hcat)
- SB: the julia vector vs n x 1 and 1 x n matrix keeps annoying me when I do linear algebra. I have to convert everything back and forth all the time depending on what the functions want.

<b>TH</b>:
- The julia array rule convention, that `A[i1,...,in]` has dimension that sum of the dimensions of the indices `ij`, that is, `sum(length(size(ij)) for j in 1:n)`. There are many bad design decisions in julia, but I think this is sensible. More concretely, if `A` is an `Array{T, 2}`: 
   - `A[1, 1]` has dimension `0`, so is scalar
   - `A[[1], 1]` has dimension `1`, so is a `Vector`
   - `A[[1], [1]]` has dimension `2`, so is an `Array{T, 2}`
   - `A[1, :]` has dimension `1`, so is a `Vector`.
   - `A[:, 1]` has dimension `1`, so is a `Vector`.

  For Oscar matrices we are inconsistent:
   - `A[1, 1]` is a scalar
   - `A[[1, 2], [1, 2]]` is a matrix
   - `A[[1, 2], 2]` is a matrix?
   - `A[:, 2]` is a matrix?

  The other option is that we make `A[1, 1]` return an Oscar `1x1` matrix, then we would also be consistent.
   
  I don't think that there is a big problem with `vcat` or `hcat`. In the end aren't these just submatrices? `vcat(M[i, :] for i = [1,3,5])` can be written either as `M[[1, 3, 5], :]` or `vcat(M[[i], :] for i in [1,3,5])`. One can always get a matrix by just doing `M[[i], :]` instead of `M[i, :]`. On the other hand, it cumbersome to go from `Vector` to `1xn` or `nx1` matrix.
  
  Let me summarize the arguments in favor of changing `A[[1, 2], 1]` resp. `A[:, 1]` to return `Vector`:
  - We have functionality for `solve` and `*` which works for Oscar matrices and julia `Vector` (we did not have this when we introduced indexing)
  - It is easy to get the old behavior: Just do `A[:, [i]]` instead of `A[:, i]`.
  - (Consistency with `Array` conventions)

<b>TH: We will do it like this.</b>
  
- Julia has an `eachrow` and `eachcolumn` function. Their return views of `Vector`, which we could in principle also do?
    ```
    julia> B = [1 2; 3 4];

    julia> first(eachrow(B))
    2-element view(::Matrix{Int64}, 1, :) with eltype         Int64:
     1
     2

    julia> ans[1] = 10
    10
    
    julia> B
    2×2 Matrix{Int64}:
     10  2
      3  4
  ```
  <b>This is not too critical for 1.0, since it can be added later.</b>
  
  TH: We can do this with the same syntax and behavior.

### Broadcasting

We need this! (Maybe TH can give pointers to Aaruni?)

```
B = f.(A)
A .= B
```

Comment:
- "Fusing" or something efficient is not important right now.
- Implementation is connected to the "Iteration" question.

### "Rational"

There is a lot functionality, where one has a matrix over a domain $R$ and wants the result over the field of fractions $K$. Examples are `solve_rational` or `nullspace_right_rational`. In the second case, the result is returned as a matrix over $R$, so that it is not just an application of `map_entries`. Even better: it is a basis in R for the K-basis which is different from the R-basis. We have examples where the span of the nullspace_rational is strictly less than nullspace as the saturation is missing

Open questions:
- Keep those?
- Rename those to `rational_*`?
- see above: version with numerator and denominator? simplified?

<b>TH: Simplified is hard/impossible in general. So maybe the former? </b>

TH: Not clear if we want this or for wich functions we want this. Claus' only application is solving.

### Misc

- sanitize pseudo_inverse vs (potentially) rational_inverse (TH: )
- trivial: purge solve_dixon from the planet: it returns s (in Z^n) and M s.th. As = b mod M. The size of M is caluclated via Hadamard, so no early abort.
- come up with a decent interface for the various flavours:
  - det, det_fflu, det_df, det_clow, det_popov
  - lu, lu!, fflu, fflu!, Strassen.???
  - charpoly_????

TH: Do this via an `algorithm::Symbol` keyword. (Alternative would be a `Det.` submodule).

### Renaming

Old | New
----|----
`det` | `determinant`?
`tr` | `trace`?
`minpoly`? | `minimal_polynomial`?
`charpoly` ? | `characteristic_polynomial`?
`lu` | `lu_decomposition`
`fflu` | `lu_decomposition_fraction_free`
...

We want the long forms, and maybe keep the short forms to be in accord in julia.

## Controversial things

### Iteration (and broadcasting)

Background: There was once a push to make Nemo/Oscar matrices implement as much as possible from the "array interface" (which is underspecified and does not really exist) from julia Base. One concrete step was
https://github.com/Nemocas/AbstractAlgebra.jl/pull/540, 
which implemented iteration and indexing. Note that the length of the discussion shows that this has been and still is controversial.

At the moment we have `for x in A`, which iterates the elements in the *wrong* order:
```
julia> A = ZZ[1 2; 3 4]
[1   2]
[3   4]

julia> for x in A; @show x; end
x = 1
x = 3
x = 2
x = 4
```

I think we have to do it this way, so that we have
```
julia> [a for a in A]
2×2 Matrix{ZZRingElem}:
 1  2
 3  4

julia> A == matrix(ZZ, collect(A))
true
```

The big disadvantage is that
- We are loosing
  ```
  julia> matrix(ZZ, 2, 2, vec(A)) == A
  false
  ```
- It is inefficient for flint matrices CF: well.. for nmod_mat=gfp_mat: yes. For ZZMatrix with large entries: no, as the entries are everywhere. For QQmatrix my guess it it never matters

Open question:
- Does anyone actually use `for a in A`? Should we remove `for a in A`? Should we remove for non-rows or non-columns?
- If we keep it, should it be row major iteration?
- Should we remove it and add something like `for a in elements(A)` with `elements(A; order = :rowmajor)` the default?
- reshape - when we can? (ie. non-permuted rows, no view)
<b>TH: Did you ever miss this? 
I really dislike the `reshape` in Base:
```julia>
A = [1 2; 3 4];

julia> B = reshape(A, (1, 4))
1×4 Matrix{Int64}:
 1  3  2  4

julia> B[1, 1] = 10
10

julia> A
2×2 Matrix{Int64}:
 10  2
  3  4
```
Do you want the same nonsensical behavior?</b>

- view with disjoint row ranges for flint, arbitrary for generic? TH: No, we need to be consistent. 
- support, possibly, for packed matrices? TH: Implementation detail, can be added later.

Comment:
- I think this might also be relevant to implementing broadcasting.

<b>TH:  </b>

# Building blocks:
 - efficient, in place, addrows, columns, butterfly (rows/ cols transformed at the same time)
 - findfirst_nonzero
 - iszero_row, is_zero_column, is_zero_entry
 - for flint matrices, uniform(?) interface to pointer based implementations
 - spinning as a primitive? echelon_continue?
 
# Correctness:
e.g. determinant over Z (Q, NumberField, any situation where CRT applies) is much easier with unproven results...

# normal forms
 - ref/rref/ hnf/ howell (all echelon)
 - popov
 - jordan, frobenius, ... (names and definitions)
 - spectrum, eigen space, eigen value (definitions: require roots or more general)
 - imprecise rings (padic, real): QR, LU, ???, cholesky?

# Obsolete questions/material

Questions:
- Are the names OK? We want something "generic". SB: Will we keep special cases such as `hermite_form`?
- I avoided adding `row` to the name, because I did not plan on having methods for column echelon forms.
- Should we promise anything for things which are not PIDs?
- Should we promise anything about the pivots for the reduced version, if the coefficient ring is not a field?
- What do we promise in the not so obvious cases? I am thinking about $\mathbf{Z}/n\mathbf{Z}$, where one usually wants a "Howell normal form". There is a nice overview by Storjohann in his PhD thesis (p. 28, https://doi.org/10.3929/ethz-a-004141007):
  ![Screenshot 2023-12-02 at 15.15.43](https://hackmd.io/_uploads/Hy0Bh2_B6.png)
  
  Note that "(r2)" is the ususal reducedness of the pivots and for the elements above the pivots.

- the popov_form is missing - and weird as it is not a triangular one
- one way or other, the possibility to contine an echelon form (ie. promising that the top n rows are reduced already)