Skip to content

Commit

Permalink
mat: provide mechanism to output Python and MATLAB syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
kortschak committed May 8, 2020
1 parent 3182a07 commit de92f8e
Show file tree
Hide file tree
Showing 3 changed files with 436 additions and 28 deletions.
284 changes: 281 additions & 3 deletions mat/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package mat
import (
"fmt"
"strconv"
"strings"
)

// Formatted returns a fmt.Formatter for the matrix m using the given options.
Expand All @@ -27,6 +28,8 @@ type formatter struct {
margin int
dot byte
squeeze bool

format func(m Matrix, prefix string, margin int, dot byte, squeeze bool, fs fmt.State, c rune)
}

// FormatOption is a functional option for matrix formatting.
Expand All @@ -51,18 +54,35 @@ func DotByte(b byte) FormatOption {
return func(f *formatter) { f.dot = b }
}

// Squeeze sets the printing behaviour to minimise column width for each individual column.
// Squeeze sets the printing behavior to minimise column width for each individual column.
func Squeeze() FormatOption {
return func(f *formatter) { f.squeeze = true }
}

// FormatMATLAB sets the printing behavior to output MATLAB syntax. If MATLAB syntax is
// specified, the ' ' verb flag and Excerpt option are ignored. If the alternative syntax
// verb flag, '#' is used the matrix is formatted in rows and columns.
func FormatMATLAB() FormatOption {
return func(f *formatter) { f.format = formatMATLAB }
}

// FormatPython sets the printing behavior to output Python syntax. If Python syntax is
// specified, the ' ' verb flag and Excerpt option are ignored. If the alternative syntax
// verb flag, '#' is used the matrix is formatted in rows and columns.
func FormatPython() FormatOption {
return func(f *formatter) { f.format = formatPython }
}

// Format satisfies the fmt.Formatter interface.
func (f formatter) Format(fs fmt.State, c rune) {
if c == 'v' && fs.Flag('#') {
if c == 'v' && fs.Flag('#') && f.format == nil {
fmt.Fprintf(fs, "%#v", f.matrix)
return
}
format(f.matrix, f.prefix, f.margin, f.dot, f.squeeze, fs, c)
if f.format == nil {
f.format = format
}
f.format(f.matrix, f.prefix, f.margin, f.dot, f.squeeze, fs, c)
}

// format prints a pretty representation of m to the fs io.Writer. The format character c
Expand Down Expand Up @@ -193,6 +213,264 @@ func format(m Matrix, prefix string, margin int, dot byte, squeeze bool, fs fmt.
}
}

// formatMATLAB prints a MATLAB representation of m to the fs io.Writer. The format character c
// specifies the numerical representation of elements; valid values are those for float64
// specified in the fmt package, with their associated flags.
// The printed range of the matrix can be limited by specifying a positive value for margin;
// If squeeze is true, column widths are determined on a per-column basis.
//
// formatMATLAB will not provide Go syntax output.
func formatMATLAB(m Matrix, prefix string, _ int, _ byte, squeeze bool, fs fmt.State, c rune) {
rows, cols := m.Dims()

prec, pOk := fs.Precision()
width, _ := fs.Width()
if !fs.Flag('#') {
switch c {
case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
default:
fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
return
}
format := fmtString(fs, c, prec, width)
fs.Write([]byte{'['})
for i := 0; i < rows; i++ {
if i != 0 {
fs.Write([]byte("; "))
}
for j := 0; j < cols; j++ {
if j != 0 {
fs.Write([]byte{' '})
}
fmt.Fprintf(fs, format, m.At(i, j))
}
}
fs.Write([]byte{']'})
return
}

if !pOk {
prec = -1
}

printed := rows
if cols > printed {
printed = cols
}

var (
maxWidth int
widths widther
buf, pad []byte
)
if squeeze {
widths = make(columnWidth, cols)
} else {
widths = new(uniformWidth)
}
switch c {
case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
if c == 'v' {
buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
} else {
buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
}
default:
fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
return
}
width = max(width, maxWidth)
pad = make([]byte, max(width, 1))
for i := range pad {
pad[i] = ' '
}

for i := 0; i < rows; i++ {
var el string
switch {
case rows == 1:
fmt.Fprint(fs, "[")
el = "]"
case i == 0:
fmt.Fprint(fs, "[\n"+prefix+" ")
el = "\n"
case i < rows-1:
fmt.Fprint(fs, prefix+" ")
el = "\n"
default:
fmt.Fprint(fs, prefix+" ")
el = "\n" + prefix + "]"
}

for j := 0; j < cols; j++ {
v := m.At(i, j)
if c == 'v' {
buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
} else {
buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
}
if fs.Flag('-') {
fs.Write(buf)
fs.Write(pad[:widths.width(j)-len(buf)])
} else {
fs.Write(pad[:widths.width(j)-len(buf)])
fs.Write(buf)
}

if j < cols-1 {
fs.Write(pad[:1])
}
}

fmt.Fprint(fs, el)
}
}

