mat: provide mechanism to output Python and MATLAB syntax

This commit is contained in:
Dan Kortschak
2020-05-05 21:43:17 +09:30
parent 3182a0783b
commit de92f8ef4d
3 changed files with 436 additions and 28 deletions

View File

@@ -7,6 +7,7 @@ package mat
import (
"fmt"
"strconv"
"strings"
)
// Formatted returns a fmt.Formatter for the matrix m using the given options.
@@ -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.
@@ -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
@@ -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)

View File

@@ -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.
@@ -108,5 +150,4 @@ func ExampleExcerpt() {
//
// excerpt long row vector: Dims(1, 100)
// [ 0 1 2 ... ... 97 98 99]
}

View File

@@ -23,8 +23,8 @@ func TestFormat(t *testing.T) {
}{
// Dense matrix representation
{
Formatted(NewDense(3, 3, []float64{0, 0, 0, 0, 0, 0, 0, 0, 0})),
[]rp{
m: Formatted(NewDense(3, 3, []float64{0, 0, 0, 0, 0, 0, 0, 0, 0})),
rep: []rp{
{"%v", "⎡0 0 0⎤\n⎢0 0 0⎥\n⎣0 0 0⎦"},
{"% f", "⎡. . .⎤\n⎢. . .⎥\n⎣. . .⎦"},
{"%#v", fmt.Sprintf("%#v", NewDense(3, 3, nil))},
@@ -32,52 +32,52 @@ func TestFormat(t *testing.T) {
},
},
{
Formatted(NewDense(3, 3, []float64{1, 1, 1, 1, 1, 1, 1, 1, 1})),
[]rp{
m: Formatted(NewDense(3, 3, []float64{1, 1, 1, 1, 1, 1, 1, 1, 1})),
rep: []rp{
{"%v", "⎡1 1 1⎤\n⎢1 1 1⎥\n⎣1 1 1⎦"},
{"% f", "⎡1 1 1⎤\n⎢1 1 1⎥\n⎣1 1 1⎦"},
{"%#v", fmt.Sprintf("%#v", NewDense(3, 3, []float64{1, 1, 1, 1, 1, 1, 1, 1, 1}))},
},
},
{
Formatted(NewDense(3, 3, []float64{1, 1, 1, 1, 1, 1, 1, 1, 1}), Prefix("\t")),
[]rp{
m: Formatted(NewDense(3, 3, []float64{1, 1, 1, 1, 1, 1, 1, 1, 1}), Prefix("\t")),
rep: []rp{
{"%v", "⎡1 1 1⎤\n\t⎢1 1 1⎥\n\t⎣1 1 1⎦"},
{"% f", "⎡1 1 1⎤\n\t⎢1 1 1⎥\n\t⎣1 1 1⎦"},
{"%#v", fmt.Sprintf("%#v", NewDense(3, 3, []float64{1, 1, 1, 1, 1, 1, 1, 1, 1}))},
},
},
{
Formatted(NewDense(3, 3, []float64{1, 0, 0, 0, 1, 0, 0, 0, 1})),
[]rp{
m: Formatted(NewDense(3, 3, []float64{1, 0, 0, 0, 1, 0, 0, 0, 1})),
rep: []rp{
{"%v", "⎡1 0 0⎤\n⎢0 1 0⎥\n⎣0 0 1⎦"},
{"% f", "⎡1 . .⎤\n⎢. 1 .⎥\n⎣. . 1⎦"},
{"%#v", fmt.Sprintf("%#v", NewDense(3, 3, []float64{1, 0, 0, 0, 1, 0, 0, 0, 1}))},
},
},
{
Formatted(NewDense(2, 3, []float64{1, 2, 3, 4, 5, 6})),
[]rp{
m: Formatted(NewDense(2, 3, []float64{1, 2, 3, 4, 5, 6})),
rep: []rp{
{"%v", "⎡1 2 3⎤\n⎣4 5 6⎦"},
{"% f", "⎡1 2 3⎤\n⎣4 5 6⎦"},
{"%#v", fmt.Sprintf("%#v", NewDense(2, 3, []float64{1, 2, 3, 4, 5, 6}))},
},
},
{
Formatted(NewDense(3, 2, []float64{1, 2, 3, 4, 5, 6})),
[]rp{
m: Formatted(NewDense(3, 2, []float64{1, 2, 3, 4, 5, 6})),
rep: []rp{
{"%v", "⎡1 2⎤\n⎢3 4⎥\n⎣5 6⎦"},
{"% f", "⎡1 2⎤\n⎢3 4⎥\n⎣5 6⎦"},
{"%#v", fmt.Sprintf("%#v", NewDense(3, 2, []float64{1, 2, 3, 4, 5, 6}))},
},
},
{
func() fmt.Formatter {
m: func() fmt.Formatter {
m := NewDense(2, 3, []float64{0, 1, 2, 3, 4, 5})
m.Apply(sqrt, m)
return Formatted(m)
}(),
[]rp{
rep: []rp{
{"%v", "⎡ 0 1 1.4142135623730951⎤\n⎣1.7320508075688772 2 2.23606797749979⎦"},
{"%.2f", "⎡0.00 1.00 1.41⎤\n⎣1.73 2.00 2.24⎦"},
{"% f", "⎡ . 1 1.4142135623730951⎤\n⎣1.7320508075688772 2 2.23606797749979⎦"},
@@ -85,12 +85,12 @@ func TestFormat(t *testing.T) {
},
},
{
func() fmt.Formatter {
m: func() fmt.Formatter {
m := NewDense(3, 2, []float64{0, 1, 2, 3, 4, 5})
m.Apply(sqrt, m)
return Formatted(m)
}(),
[]rp{
rep: []rp{
{"%v", "⎡ 0 1⎤\n⎢1.4142135623730951 1.7320508075688772⎥\n⎣ 2 2.23606797749979⎦"},
{"%.2f", "⎡0.00 1.00⎤\n⎢1.41 1.73⎥\n⎣2.00 2.24⎦"},
{"% f", "⎡ . 1⎤\n⎢1.4142135623730951 1.7320508075688772⎥\n⎣ 2 2.23606797749979⎦"},
@@ -98,12 +98,12 @@ func TestFormat(t *testing.T) {
},
},
{
func() fmt.Formatter {
m: func() fmt.Formatter {
m := NewDense(2, 3, []float64{0, 1, 2, 3, 4, 5})
m.Apply(sqrt, m)
return Formatted(m, Squeeze())
}(),
[]rp{
rep: []rp{
{"%v", "⎡ 0 1 1.4142135623730951⎤\n⎣1.7320508075688772 2 2.23606797749979⎦"},
{"%.2f", "⎡0.00 1.00 1.41⎤\n⎣1.73 2.00 2.24⎦"},
{"% f", "⎡ . 1 1.4142135623730951⎤\n⎣1.7320508075688772 2 2.23606797749979⎦"},
@@ -111,35 +111,124 @@ func TestFormat(t *testing.T) {
},
},
{
func() fmt.Formatter {
m: func() fmt.Formatter {
m := NewDense(1, 10, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
return Formatted(m, Excerpt(3))
}(),
[]rp{
rep: []rp{
{"%v", "Dims(1, 10)\n[ 1 2 3 ... ... 8 9 10]"},
},
},
{
func() fmt.Formatter {
m: func() fmt.Formatter {
m := NewDense(10, 1, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
return Formatted(m, Excerpt(3))
}(),
[]rp{
rep: []rp{
{"%v", "Dims(10, 1)\n⎡ 1⎤\n⎢ 2⎥\n⎢ 3⎥\n .\n .\n .\n⎢ 8⎥\n⎢ 9⎥\n⎣10⎦"},
},
},
{
func() fmt.Formatter {
m: func() fmt.Formatter {
m := NewDense(10, 10, nil)
for i := 0; i < 10; i++ {
m.Set(i, i, 1)
}
return Formatted(m, Excerpt(3))
}(),
[]rp{
rep: []rp{
{"%v", "Dims(10, 10)\n⎡1 0 0 ... ... 0 0 0⎤\n⎢0 1 0 0 0 0⎥\n⎢0 0 1 0 0 0⎥\n .\n .\n .\n⎢0 0 0 1 0 0⎥\n⎢0 0 0 0 1 0⎥\n⎣0 0 0 ... ... 0 0 1⎦"},
},
},
{
m: Formatted(NewDense(9, 1, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}), FormatMATLAB()),
rep: []rp{
{"%v", "[1; 2; 3; 4; 5; 6; 7; 8; 9]"},
{"%#v", "[\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n]"},
{"%s", "%!s(*mat.Dense=Dims(9, 1))"},
{"%#s", "%!s(*mat.Dense=Dims(9, 1))"},
},
},
{
m: Formatted(NewDense(1, 9, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}), FormatMATLAB()),
rep: []rp{
{"%v", "[1 2 3 4 5 6 7 8 9]"},
{"%#v", "[1 2 3 4 5 6 7 8 9]"},
},
},
{
m: Formatted(NewDense(3, 3, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}), FormatMATLAB()),
rep: []rp{
{"%v", "[1 2 3; 4 5 6; 7 8 9]"},
{"%#v", "[\n 1 2 3\n 4 5 6\n 7 8 9\n]"},
},
},
{
m: Formatted(NewDense(9, 1, []float64{1, -2, 3, 4, 5, 6, 7, 8, 9}), FormatMATLAB()),
rep: []rp{
{"%v", "[1; -2; 3; 4; 5; 6; 7; 8; 9]"},
{"%#v", "[\n 1\n -2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n]"},
},
},
{
m: Formatted(NewDense(1, 9, []float64{1, -2, 3, 4, 5, 6, 7, 8, 9}), FormatMATLAB()),
rep: []rp{
{"%v", "[1 -2 3 4 5 6 7 8 9]"},
{"%#v", "[ 1 -2 3 4 5 6 7 8 9]"},
},
},
{
m: Formatted(NewDense(3, 3, []float64{1, -2, 3, 4, 5, 6, 7, 8, 9}), FormatMATLAB()),
rep: []rp{
{"%v", "[1 -2 3; 4 5 6; 7 8 9]"},
{"%#v", "[\n 1 -2 3\n 4 5 6\n 7 8 9\n]"},
},
},
{
m: Formatted(NewDense(9, 1, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}), FormatPython()),
rep: []rp{
{"%v", "[[1], [2], [3], [4], [5], [6], [7], [8], [9]]"},
{"%#v", "[[1],\n [2],\n [3],\n [4],\n [5],\n [6],\n [7],\n [8],\n [9]]"},
{"%s", "%!s(*mat.Dense=Dims(9, 1))"},
{"%#s", "%!s(*mat.Dense=Dims(9, 1))"},
},
},
{
m: Formatted(NewDense(1, 9, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}), FormatPython()),
rep: []rp{
{"%v", "[1, 2, 3, 4, 5, 6, 7, 8, 9]"},
{"%#v", "[1, 2, 3, 4, 5, 6, 7, 8, 9]"},
},
},
{
m: Formatted(NewDense(3, 3, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}), FormatPython()),
rep: []rp{
{"%v", "[[1, 2, 3], [4, 5, 6], [7, 8, 9]]"},
{"%#v", "[[1, 2, 3],\n [4, 5, 6],\n [7, 8, 9]]"},
},
},
{
m: Formatted(NewDense(9, 1, []float64{1, -2, 3, 4, 5, 6, 7, 8, 9}), FormatPython()),
rep: []rp{
{"%v", "[[1], [-2], [3], [4], [5], [6], [7], [8], [9]]"},
{"%#v", "[[ 1],\n [-2],\n [ 3],\n [ 4],\n [ 5],\n [ 6],\n [ 7],\n [ 8],\n [ 9]]"},
},
},
{
m: Formatted(NewDense(1, 9, []float64{1, -2, 3, 4, 5, 6, 7, 8, 9}), FormatPython()),
rep: []rp{
{"%v", "[1, -2, 3, 4, 5, 6, 7, 8, 9]"},
{"%#v", "[ 1, -2, 3, 4, 5, 6, 7, 8, 9]"},
},
},
{
m: Formatted(NewDense(3, 3, []float64{1, -2, 3, 4, 5, 6, 7, 8, 9}), FormatPython()),
rep: []rp{
{"%v", "[[1, -2, 3], [4, 5, 6], [7, 8, 9]]"},
{"%#v", "[[ 1, -2, 3],\n [ 4, 5, 6],\n [ 7, 8, 9]]"},
},
},
} {
for j, rp := range test.rep {
got := fmt.Sprintf(rp.format, test.m)