///|
pub struct FieldMatrix {
  cells : Array[Symbols]
  columns : Int32
}

///|
pub fn FieldMatrix::new(
  rows : Array[Array[Int32]],
) -> FieldMatrix raise CodeError {
  if rows.length() == 0 || rows.length() > 64 {
    raise Limit("matrix row count must be 1..64")
  }
  let columns = rows[0].length()
  if columns == 0 || columns > 64 {
    raise Limit("matrix column count must be 1..64")
  }
  let cells : Array[Symbols] = []
  for row in rows {
    if row.length() != columns {
      raise Shape("ragged matrix")
    }
    cells.push(Symbols::new(row))
  }
  { cells, columns, }
}

///|
pub fn FieldMatrix::identity(size : Int32) -> FieldMatrix raise CodeError {
  if size < 1 || size > 64 {
    raise Limit("identity size must be 1..64")
  }
  let rows : Array[Array[Int32]] = []
  for i in 0.. Int32 {
  self.cells.length()
}

///|
pub fn FieldMatrix::cols(self : FieldMatrix) -> Int32 {
  self.columns
}

///|
pub fn FieldMatrix::to_arrays(self : FieldMatrix) -> Array[Array[Int32]] {
  self.cells.map(row => row.to_array())
}

///|
pub fn FieldMatrix::transpose(self : FieldMatrix) -> FieldMatrix {
  let cells : Array[Symbols] = []
  for col in 0.. FieldMatrix raise CodeError {
  if self.cols() != other.rows() {
    raise Shape("matrix multiplication dimension mismatch")
  }
  let right = other.transpose()
  let cells : Array[Symbols] = []
  for row in self.cells {
    let values : Array[Int32] = []
    for col in right.cells {
      values.push(row.dot(col))
    }
    cells.push({ values, })
  }
  { cells, columns: other.cols(), }
}

///|
// This independent batch elimination is also a reference for streaming rank.
pub fn FieldMatrix::rank(self : FieldMatrix) -> Int32 {
  let rows = self.to_arrays()
  let mut pivot = 0
  for col in 0.. FieldMatrix raise CodeError {
  if self.rows() != self.cols() {
    raise Shape("inverse requires a square matrix")
  }
  let n = self.rows()
  let rows = self.to_arrays()
  let result = FieldMatrix::identity(n).to_arrays()
  for col in 0..