// formatPython prints a Python representation of m to the fs io.Writer. The format character c
// specifies the numerical representation of elements; valid values are those for float64
// specified in the fmt package, with their associated flags.
// The printed range of the matrix can be limited by specifying a positive value for margin;
// If squeeze is true, column widths are determined on a per-column basis.
//
// formatPython will not provide Go syntax output.
func formatPython(m Matrix, prefix string, _ int, _ byte, squeeze bool, fs fmt.State, c rune) {
rows, cols := m.Dims()

prec, pOk := fs.Precision()
width, _ := fs.Width()
if !fs.Flag('#') {
switch c {
case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
default:
fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
return
}
format := fmtString(fs, c, prec, width)
fs.Write([]byte{'['})
if rows > 1 {
fs.Write([]byte{'['})
}
for i := 0; i < rows; i++ {
if i != 0 {
fs.Write([]byte("], ["))
}
for j := 0; j < cols; j++ {
if j != 0 {
fs.Write([]byte(", "))
}
fmt.Fprintf(fs, format, m.At(i, j))
}
}
if rows > 1 {
fs.Write([]byte{']'})
}
fs.Write([]byte{']'})
return
}

if !pOk {
prec = -1
}

printed := rows
if cols > printed {
printed = cols
}

var (
maxWidth int
widths widther
buf, pad []byte
)
if squeeze {
widths = make(columnWidth, cols)
} else {
widths = new(uniformWidth)
}
switch c {
case 'v', 'e', 'E', 'f', 'F', 'g', 'G':
if c == 'v' {
buf, maxWidth = maxCellWidth(m, 'g', printed, prec, widths)
} else {
buf, maxWidth = maxCellWidth(m, c, printed, prec, widths)
}
default:
fmt.Fprintf(fs, "%%!%c(%T=Dims(%d, %d))", c, m, rows, cols)
return
}
width = max(width, maxWidth)
pad = make([]byte, max(width, 1))
for i := range pad {
pad[i] = ' '
}

for i := 0; i < rows; i++ {
if i != 0 {
fmt.Fprint(fs, prefix)
}
var el string
switch {
case rows == 1:
fmt.Fprint(fs, "[")
el = "]"
case i == 0:
fmt.Fprint(fs, "[[")
el = "],\n"
case i < rows-1:
fmt.Fprint(fs, " [")
el = "],\n"
default:
fmt.Fprint(fs, " [")
el = "]]"
}

for j := 0; j < cols; j++ {
v := m.At(i, j)
if c == 'v' {
buf = strconv.AppendFloat(buf[:0], v, 'g', prec, 64)
} else {
buf = strconv.AppendFloat(buf[:0], v, byte(c), prec, 64)
}
if fs.Flag('-') {
fs.Write(buf)
fs.Write(pad[:widths.width(j)-len(buf)])
} else {
fs.Write(pad[:widths.width(j)-len(buf)])
fs.Write(buf)
}

if j < cols-1 {
fs.Write([]byte{','})
fs.Write(pad[:1])
}
}

fmt.Fprint(fs, el)
}
}

// This is horrible, but it's what we have.
func fmtString(fs fmt.State, c rune, prec, width int) string {
var b strings.Builder
b.WriteByte('%')
for _, f := range "0+- " {
if fs.Flag(int(f)) {
b.WriteByte(byte(f))
}
}
if width >= 0 {
fmt.Fprint(&b, width)
}
if prec >= 0 {
b.WriteByte('.')
if prec > 0 {
fmt.Fprint(&b, prec)
}
}
b.WriteRune(c)
return b.String()
}

func maxCellWidth(m Matrix, c rune, printed, prec int, w widther) ([]byte, int) {
var (
buf = make([]byte, 0, 64)
Expand Down
43 changes: 42 additions & 1 deletion mat/format_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,48 @@ func ExampleFormatted() {
// ⎣0 0 6⎦
}

func ExampleFormatted_mATLAB() {
a := mat.NewDense(3, 3, []float64{1, 2, 3, 0, 4, 5, 0, 0, 6})

// Create a matrix formatting value using MATLAB format...
fa := mat.Formatted(a, mat.FormatMATLAB())

// and then print with and without layout formatting.
fmt.Printf("standard syntax:\na = %v\n\n", fa)
fmt.Printf("layout syntax:\na = %#v\n\n", fa)

// Output:
// standard syntax:
// a = [1 2 3; 0 4 5; 0 0 6]
//
// layout syntax:
// a = [
// 1 2 3
// 0 4 5
// 0 0 6
// ]
}

func ExampleFormatted_python() {
a := mat.NewDense(3, 3, []float64{1, 2, 3, 0, 4, 5, 0, 0, 6})

// Create a matrix formatting value with a prefix using Python format...
fa := mat.Formatted(a, mat.Prefix(" "), mat.FormatPython())

// and then print with and without layout formatting.
fmt.Printf("standard syntax:\na = %v\n\n", fa)
fmt.Printf("layout syntax:\na = %#v\n\n", fa)

// Output:
// standard syntax:
// a = [[1, 2, 3], [0, 4, 5], [0, 0, 6]]
//
// layout syntax:
// a = [[1, 2, 3],
// [0, 4, 5],
// [0, 0, 6]]
}

func ExampleExcerpt() {
// Excerpt allows diagnostic display of very large
// matrices and vectors.
Expand Down Expand Up @@ -108,5 +150,4 @@ func ExampleExcerpt() {
//
// excerpt long row vector: Dims(1, 100)
// [ 0 1 2 ... ... 97 98 99]

}
Loading

0 comments on commit de92f8e

Please sign in to comment.