496 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			496 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
| // Copyright ©2015 The Gonum Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package mat
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/binary"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"math"
 | |
| )
 | |
| 
 | |
| // version is the current on-disk codec version.
 | |
| const version uint32 = 0x1
 | |
| 
 | |
| // maxLen is the biggest slice/array len one can create on a 32/64b platform.
 | |
| const maxLen = int64(int(^uint(0) >> 1))
 | |
| 
 | |
| var (
 | |
| 	headerSize  = binary.Size(storage{})
 | |
| 	sizeFloat64 = binary.Size(float64(0))
 | |
| 
 | |
| 	errWrongType = errors.New("mat: wrong data type")
 | |
| 
 | |
| 	errTooBig    = errors.New("mat: resulting data slice too big")
 | |
| 	errTooSmall  = errors.New("mat: input slice too small")
 | |
| 	errBadBuffer = errors.New("mat: data buffer size mismatch")
 | |
| 	errBadSize   = errors.New("mat: invalid dimension")
 | |
| )
 | |
| 
 | |
| // Type encoding scheme:
 | |
| //
 | |
| // Type 		Form 	Packing 	Uplo 		Unit 		Rows 	Columns kU 	kL
 | |
| // uint8 		[GST] 	uint8 [BPF] 	uint8 [AUL] 	bool 		int64 	int64 	int64 	int64
 | |
| // General 		'G' 	'F' 		'A' 		false 		r 	c 	0 	0
 | |
| // Band 		'G' 	'B' 		'A' 		false 		r 	c 	kU 	kL
 | |
| // Symmetric 		'S' 	'F' 		ul 		false 		n 	n 	0 	0
 | |
| // SymmetricBand 	'S' 	'B' 		ul 		false 		n 	n 	k 	k
 | |
| // SymmetricPacked 	'S' 	'P' 		ul 		false 		n 	n 	0 	0
 | |
| // Triangular 		'T' 	'F' 		ul 		Diag==Unit 	n 	n 	0 	0
 | |
| // TriangularBand 	'T' 	'B' 		ul 		Diag==Unit 	n 	n 	k 	k
 | |
| // TriangularPacked 	'T' 	'P' 		ul	 	Diag==Unit 	n 	n 	0 	0
 | |
| //
 | |
| // G - general, S - symmetric, T - triangular
 | |
| // F - full, B - band, P - packed
 | |
| // A - all, U - upper, L - lower
 | |
| 
 | |
| // MarshalBinary encodes the receiver into a binary form and returns the result.
 | |
| //
 | |
| // Dense is little-endian encoded as follows:
 | |
| //
 | |
| //	 0 -  3  Version = 1          (uint32)
 | |
| //	 4       'G'                  (byte)
 | |
| //	 5       'F'                  (byte)
 | |
| //	 6       'A'                  (byte)
 | |
| //	 7       0                    (byte)
 | |
| //	 8 - 15  number of rows       (int64)
 | |
| //	16 - 23  number of columns    (int64)
 | |
| //	24 - 31  0                    (int64)
 | |
| //	32 - 39  0                    (int64)
 | |
| //	40 - ..  matrix data elements (float64)
 | |
| //	         [0,0] [0,1] ... [0,ncols-1]
 | |
| //	         [1,0] [1,1] ... [1,ncols-1]
 | |
| //	         ...
 | |
| //	         [nrows-1,0] ... [nrows-1,ncols-1]
 | |
