//
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
// http://mathnetnumerics.codeplex.com
//
// Copyright (c) 2009-2010 Math.NET
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using zlib;
namespace MathNet.Numerics.LinearAlgebra.IO
{
using Generic;
using Matlab;
using Properties;
///
/// Writes matrices to a Matlab file.
///
public class MatlabMatrixWriter : IDisposable
{
///
/// The file header value
///
private const string HeaderText = "MATLAB 5.0 MAT-file, Platform: .NET 4 - Math.NET Numerics, Created on: ";
///
/// The length of the header text.
///
private const int HeaderTextLength = 116;
///
/// Have we written the header yet.
///
private bool _headerWritten;
///
/// The binary writer to write to.
///
private BinaryWriter _writer;
///
/// Initializes a new instance of the class.
///
/// The name of the Matlab file to save the matrices to.
public MatlabMatrixWriter(string filename)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentException(Resources.StringNullOrEmpty, "filename");
}
_writer = new BinaryWriter(new BufferedStream(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)));
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
if (_writer != null)
{
_writer.Flush();
_writer.Close();
_writer = null;
}
}
///
/// Writes the given to the file.
///
/// The matrix to write.
/// The name of the matrix to store in the file.
/// If either or is null.
/// The data type of the Matrix. It can be either: double, float, Complex, or Complex32.
public void WriteMatrix(Matrix matrix, string name) where TDataType : struct, IEquatable, IFormattable
{
if (matrix == null)
{
throw new ArgumentNullException("matrix");
}
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(Resources.StringNullOrEmpty, "name");
}
if (name.IndexOf(' ') > -1)
{
throw new ArgumentException(string.Format(Resources.NameCannotContainASpace, name), "name");
}
if (!_headerWritten)
{
WriteHeader();
_headerWritten = true;
}
// write datatype
_writer.Write((int)DataType.Compressed);
byte[] data = null;
if (typeof(TDataType) == typeof(double))
{
if (matrix is Double.SparseMatrix)
{
data = GetSparseDataArray((Double.SparseMatrix)(object)matrix, name);
}
else
{
data = GetDenseDataArray((Double.Matrix)(object)matrix, name);
}
}
else if (typeof(TDataType) == typeof(float))
{
if (matrix is Single.SparseMatrix)
{
data = GetSparseDataArray((Single.SparseMatrix)(object)matrix, name);
}
else
{
data = GetDenseDataArray((Single.Matrix)(object)matrix, name);
}
}
else if (typeof(TDataType) == typeof(System.Numerics.Complex))
{
if (matrix is Complex.SparseMatrix)
{
data = GetSparseDataArray((Complex.SparseMatrix)(object)matrix, name);
}
else
{
data = GetDenseDataArray((Complex.Matrix)(object)matrix, name);
}
}
else if (typeof(TDataType) == typeof(Numerics.Complex32))
{
if (matrix is Complex32.SparseMatrix)
{
data = GetSparseDataArray((Complex32.SparseMatrix)(object)matrix, name);
}
else
{
data = GetDenseDataArray((Complex32.Matrix)(object)matrix, name);
}
}
else
{
throw new NotSupportedException();
}
WriteCompressedData(data);
}
///
/// Writes the given to the file.
///
/// The matrices to write.
/// The names of the matrices to store in the file.
/// If either or is null.
/// The data type of the Matrix. It can be either: double, float, Complex, or Complex32.
public void WriteMatrices(IList> matrices, IList names) where TDataType : struct, IEquatable, IFormattable
{
if (matrices == null)
{
throw new ArgumentNullException("matrices");
}
if (names == null)
{
throw new ArgumentNullException("names");
}
if (matrices.Count != names.Count)
{
throw new ArgumentException(Resources.ArgumentMatrixDimensions);
}
for (int i = 0; i < matrices.Count; i++)
{
WriteMatrix(matrices[i], names[i]);
}
}
///
/// Closes the stream the being written to.
///
/// Calls .
public void Close()
{
Dispose();
}
///
/// Writes the matrix tag and name.
///
/// The writer we are using.
/// The array class we are writing.
/// if set to true if this a complex matrix.
/// The name name of the matrix.
/// The number of rows.
/// The columns of columns.
/// The maximum number of non-zero elements.
private static void WriteMatrixTagAndName(BinaryWriter writer, ArrayClass arrayClass, bool isComplex, string name, int rows, int columns, int nzmax)
{
writer.Write((int)DataType.Matrix);
// add place holder for data size
writer.Write(0);
// write flag, data type and size
writer.Write((int)DataType.UInt32);
writer.Write(8);
// write array class and flags
writer.Write((byte)arrayClass);
if (isComplex)
{
writer.Write((byte)ArrayFlags.Complex);
}
else
{
writer.Write((byte)0);
}
writer.Write((short)0);
writer.Write(nzmax);
// write dimensions
writer.Write((int)DataType.Int32);
writer.Write(8);
writer.Write(rows);
writer.Write(columns);
byte[] nameBytes = Encoding.ASCII.GetBytes(name);
// write name
if (nameBytes.Length > 4)
{
writer.Write((int)DataType.Int8);
writer.Write(nameBytes.Length);
writer.Write(nameBytes);
int pad = 8 - (nameBytes.Length % 8);
PadData(writer, pad);
}
else
{
writer.Write((short)DataType.Int8);
writer.Write((short)nameBytes.Length);
writer.Write(nameBytes);
PadData(writer, 4 - nameBytes.Length);
}
}
///
/// Compresses the data array.
///
/// The data to compress.
/// The compressed data.
private static byte[] CompressData(byte[] data)
{
using (var compressedStream = new MemoryStream())
{
using (var outputStream = new ZOutputStream(compressedStream, zlibConst.Z_DEFAULT_COMPRESSION))
{
outputStream.Write(data, 0, data.Length);
}
return compressedStream.ToArray();
}
}
///
/// Gets the dense data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetDenseDataArray(Matrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
WriteMatrixTagAndName(dataWriter, ArrayClass.Double, false, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
dataWriter.Write((int)DataType.Double);
dataWriter.Write(matrix.RowCount * matrix.ColumnCount * 8);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
dataWriter.Write(value);
}
}
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Gets the dense data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetDenseDataArray(Matrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
WriteMatrixTagAndName(dataWriter, ArrayClass.Single, false, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
dataWriter.Write((int)DataType.Single);
dataWriter.Write(matrix.RowCount * matrix.ColumnCount * 4);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
dataWriter.Write(value);
}
}
var pad = (matrix.RowCount * matrix.ColumnCount * 4) % 8;
PadData(dataWriter, pad);
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Gets the dense data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetDenseDataArray(Matrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
WriteMatrixTagAndName(dataWriter, ArrayClass.Double, true, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
dataWriter.Write((int)DataType.Double);
dataWriter.Write(matrix.RowCount * matrix.ColumnCount * 8);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
dataWriter.Write(value.Real);
}
}
dataWriter.Write((int)DataType.Double);
dataWriter.Write(matrix.RowCount * matrix.ColumnCount * 8);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
dataWriter.Write(value.Imaginary);
}
}
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Gets the dense data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetDenseDataArray(Matrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
WriteMatrixTagAndName(dataWriter, ArrayClass.Single, true, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
dataWriter.Write((int)DataType.Single);
dataWriter.Write(matrix.RowCount * matrix.ColumnCount * 4);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
dataWriter.Write(value.Real);
}
}
var pad = (matrix.RowCount * matrix.ColumnCount * 4) % 8;
PadData(dataWriter, pad);
dataWriter.Write((int)DataType.Single);
dataWriter.Write(matrix.RowCount * matrix.ColumnCount * 4);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
dataWriter.Write(value.Real);
}
}
PadData(dataWriter, pad);
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Gets the sparse data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetSparseDataArray(Double.SparseMatrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(dataWriter, ArrayClass.Sparse, false, name, matrix.RowCount, matrix.ColumnCount, nzmax);
// write ir
dataWriter.Write((int)DataType.Int32);
dataWriter.Write(nzmax * 4);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item1);
}
}
// add pad if needed
if (nzmax % 2 == 1)
{
dataWriter.Write(0);
}
// write jc
dataWriter.Write((int)DataType.Int32);
dataWriter.Write((matrix.ColumnCount + 1) * 4);
dataWriter.Write(0);
int count = 0;
foreach (var column in matrix.ColumnEnumerator())
{
count += ((Double.SparseVector)column.Item2).NonZerosCount;
dataWriter.Write(count);
}
// add pad if needed
if (matrix.ColumnCount % 2 == 0)
{
dataWriter.Write(0);
}
// write data
dataWriter.Write((int)DataType.Double);
dataWriter.Write(nzmax * 8);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item2);
}
}
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Gets the sparse data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetSparseDataArray(Single.SparseMatrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(dataWriter, ArrayClass.Sparse, false, name, matrix.RowCount, matrix.ColumnCount, nzmax);
// write ir
dataWriter.Write((int)DataType.Int32);
dataWriter.Write(nzmax * 4);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item1);
}
}
// add pad if needed
if (nzmax % 2 == 1)
{
dataWriter.Write(0);
}
// write jc
dataWriter.Write((int)DataType.Int32);
dataWriter.Write((matrix.ColumnCount + 1) * 4);
dataWriter.Write(0);
int count = 0;
foreach (var column in matrix.ColumnEnumerator())
{
count += ((Single.SparseVector)column.Item2).NonZerosCount;
dataWriter.Write(count);
}
// add pad if needed
if (matrix.ColumnCount % 2 == 0)
{
dataWriter.Write(0);
}
// write data
dataWriter.Write((int)DataType.Single);
dataWriter.Write(nzmax * 4);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item2);
}
}
var pad = (nzmax * 4) % 8;
PadData(dataWriter, pad);
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Gets the sparse data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetSparseDataArray(Complex.SparseMatrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(dataWriter, ArrayClass.Sparse, true, name, matrix.RowCount, matrix.ColumnCount, nzmax);
// write ir
dataWriter.Write((int)DataType.Int32);
dataWriter.Write(nzmax * 4);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item1);
}
}
// add pad if needed
if (nzmax % 2 == 1)
{
dataWriter.Write(0);
}
// write jc
dataWriter.Write((int)DataType.Int32);
dataWriter.Write((matrix.ColumnCount + 1) * 4);
dataWriter.Write(0);
int count = 0;
foreach (var column in matrix.ColumnEnumerator())
{
count += ((Complex.SparseVector)column.Item2).NonZerosCount;
dataWriter.Write(count);
}
// add pad if needed
if (matrix.ColumnCount % 2 == 0)
{
dataWriter.Write(0);
}
// write data
dataWriter.Write((int)DataType.Double);
dataWriter.Write(nzmax * 8);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item2.Real);
}
}
dataWriter.Write((int)DataType.Double);
dataWriter.Write(nzmax * 8);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item2.Real);
}
}
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Gets the sparse data array.
///
/// The matrix to get the data from.
/// The name of the matrix.
/// The matrix data as an array.
private static byte[] GetSparseDataArray(Complex32.SparseMatrix matrix, string name)
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(dataWriter, ArrayClass.Sparse, true, name, matrix.RowCount, matrix.ColumnCount, nzmax);
// write ir
dataWriter.Write((int)DataType.Int32);
dataWriter.Write(nzmax * 4);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item1);
}
}
// add pad if needed
if (nzmax % 2 == 1)
{
dataWriter.Write(0);
}
// write jc
dataWriter.Write((int)DataType.Int32);
dataWriter.Write((matrix.ColumnCount + 1) * 4);
dataWriter.Write(0);
int count = 0;
foreach (var column in matrix.ColumnEnumerator())
{
count += ((Complex32.SparseVector)column.Item2).NonZerosCount;
dataWriter.Write(count);
}
// add pad if needed
if (matrix.ColumnCount % 2 == 0)
{
dataWriter.Write(0);
}
// write data
dataWriter.Write((int)DataType.Single);
dataWriter.Write(nzmax * 4);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item2.Real);
}
}
var pad = (nzmax * 4) % 8;
PadData(dataWriter, pad);
dataWriter.Write((int)DataType.Single);
dataWriter.Write(nzmax * 4);
foreach (var column in matrix.ColumnEnumerator())
{
foreach (var row in column.Item2.GetIndexedEnumerator())
{
dataWriter.Write(row.Item2.Real);
}
}
PadData(dataWriter, pad);
data = dataMemoryStream.ToArray();
}
return data;
}
///
/// Writes the compressed data.
///
/// The data to write.
private void WriteCompressedData(byte[] data)
{
// fill in data size
var size = BitConverter.GetBytes(data.Length);
data[4] = size[0];
data[5] = size[1];
data[6] = size[2];
data[7] = size[3];
// compress data
var compressedData = CompressData(data);
// write compressed data to file
_writer.Write(compressedData.Length);
_writer.Write(compressedData);
}
///
/// Writes the file header.
///
private void WriteHeader()
{
var header = Encoding.ASCII.GetBytes(HeaderText + DateTime.Now.ToString(Resources.MatlabDateHeaderFormat));
_writer.Write(header);
PadData(_writer, HeaderTextLength - header.Length + 8, 32);
// write version
_writer.Write((short)0x100);
// write little endian indicator
_writer.Write((byte)0x49);
_writer.Write((byte)0x4D);
}
///
/// Pads the data with the given byte.
///
/// Where to write the pad values.
/// The number of bytes to pad.
/// What value to pad with.
private static void PadData(BinaryWriter writer, int bytes, byte pad = (byte)0)
{
for (int i = 0; i < bytes; i++)
{
writer.Write(pad);
}
}
}
}