sml-matrix-market

June 26, 2025 ยท View on GitHub

Standard ML package for parsing MatrixMarket (.mtx) files.

Compatible with the smlpkg package manager.

This library is parallelized if compiled with MaPLe.

Library sources

There are two source files:

  • lib/github.com/shwestrick/sml-matrix-market/sources.mlton.mlb
  • lib/github.com/shwestrick/sml-matrix-market/sources.mpl.mlb

The .mlton.mlb file is for use with MLton and the .mpl.mlb file is for use with MaPLe. Both supply the same interface, described below.

Interface

This library is designed to closely model the actual file format itself.

The library defines a functor, MatrixMarket, which takes implementations of INTEGERs and REALs as argument. This allows for choosing between different integer sizes and real precisions. For example, using I = Int32 will store all parsed integers as 32-bit values.

The REAL implementation also needs to define a function fromLargeWord: LargeWord.word -> real which rounds the input value (interpreted as an unsigned integer) to the nearest representable floating point value. In MLton (and MaPLe), suitable functions are MLton.Real32.fromLargeWord and MLton.Real64.fromLargeWord. This function is needed by sml-fast-real for fast real number parsing.

See below for example usage.

functor MatrixMarket
  (structure I: INTEGER
   structure R:
   sig
     include REAL
     val fromLargeWord: LargeWord.word -> real
   end):
sig

  (* For files with the following header, data is organized into columns.
   *   %%MatrixMarket matrix array ...
   * Note that the encoding of the columns depends on symmetries! If the
   * data is either symmetric, skew-symmetric, or Hermitian, only the lower
   * triangular portion is included, and the 2D structure will therefore be
   * jagged. Each inner array is either:
   *   (a) one whole column (if no symmetry), or
   *   (b) a lower triangular portion of a column (if there is some symmetry)
   *)
  structure Columns:
  sig
    datatype columns =
      Real of R.real array array
    | Integer of I.int array array
    | Complex of {re: R.real, im: R.real} array array
    type t = columns
  end


  (* For files with the following header, data is stored sparsely.
   *   %%MatrixMarket matrix coordinate [real|integer|complex] ...
   * The data of such a matrix will be parsed into the SML form:
   *   Coordinate {row_indices, col_indices, values}
   * These three arrays together are essentially an SoA representation
   * of the tuples (row, col, value) which are the entries of the matrix.
   * For every i, we have one entry:
   *     (row_indices[i], col_indices[i], values[i])
   *)
  structure Values:
  sig
    datatype values =
      Real of R.real array
    | Integer of I.int array
    | Complex of {re: R.real, im: R.real} array
    type t = values
  end


  (* Array and Coordinate forms described above. The Pattern form is
   * essentially the same as Coordinate, but with no values.
   *)
  datatype data =
    Array of Columns.t
  | Coordinate of
      {row_indices: I.int array, col_indices: I.int array, values: Values.t}
  | Pattern of {row_indices: I.int array, col_indices: I.int array}


  (* The data stored in MatrixMarket files has a few ad-hoc compression
   * schemes, taking advantage of symmetries commonly found in matrices:
   *
   *   General: no symmetry, all entries are included in the data.
   *
   *   Symmetric: matrix is symmetric along the diagonal (M[i,j] = M[j,i]).
   *     The data includes only the lower triangular portion, including
   *     diagonal entries, which can be non-zero.
   *
   *   SkewSymmetric: same as Symmetric, but the data along the diagonal is 0
   *     and is excluded.
   *
   *   Hermitian: as the name suggests. The values must be complex, and only
   *     the lower triangular portion of the matrix is included in the data,
   *     including the diagonal, which can be non-zero. The upper triangular
   *     portion is excluded; this can be recovered according to the definition
   *     of Hermitian matrices: M[i,j] = ComplexConjugate(M[j,i]).
   *)
  datatype symmetry =
    General
  | Symmetric
  | SkewSymmetric
  | Hermitian


  datatype matrix =
    Matrix of {num_cols: int, num_rows: int, data: data, symm: symmetry}

  (* read_file(path) -> matrix *)
  val read_file: string -> matrix
end

Example usage

Example main.mlb for MLton or MaPLe.

$(SML_LIB)/basis/basis.mlb
$(SML_LIB)/basis/mlton.mlb                                     (* for MLton.Real64.fromLargeWord *)
lib/github.com/shwestrick/sml-matrix-market/sources.mlton.mlb  (* or use .mpl.mlb instead for MaPLe *)
main.sml

Example main.sml:

structure R64 =
struct
  open MLton.Real64 (* need this for fromLargeWord *)
  open Real64
end

structure M = MatrixMarket(structure I = Int64 structure R = R64)

val matrix = M.read_file "example.mtx"

val () =
  case matrix of
    M.Matrix {num_rows, num_cols, data, symm} =>
      print ("num_rows = " ^ Int.toString num_rows
             ^ ", num_cols = " ^ Int.toString num_cols
             ^ ", ...\n")

Example example.mtx:

%%MatrixMarket matrix coordinate real general
% hello world
4 5 3
1 2 -42.0e-1
1 4 12345
4 5 2.