| func (m Dense) MarshalBinary() ([]byte, error) {
 | |
| 	bufLen := int64(headerSize) + int64(m.mat.Rows)*int64(m.mat.Cols)*int64(sizeFloat64)
 | |
| 	if bufLen <= 0 {
 | |
| 		// bufLen is too big and has wrapped around.
 | |
| 		return nil, errTooBig
 | |
| 	}
 | |
| 
 | |
| 	header := storage{
 | |
| 		Form: 'G', Packing: 'F', Uplo: 'A',
 | |
| 		Rows: int64(m.mat.Rows), Cols: int64(m.mat.Cols),
 | |
| 		Version: version,
 | |
| 	}
 | |
| 	buf := make([]byte, bufLen)
 | |
| 	n, err := header.marshalBinaryTo(bytes.NewBuffer(buf[:0]))
 | |
| 	if err != nil {
 | |
| 		return buf[:n], err
 | |
| 	}
 | |
| 
 | |
| 	p := headerSize
 | |
| 	r, c := m.Dims()
 | |
| 	for i := 0; i < r; i++ {
 | |
| 		for j := 0; j < c; j++ {
 | |
| 			binary.LittleEndian.PutUint64(buf[p:p+sizeFloat64], math.Float64bits(m.at(i, j)))
 | |
| 			p += sizeFloat64
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return buf, nil
 | |
| }
 | |
| 
 | |
| // MarshalBinaryTo encodes the receiver into a binary form and writes it into w.
 | |
| // MarshalBinaryTo returns the number of bytes written into w and an error, if any.
 | |
| //
 | |
| // See MarshalBinary for the on-disk layout.
 | |
| func (m Dense) MarshalBinaryTo(w io.Writer) (int, error) {
 | |
| 	header := storage{
 | |
| 		Form: 'G', Packing: 'F', Uplo: 'A',
 | |
| 		Rows: int64(m.mat.Rows), Cols: int64(m.mat.Cols),
 | |
| 		Version: version,
 | |
| 	}
 | |
| 	n, err := header.marshalBinaryTo(w)
 | |
| 	if err != nil {
 | |
| 		return n, err
 | |
| 	}
 | |
| 
 | |
| 	r, c := m.Dims()
 | |
| 	var b [8]byte
 | |
| 	for i := 0; i < r; i++ {
 | |
| 		for j := 0; j < c; j++ {
 | |
| 			binary.LittleEndian.PutUint64(b[:], math.Float64bits(m.at(i, j)))
 | |
| 			nn, err := w.Write(b[:])
 | |
| 			n += nn
 | |
| 			if err != nil {
 | |
| 				return n, err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // UnmarshalBinary decodes the binary form into the receiver.
 | |
| // It panics if the receiver is a non-empty Dense matrix.
 | |
| //
 | |
| // See MarshalBinary for the on-disk layout.
 | |
| //
 | |
| // Limited checks on the validity of the binary input are performed:
 | |
| //   - ErrShape is returned if the number of rows or columns is negative,
 | |
| //   - an error is returned if the resulting Dense matrix is too
 | |
| //     big for the current architecture (e.g. a 16GB matrix written by a
 | |
| //     64b application and read back from a 32b application.)
 | |
| //
 | |
| // UnmarshalBinary does not limit the size of the unmarshaled matrix, and so
 | |
| // it should not be used on untrusted data.
 | |
| func (m *Dense) UnmarshalBinary(data []byte) error {
 | |
| 	if !m.IsEmpty() {
 | |
| 		panic("mat: unmarshal into non-empty matrix")
 | |
| 	}
 | |
| 
 | |
| 	if len(data) < headerSize {
 | |
| 		return errTooSmall
 | |
| 	}
 | |
| 
 | |
| 	var header storage
 | |
| 	err := header.unmarshalBinary(data[:headerSize])
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	rows := header.Rows
 | |
| 	cols := header.Cols
 | |
| 	header.Version = 0
 | |
| 	header.Rows = 0
 | |
| 	header.Cols = 0
 | |
| 	if (header != storage{Form: 'G', Packing: 'F', Uplo: 'A'}) {
 | |
| 		return errWrongType
 | |
| 	}
 | |
| 	if rows < 0 || cols < 0 {
 | |
| 		return errBadSize
 | |
| 	}
 | |
| 	size := rows * cols
 | |
| 	if size == 0 {
 | |
| 		return ErrZeroLength
 | |
| 	}
 | |
| 	if int(size) < 0 || size > maxLen {
 | |
| 		return errTooBig
 | |
| 	}
 | |
| 	if len(data) != headerSize+int(rows*cols)*sizeFloat64 {
 | |
| 		return errBadBuffer
 | |
| 	}
 | |
| 
 | |
| 	p := headerSize
 | |
| 	m.reuseAsNonZeroed(int(rows), int(cols))
 | |
| 	for i := range m.mat.Data {
 | |
| 		m.mat.Data[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[p : p+sizeFloat64]))
 | |
| 		p += sizeFloat64
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // UnmarshalBinaryFrom decodes the binary form into the receiver and returns
 | |
| // the number of bytes read and an error if any.
 | |
| // It panics if the receiver is a non-empty Dense matrix.
 | |
| //
 | |
| // See MarshalBinary for the on-disk layout.
 | |
| //
 | |
| // Limited checks on the validity of the binary input are performed:
 | |
| //   - ErrShape is returned if the number of rows or columns is negative,
 | |
| //   - an error is returned if the resulting Dense matrix is too
 | |
| //     big for the current architecture (e.g. a 16GB matrix written by a
 | |
| //     64b application and read back from a 32b application.)
 | |
| //
 | |
| // UnmarshalBinary does not limit the size of the unmarshaled matrix, and so
 | |
| // it should not be used on untrusted data.
 | |
| func (m *Dense) UnmarshalBinaryFrom(r io.Reader) (int, error) {
 | |
| 	if !m.IsEmpty() {
 | |
| 		panic("mat: unmarshal into non-empty matrix")
 | |
| 	}
 | |
| 
 | |
| 	var header storage
 | |
| 	n, err := header.unmarshalBinaryFrom(r)
 | |
| 	if err != nil {
 | |
| 		return n, err
 | |
| 	}
 | |
| 	rows := header.Rows
 | |
| 	cols := header.Cols
 | |
| 	header.Version = 0
 | |
| 	header.Rows = 0
 | |
| 	header.Cols = 0
 | |
| 	if (header != storage{Form: 'G', Packing: 'F', Uplo: 'A'}) {
 | |
| 		return n, errWrongType
 | |
| 	}
 | |
| 	if rows < 0 || cols < 0 {
 | |
| 		return n, errBadSize
 | |
| 	}
 | |
| 	size := rows * cols
 | |
| 	if size == 0 {
 | |
| 		return n, ErrZeroLength
 | |
| 	}
 | |
| 	if int(size) < 0 || size > maxLen {
 | |
| 		return n, errTooBig
 | |
| 	}
 | |
| 
 | |
| 	m.reuseAsNonZeroed(int(rows), int(cols))
 | |
| 	var b [8]byte
 | |
| 	for i := range m.mat.Data {
 | |
| 		nn, err := readFull(r, b[:])
 | |
| 		n += nn
 | |
| 		if err != nil {
 | |
| 			if err == io.EOF {
 | |
| 				return n, io.ErrUnexpectedEOF
 | |
| 			}
 | |
| 			return n, err
 | |
| 		}
 | |
| 		m.mat.Data[i] = math.Float64frombits(binary.LittleEndian.Uint64(b[:]))
 | |
| 	}
 | |
| 
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // MarshalBinary encodes the receiver into a binary form and returns the result.
 | |
| //
 | |
| // VecDense is little-endian encoded as follows:
 | |
| //
 | |
| //	 0 -  3  Version = 1            (uint32)
 | |
| //	 4       'G'                    (byte)
 | |
| //	 5       'F'                    (byte)
 | |
| //	 6       'A'                    (byte)
 | |
| //	 7       0                      (byte)
 | |
| //	 8 - 15  number of elements     (int64)
 | |
| //	16 - 23  1                      (int64)
 | |
| //	24 - 31  0                      (int64)
 | |
| //	32 - 39  0                      (int64)
 | |
| //	40 - ..  vector's data elements (float64)
 | |
| func (v VecDense) MarshalBinary() ([]byte, error) {
 | |
| 	bufLen := int64(headerSize) + int64(v.mat.N)*int64(sizeFloat64)
 | |
| 	if bufLen <= 0 {
 | |
| 		// bufLen is too big and has wrapped around.
 | |
| 		return nil, errTooBig
 | |
| 	}
 | |
| 
 | |
| 	header := storage{
 | |
| 		Form: 'G', Packing: 'F', Uplo: 'A',
 | |
| 		Rows: int64(v.mat.N), Cols: 1,
 | |
| 		Version: version,
 | |
| 	}
 | |
| 	buf := make([]byte, bufLen)
 | |
| 	n, err := header.marshalBinaryTo(bytes.NewBuffer(buf[:0]))
 | |
| 	if err != nil {
 | |
| 		return buf[:n], err
 | |
| 	}
 | |
| 
 | |
| 	p := headerSize
 | |
| 	for i := 0; i < v.mat.N; i++ {
 | |
| 		binary.LittleEndian.PutUint64(buf[p:p+sizeFloat64], math.Float64bits(v.at(i)))
 | |
| 		p += sizeFloat64
 | |
| 	}
 | |
| 
 | |
| 	return buf, nil
 | |
| }
 | |
| 
 | |
| // MarshalBinaryTo encodes the receiver into a binary form, writes it to w and
 | |
| // returns the number of bytes written and an error if any.
 | |
| //
 | |
| // See MarshalBinary for the on-disk format.
 | |
| func (v VecDense) MarshalBinaryTo(w io.Writer) (int, error) {
 | |
| 	header := storage{
 | |
| 		Form: 'G', Packing: 'F', Uplo: 'A',
 | |
| 		Rows: int64(v.mat.N), Cols: 1,
 | |
| 		Version: version,
 | |
| 	}
 | |
| 	n, err := header.marshalBinaryTo(w)
 | |
| 	if err != nil {
 | |
| 		return n, err
 | |
| 	}
 | |
| 
 | |
| 	var buf [8]byte
 | |
| 	for i := 0; i < v.mat.N; i++ {
 | |
| 		binary.LittleEndian.PutUint64(buf[:], math.Float64bits(v.at(i)))
 | |
| 		nn, err := w.Write(buf[:])
 | |
| 		n += nn
 | |
| 		if err != nil {
 | |
| 			return n, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // UnmarshalBinary decodes the binary form into the receiver.
 | |
| // It panics if the receiver is a non-empty VecDense.
 | |
| //
 | |
| // See MarshalBinary for the on-disk layout.
 | |
| //
 | |
| // Limited checks on the validity of the binary input are performed:
 | |
| //   - ErrShape is returned if the number of rows is negative,
 | |
| //   - an error is returned if the resulting VecDense is too
 | |
| //     big for the current architecture (e.g. a 16GB vector written by a
 | |
| //     64b application and read back from a 32b application.)
 | |
| //
 | |
| // UnmarshalBinary does not limit the size of the unmarshaled vector, and so
 | |
| // it should not be used on untrusted data.
 | |
| func (v *VecDense) UnmarshalBinary(data []byte) error {
 | |
| 	if !v.IsEmpty() {
 | |
| 		panic("mat: unmarshal into non-empty vector")
 | |
| 	}
 | |
| 
 | |
| 	if len(data) < headerSize {
 | |
| 		return errTooSmall
 | |
| 	}
 | |
| 
 | |
| 	var header storage
 | |
| 	err := header.unmarshalBinary(data[:headerSize])
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if header.Cols != 1 {
 | |
| 		return ErrShape
 | |
| 	}
 | |
| 	n := header.Rows
 | |
| 	header.Version = 0
 | |
| 	header.Rows = 0
 | |
| 	header.Cols = 0
 | |
| 	if (header != storage{Form: 'G', Packing: 'F', Uplo: 'A'}) {
 | |
| 		return errWrongType
 | |
| 	}
 | |
| 	if n == 0 {
 | |
| 		return ErrZeroLength
 | |
| 	}
 | |
| 	if n < 0 {
 | |
| 		return errBadSize
 | |
| 	}
 | |
| 	if int64(maxLen) < n {
 | |
| 		return errTooBig
 | |
| 	}
 | |
| 	if len(data) != headerSize+int(n)*sizeFloat64 {
 | |
| 		return errBadBuffer
 | |
| 	}
 | |
| 
 | |
| 	p := headerSize
 | |
| 	v.reuseAsNonZeroed(int(n))
 | |
| 	for i := range v.mat.Data {
 | |
| 		v.mat.Data[i] = math.Float64frombits(binary.LittleEndian.Uint64(data[p : p+sizeFloat64]))
 | |
| 		p += sizeFloat64
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // UnmarshalBinaryFrom decodes the binary form into the receiver, from the
 | |
| // io.Reader and returns the number of bytes read and an error if any.
 | |
| // It panics if the receiver is a non-empty VecDense.
 | |
| //
 | |
| // See MarshalBinary for the on-disk layout.
 | |
| // See UnmarshalBinary for the list of sanity checks performed on the input.
 | |
| func (v *VecDense) UnmarshalBinaryFrom(r io.Reader) (int, error) {
 | |
| 	if !v.IsEmpty() {
 | |
| 		panic("mat: unmarshal into non-empty vector")
 | |
| 	}
 | |
| 
 | |
| 	var header storage
 | |
| 	n, err := header.unmarshalBinaryFrom(r)
 | |
| 	if err != nil {
 | |
| 		return n, err
 | |
| 	}
 | |
| 	if header.Cols != 1 {
 | |
| 		return n, ErrShape
 | |
| 	}
 | |
| 	l := header.Rows
 | |
| 	header.Version = 0
 | |
| 	header.Rows = 0
 | |
| 	header.Cols = 0
 | |
| 	if (header != storage{Form: 'G', Packing: 'F', Uplo: 'A'}) {
 | |
| 		return n, errWrongType
 | |
| 	}
 | |
| 	if l == 0 {
 | |
| 		return n, ErrZeroLength
 | |
| 	}
 | |
| 	if l < 0 {
 | |
| 		return n, errBadSize
 | |
| 	}
 | |
| 	if int64(maxLen) < l {
 | |
| 		return n, errTooBig
 | |
| 	}
 | |
| 
 | |
| 	v.reuseAsNonZeroed(int(l))
 | |
| 	var b [8]byte
 | |
| 	for i := range v.mat.Data {
 | |
| 		nn, err := readFull(r, b[:])
 | |
| 		n += nn
 | |
| 		if err != nil {
 | |
| 			if err == io.EOF {
 | |
| 				return n, io.ErrUnexpectedEOF
 | |
| 			}
 | |
| 			return n, err
 | |
| 		}
 | |
| 		v.mat.Data[i] = math.Float64frombits(binary.LittleEndian.Uint64(b[:]))
 | |
| 	}
 | |
| 
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // storage is the internal representation of the storage format of a
 | |
| // serialised matrix.
 | |
| type storage struct {
 | |
| 	Version uint32 // Keep this first.
 | |
| 	Form    byte   // [GST]
 | |
| 	Packing byte   // [BPF]
 | |
| 	Uplo    byte   // [AUL]
 | |
| 	Unit    bool
 | |
| 	Rows    int64
 | |
| 	Cols    int64
 | |
| 	KU      int64
 | |
| 	KL      int64
 | |
| }
 | |
| 
 | |
| // TODO(kortschak): Consider replacing these with calls to direct
 | |
| // encoding/decoding of fields rather than to binary.Write/binary.Read.
 | |
| 
 | |
| func (s storage) marshalBinaryTo(w io.Writer) (int, error) {
 | |
| 	buf := bytes.NewBuffer(make([]byte, 0, headerSize))
 | |
| 	err := binary.Write(buf, binary.LittleEndian, s)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	return w.Write(buf.Bytes())
 | |
| }
 | |
| 
 | |
| func (s *storage) unmarshalBinary(buf []byte) error {
 | |
| 	err := binary.Read(bytes.NewReader(buf), binary.LittleEndian, s)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if s.Version != version {
 | |
| 		return fmt.Errorf("mat: incorrect version: %d", s.Version)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *storage) unmarshalBinaryFrom(r io.Reader) (int, error) {
 | |
| 	buf := make([]byte, headerSize)
 | |
| 	n, err := readFull(r, buf)
 | |
| 	if err != nil {
 | |
| 		return n, err
 | |
| 	}
 | |
| 	return n, s.unmarshalBinary(buf[:n])
 | |
| }
 | |
| 
 | |
| // readFull reads from r into buf until it has read len(buf).
 | |
| // It returns the number of bytes copied and an error if fewer bytes were read.
 | |
| // If an EOF happens after reading fewer than len(buf) bytes, io.ErrUnexpectedEOF is returned.
 | |
| func readFull(r io.Reader, buf []byte) (int, error) {
 | |
| 	var n int
 | |
| 	var err error
 | |
| 	for n < len(buf) && err == nil {
 | |
| 		var nn int
 | |
| 		nn, err = r.Read(buf[n:])
 | |
| 		n += nn
 | |
| 	}
 | |
| 	if n == len(buf) {
 | |
| 		return n, nil
 | |
| 	}
 | |
| 	if err == io.EOF {
 | |
| 		return n, io.ErrUnexpectedEOF
 | |
| 	}
 | |
| 	return n, err
 | |
| }
 |