Browse Source

Data: Matlab: static MatlabWriter api design (consistent with MatlabReader)

provider
Christoph Ruegg 12 years ago
parent
commit
2b177f935a
  1. 732
      src/Data/Matlab/Formatter.cs
  2. 1
      src/Data/Matlab/Matlab.csproj
  3. 14
      src/Data/Matlab/MatlabMatrix.cs
  4. 18
      src/Data/Matlab/MatlabReader.cs
  5. 775
      src/Data/Matlab/MatlabWriter.cs
  6. 143
      src/Data/Matlab/Parser.cs
  7. 134
      src/DataUnitTests/Matlab/MatlabWriterTests.cs

732
src/Data/Matlab/Formatter.cs

@ -0,0 +1,732 @@
// <copyright file="Formatter.cs" company="Math.NET">
// 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-2014 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.
// </copyright>
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Numerics;
using System.Text;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Storage;
using MathNet.Numerics.Properties;
namespace MathNet.Numerics.Data.Matlab
{
/// <summary>
/// Format a MATLAB file.
/// </summary>
internal static class Formatter
{
/// <summary>
/// The file header value
/// </summary>
const string HeaderText = "MATLAB 5.0 MAT-file, Platform: .NET 4 - Math.NET Numerics, Created on: ";
/// <summary>
/// The length of the header text.
/// </summary>
const int HeaderTextLength = 116;
/// <summary>
/// Format a matrix block byte array
/// </summary>
internal static MatlabMatrix FormatMatrix<T>(Matrix<T> matrix, string name)
where T : struct, IEquatable<T>, 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 (typeof(T) == typeof(double))
{
var sparse = matrix as LinearAlgebra.Double.SparseMatrix;
return sparse != null
? GetSparseDataArray(sparse, name)
: GetDenseDataArray((LinearAlgebra.Double.Matrix)(object)matrix, name);
}
if (typeof(T) == typeof(float))
{
var sparse = matrix as LinearAlgebra.Single.SparseMatrix;
return sparse != null
? GetSparseDataArray(sparse, name)
: GetDenseDataArray((LinearAlgebra.Single.Matrix)(object)matrix, name);
}
if (typeof(T) == typeof(Complex))
{
var sparse = matrix as LinearAlgebra.Complex.SparseMatrix;
return sparse != null
? GetSparseDataArray(sparse, name)
: GetDenseDataArray((LinearAlgebra.Complex.Matrix)(object)matrix, name);
}
if (typeof(T) == typeof(Complex32))
{
var sparse = matrix as LinearAlgebra.Complex32.SparseMatrix;
return sparse != null
? GetSparseDataArray(sparse, name)
: GetDenseDataArray((LinearAlgebra.Complex32.Matrix)(object)matrix, name);
}
throw new NotSupportedException();
}
/// <summary>
/// Writes all matrix blocks to a stream.
/// </summary>
internal static void FormatFile(Stream stream, IEnumerable<MatlabMatrix> matrices)
{
using (var buffer = new BufferedStream(stream))
using (var writer = new BinaryWriter(buffer))
{
WriteHeader(writer);
foreach (var matrix in matrices)
{
// write data type
writer.Write((int)DataType.Compressed);
WriteCompressedData(writer, matrix.Data);
}
writer.Flush();
writer.Close();
}
}
/// <summary>
/// Writes the matrix tag and name.
/// </summary>
/// <param name="writer">The writer we are using.</param>
/// <param name="arrayClass">The array class we are writing.</param>
/// <param name="isComplex">if set to <c>true</c> if this a complex matrix.</param>
/// <param name="name">The name of the matrix.</param>
/// <param name="rows">The number of rows.</param>
/// <param name="columns">The columns of columns.</param>
/// <param name="nzmax">The maximum number of non-zero elements.</param>
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);
var nameBytes = Encoding.ASCII.GetBytes(name);
// write name
if (nameBytes.Length > 4)
{
writer.Write((int)DataType.Int8);
writer.Write(nameBytes.Length);
writer.Write(nameBytes);
var 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);
}
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetDenseDataArray(Matrix<double> matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
WriteMatrixTagAndName(writer, ArrayClass.Double, false, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
writer.Write((int)DataType.Double);
writer.Write(matrix.RowCount*matrix.ColumnCount*8);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
writer.Write(value);
}
}
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetDenseDataArray(Matrix<float> matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
WriteMatrixTagAndName(writer, ArrayClass.Single, false, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
int size = matrix.RowCount*matrix.ColumnCount*4;
writer.Write((int)DataType.Single);
writer.Write(size);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
writer.Write(value);
}
}
PadData(writer, size%8);
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetDenseDataArray(Matrix<Complex> matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
WriteMatrixTagAndName(writer, ArrayClass.Double, true, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
int size = matrix.RowCount*matrix.ColumnCount*8;
writer.Write((int)DataType.Double);
writer.Write(size);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
writer.Write(value.Real);
}
}
writer.Write((int)DataType.Double);
writer.Write(size);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
writer.Write(value.Imaginary);
}
}
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetDenseDataArray(Matrix<Complex32> matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
WriteMatrixTagAndName(writer, ArrayClass.Single, true, name, matrix.RowCount, matrix.ColumnCount, 0);
// write data
int size = matrix.RowCount*matrix.ColumnCount*4;
writer.Write((int)DataType.Single);
writer.Write(size);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
writer.Write(value.Real);
}
}
PadData(writer, size%8);
writer.Write((int)DataType.Single);
writer.Write(size);
for (var j = 0; j < matrix.ColumnCount; j++)
{
var column = matrix.Column(j);
foreach (var value in column)
{
writer.Write(value.Real);
}
}
PadData(writer, size%8);
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetSparseDataArray(LinearAlgebra.Double.SparseMatrix matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(writer, ArrayClass.Sparse, false, name, matrix.RowCount, matrix.ColumnCount, nzmax);
// write ir
writer.Write((int)DataType.Int32);
writer.Write(nzmax*4);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item1);
}
}
// add pad if needed
if (nzmax%2 == 1)
{
writer.Write(0);
}
// write jc
writer.Write((int)DataType.Int32);
writer.Write((matrix.ColumnCount + 1)*4);
writer.Write(0);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<double>)column.Storage).ValueCount;
writer.Write(count);
}
// add pad if needed
if (matrix.ColumnCount%2 == 0)
{
writer.Write(0);
}
// write data
writer.Write((int)DataType.Double);
writer.Write(nzmax*8);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item2);
}
}
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetSparseDataArray(LinearAlgebra.Single.SparseMatrix matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(writer, ArrayClass.Sparse, false, name, matrix.RowCount, matrix.ColumnCount,
nzmax);
// write ir
writer.Write((int)DataType.Int32);
writer.Write(nzmax*4);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item1);
}
}
// add pad if needed
if (nzmax%2 == 1)
{
writer.Write(0);
}
// write jc
writer.Write((int)DataType.Int32);
writer.Write((matrix.ColumnCount + 1)*4);
writer.Write(0);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<float>)column.Storage).ValueCount;
writer.Write(count);
}
// add pad if needed
if (matrix.ColumnCount%2 == 0)
{
writer.Write(0);
}
// write data
writer.Write((int)DataType.Single);
writer.Write(nzmax*4);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item2);
}
}
var pad = nzmax*4%8;
PadData(writer, pad);
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetSparseDataArray(LinearAlgebra.Complex.SparseMatrix matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(writer, ArrayClass.Sparse, true, name, matrix.RowCount, matrix.ColumnCount,
nzmax);
// write ir
writer.Write((int)DataType.Int32);
writer.Write(nzmax*4);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item1);
}
}
// add pad if needed
if (nzmax%2 == 1)
{
writer.Write(0);
}
// write jc
writer.Write((int)DataType.Int32);
writer.Write((matrix.ColumnCount + 1)*4);
writer.Write(0);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<Complex>)column.Storage).ValueCount;
writer.Write(count);
}
// add pad if needed
if (matrix.ColumnCount%2 == 0)
{
writer.Write(0);
}
// write data
writer.Write((int)DataType.Double);
writer.Write(nzmax*8);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item2.Real);
}
}
writer.Write((int)DataType.Double);
writer.Write(nzmax*8);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item2.Imaginary);
}
}
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static MatlabMatrix GetSparseDataArray(LinearAlgebra.Complex32.SparseMatrix matrix, string name)
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream))
{
var nzmax = matrix.NonZerosCount;
WriteMatrixTagAndName(writer, ArrayClass.Sparse, true, name, matrix.RowCount, matrix.ColumnCount,
nzmax);
// write ir
writer.Write((int)DataType.Int32);
writer.Write(nzmax*4);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item1);
}
}
// add pad if needed
if (nzmax%2 == 1)
{
writer.Write(0);
}
// write jc
writer.Write((int)DataType.Int32);
writer.Write((matrix.ColumnCount + 1)*4);
writer.Write(0);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<Complex32>)column.Storage).ValueCount;
writer.Write(count);
}
// add pad if needed
if (matrix.ColumnCount%2 == 0)
{
writer.Write(0);
}
// write data
writer.Write((int)DataType.Single);
writer.Write(nzmax*4);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item2.Real);
}
}
var pad = nzmax*4%8;
PadData(writer, pad);
writer.Write((int)DataType.Single);
writer.Write(nzmax*4);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
writer.Write(row.Item2.Imaginary);
}
}
PadData(writer, pad);
writer.Flush();
return new MatlabMatrix(name, stream.ToArray());
}
}
/// <summary>
/// Writes the file header.
/// </summary>
static void WriteHeader(BinaryWriter writer)
{
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);
}
/// <summary>
/// Pads the data with the given byte.
/// </summary>
/// <param name="writer">Where to write the pad values.</param>
/// <param name="bytes">The number of bytes to pad.</param>
/// <param name="pad">What value to pad with.</param>
static void PadData(BinaryWriter writer, int bytes, byte pad = (byte)0)
{
for (var i = 0; i < bytes; i++)
{
writer.Write(pad);
}
}
/// <summary>
/// Writes the compressed data.
/// </summary>
/// <param name="data">The data to write.</param>
static void WriteCompressedData(BinaryWriter writer, 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);
}
/// <summary>
/// Compresses the data array.
/// </summary>
/// <param name="data">The data to compress.</param>
/// <returns>The compressed data.</returns>
static byte[] CompressData(byte[] data)
{
var adler = BitConverter.GetBytes(Adler32.Compute(data));
using (var compressedStream = new MemoryStream())
{
compressedStream.WriteByte(0x58);
compressedStream.WriteByte(0x85);
using (var outputStream = new DeflateStream(compressedStream, CompressionMode.Compress, true))
{
outputStream.Write(data, 0, data.Length);
}
compressedStream.WriteByte(adler[3]);
compressedStream.WriteByte(adler[2]);
compressedStream.WriteByte(adler[1]);
compressedStream.WriteByte(adler[0]);
return compressedStream.ToArray();
}
}
}
}

1
src/Data/Matlab/Matlab.csproj

@ -52,6 +52,7 @@
<Compile Include="ArrayClass.cs" />
<Compile Include="ArrayFlags.cs" />
<Compile Include="MatlabMatrix.cs" />
<Compile Include="Formatter.cs" />
<Compile Include="SparseArrayReader.cs" />
<Compile Include="NumericArrayReader.cs" />
<Compile Include="DataType.cs" />

14
src/Data/Matlab/MatlabMatrix.cs

@ -37,18 +37,16 @@ namespace MathNet.Numerics.Data.Matlab
/// <summary>Name of the matrix</summary>
public string Name { get; private set; }
/// <summary>Matrix size in bytes</summary>
public int Size { get; private set; }
/// <summary>Number of dimensions</summary>
public int Dimensions { get; private set; }
/// <summary>Size of the packed matrix in bytes</summary>
public int ByteSize
{
get { return Data.Length; }
}
internal MatlabMatrix(string name, int size, int dimensions, byte[] data)
internal MatlabMatrix(string name, byte[] data)
{
Data = data;
Name = name;
Size = size;
Dimensions = dimensions;
}
}
}

18
src/Data/Matlab/MatlabReader.cs

@ -37,7 +37,7 @@ using MathNet.Numerics.LinearAlgebra;
namespace MathNet.Numerics.Data.Matlab
{
/// <summary>
/// Creates matrices from MATLAB files.
/// Creates matrices from MATLAB 5 files.
/// </summary>
public static class MatlabReader
{
@ -46,7 +46,7 @@ namespace MathNet.Numerics.Data.Matlab
/// </summary>
public static List<MatlabMatrix> List(Stream stream)
{
return Parser.ParseAll(stream);
return Parser.ParseFile(stream);
}
/// <summary>
@ -56,7 +56,7 @@ namespace MathNet.Numerics.Data.Matlab
{
using (var stream = File.OpenRead(filePath))
{
return Parser.ParseAll(stream);
return List(stream);
}
}
@ -67,7 +67,7 @@ namespace MathNet.Numerics.Data.Matlab
public static Matrix<TDataType> Unpack<TDataType>(MatlabMatrix matrixData)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
return Parser.ReadMatrixBlock<TDataType>(matrixData.Data);
return Parser.ParseMatrix<TDataType>(matrixData.Data);
}
/// <summary>
@ -77,11 +77,11 @@ namespace MathNet.Numerics.Data.Matlab
public static Matrix<TDataType> Read<TDataType>(Stream stream, string matrixName = null)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
var matrices = Parser.ParseAll(stream);
var matrices = List(stream);
if (string.IsNullOrEmpty(matrixName))
{
return Parser.ReadMatrixBlock<TDataType>(matrices.First().Data);
return Unpack<TDataType>(matrices.First());
}
var matrix = matrices.Find(m => m.Name == matrixName);
@ -90,7 +90,7 @@ namespace MathNet.Numerics.Data.Matlab
throw new KeyNotFoundException("Matrix with the provided name was not found.");
}
return Parser.ReadMatrixBlock<TDataType>(matrix.Data);
return Unpack<TDataType>(matrix);
}
/// <summary>
@ -114,9 +114,9 @@ namespace MathNet.Numerics.Data.Matlab
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
var names = new HashSet<string>(matrixNames);
return Parser.ParseAll(stream)
return List(stream)
.Where(m => names.Count == 0 || names.Contains(m.Name))
.ToDictionary(m => m.Name, m => Parser.ReadMatrixBlock<TDataType>(m.Data));
.ToDictionary(m => m.Name, Unpack<TDataType>);
}
/// <summary>

775
src/Data/Matlab/MatlabWriter.cs

@ -31,789 +31,86 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Numerics;
using System.Text;
using System.Linq;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Storage;
using MathNet.Numerics.Properties;
namespace MathNet.Numerics.Data.Matlab
{
/// <summary>
/// Writes matrices to a MATLAB file.
/// Writes matrices to a MATLAB 5 file.
/// </summary>
public class MatlabWriter : IDisposable
public static class MatlabWriter
{
/// <summary>
/// The file header value
/// </summary>
const string HeaderText = "MATLAB 5.0 MAT-file, Platform: .NET 4 - Math.NET Numerics, Created on: ";
/// <summary>
/// The length of the header text.
/// </summary>
const int HeaderTextLength = 116;
/// <summary>
/// Have we written the header yet.
/// </summary>
bool _headerWritten;
/// <summary>
/// The binary writer to write to.
/// </summary>
BinaryWriter _writer;
/// <summary>
/// Initializes a new instance of the <see cref="MatlabWriter"/> class.
/// </summary>
/// <param name="filename">The name of the MATLAB file to save the matrices to.</param>
public MatlabWriter(string filename)
public static void Store(Stream stream, IEnumerable<MatlabMatrix> matrices)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentException(Resources.StringNullOrEmpty, "filename");
}
_writer =
new BinaryWriter(
new BufferedStream(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)));
Formatter.FormatFile(stream, matrices);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
public static void Store(string filePath, IEnumerable<MatlabMatrix> matrices)
{
if (_writer != null)
using (var stream = File.OpenWrite(filePath))
{
_writer.Flush();
_writer.Close();
_writer = null;
Store(stream, matrices);
}
}
/// <summary>
/// Writes the given <see cref="Matrix{T}"/> to the file.
/// </summary>
/// <param name="matrix">The matrix to write.</param>
/// <param name="name">The name of the matrix to store in the file.</param>
/// <exception cref="ArgumentNullException">If either <paramref name="matrix"/> or <paramref name="name"/> is <c>null</c>.</exception>
/// <typeparam name="TDataType">The data type of the Matrix. It can be either: double, float, Complex, or Complex32.</typeparam>
public void WriteMatrix<TDataType>(Matrix<TDataType> matrix, string name)
public static MatlabMatrix Pack<TDataType>(Matrix<TDataType> matrix, string matrixName)
where TDataType : struct, IEquatable<TDataType>, 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 data type
_writer.Write((int)DataType.Compressed);
byte[] data;
if (typeof (TDataType) == typeof (double))
{
var o = matrix as LinearAlgebra.Double.SparseMatrix;
data = o != null
? GetSparseDataArray((LinearAlgebra.Double.SparseMatrix)(object)matrix, name)
: GetDenseDataArray((LinearAlgebra.Double.Matrix)(object)matrix, name);
}
else if (typeof (TDataType) == typeof (float))
{
var o = matrix as LinearAlgebra.Single.SparseMatrix;
data = o != null
? GetSparseDataArray((LinearAlgebra.Single.SparseMatrix)(object)matrix, name)
: GetDenseDataArray((LinearAlgebra.Single.Matrix)(object)matrix, name);
}
else if (typeof (TDataType) == typeof (Complex))
{
var o = matrix as LinearAlgebra.Complex.SparseMatrix;
data = o != null
? GetSparseDataArray((LinearAlgebra.Complex.SparseMatrix)(object)matrix, name)
: GetDenseDataArray((LinearAlgebra.Complex.Matrix)(object)matrix, name);
}
else if (typeof (TDataType) == typeof (Complex32))
{
var o = matrix as LinearAlgebra.Complex32.SparseMatrix;
data = o != null
? GetSparseDataArray((LinearAlgebra.Complex32.SparseMatrix)(object)matrix, name)
: GetDenseDataArray((LinearAlgebra.Complex32.Matrix)(object)matrix, name);
}
else
{
throw new NotSupportedException();
}
WriteCompressedData(data);
return Formatter.FormatMatrix(matrix, matrixName);
}
/// <summary>
/// Writes the given <see cref="Matrix{TDataType}"/> to the file.
/// </summary>
/// <param name="matrices">The matrices to write.</param>
/// <param name="names">The names of the matrices to store in the file.</param>
/// <exception cref="ArgumentNullException">If either <paramref name="matrices"/> or <paramref name="names"/> is null.</exception>
/// <typeparam name="TDataType">The data type of the Matrix. It can be either: double, float, Complex, or Complex32.</typeparam>
public void WriteMatrices<TDataType>(IList<Matrix<TDataType>> matrices, IList<string> names)
public static void Write<TDataType>(Stream stream, Matrix<TDataType> matrix, string matrixName)
where TDataType : struct, IEquatable<TDataType>, 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 (var i = 0; i < matrices.Count; i++)
{
WriteMatrix(matrices[i], names[i]);
}
}
/// <summary>
/// Closes the stream the being written to.
/// </summary>
/// <remarks>Calls <see cref="IDisposable.Dispose"/>.</remarks>
public void Close()
{
Dispose();
}
/// <summary>
/// Writes the matrix tag and name.
/// </summary>
/// <param name="writer">The writer we are using.</param>
/// <param name="arrayClass">The array class we are writing.</param>
/// <param name="isComplex">if set to <c>true</c> if this a complex matrix.</param>
/// <param name="name">The name of the matrix.</param>
/// <param name="rows">The number of rows.</param>
/// <param name="columns">The columns of columns.</param>
/// <param name="nzmax">The maximum number of non-zero elements.</param>
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);
var nameBytes = Encoding.ASCII.GetBytes(name);
// write name
if (nameBytes.Length > 4)
{
writer.Write((int)DataType.Int8);
writer.Write(nameBytes.Length);
writer.Write(nameBytes);
var 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);
}
}
/// <summary>
/// Compresses the data array.
/// </summary>
/// <param name="data">The data to compress.</param>
/// <returns>The compressed data.</returns>
static byte[] CompressData(byte[] data)
{
var adler = BitConverter.GetBytes(Adler32.Compute(data));
using (var compressedStream = new MemoryStream())
{
compressedStream.WriteByte(0x58);
compressedStream.WriteByte(0x85);
using (var outputStream = new DeflateStream(compressedStream, CompressionMode.Compress, true))
{
outputStream.Write(data, 0, data.Length);
}
compressedStream.WriteByte(adler[3]);
compressedStream.WriteByte(adler[2]);
compressedStream.WriteByte(adler[1]);
compressedStream.WriteByte(adler[0]);
return compressedStream.ToArray();
}
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetDenseDataArray(Matrix<double> 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;
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetDenseDataArray(Matrix<float> 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;
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetDenseDataArray(Matrix<Complex> 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;
}
/// <summary>
/// Gets the dense data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetDenseDataArray(Matrix<Complex32> 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;
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetSparseDataArray(LinearAlgebra.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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
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);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<double>)column.Storage).ValueCount;
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
dataWriter.Write(row.Item2);
}
}
data = dataMemoryStream.ToArray();
}
return data;
Store(stream, new[] { Pack(matrix, matrixName) });
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetSparseDataArray(LinearAlgebra.Single.SparseMatrix matrix, string name)
/// <typeparam name="TDataType">The data type of the Matrix. It can be either: double, float, Complex, or Complex32.</typeparam>
public static void Write<TDataType>(string filePath, Matrix<TDataType> matrix, string matrixName)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
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);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<float>)column.Storage).ValueCount;
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
dataWriter.Write(row.Item2);
}
}
var pad = (nzmax*4)%8;
PadData(dataWriter, pad);
data = dataMemoryStream.ToArray();
}
return data;
Store(filePath, new[] { Pack(matrix, matrixName) });
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetSparseDataArray(LinearAlgebra.Complex.SparseMatrix matrix, string name)
/// <typeparam name="TDataType">The data type of the Matrix. It can be either: double, float, Complex, or Complex32.</typeparam>
public static void Write<TDataType>(Stream stream, IList<Matrix<TDataType>> matrices, IList<string> names)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
if (matrices.Count != names.Count)
{
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
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);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<Complex>)column.Storage).ValueCount;
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
dataWriter.Write(row.Item2.Real);
}
}
dataWriter.Write((int)DataType.Double);
dataWriter.Write(nzmax*8);
foreach (var column in matrix.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
dataWriter.Write(row.Item2.Real);
}
}
data = dataMemoryStream.ToArray();
throw new ArgumentException("Each matrix must have a name. Number of matrices must equal to the number of names.");
}
return data;
Store(stream, matrices.Zip(names, Pack));
}
/// <summary>
/// Gets the sparse data array.
/// </summary>
/// <param name="matrix">The matrix to get the data from.</param>
/// <param name="name">The name of the matrix.</param>
/// <returns>The matrix data as an array.</returns>
static byte[] GetSparseDataArray(LinearAlgebra.Complex32.SparseMatrix matrix, string name)
/// <typeparam name="TDataType">The data type of the Matrix. It can be either: double, float, Complex, or Complex32.</typeparam>
public static void Write<TDataType>(string filePath, IList<Matrix<TDataType>> matrices, IList<string> names)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
byte[] data;
using (var dataMemoryStream = new MemoryStream())
using (var dataWriter = new BinaryWriter(dataMemoryStream))
if (matrices.Count != names.Count)
{
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
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);
var count = 0;
foreach (var column in matrix.EnumerateColumns())
{
count += ((SparseVectorStorage<Complex32>)column.Storage).ValueCount;
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
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.EnumerateColumns())
{
foreach (var row in column.EnumerateNonZeroIndexed())
{
dataWriter.Write(row.Item2.Real);
}
}
PadData(dataWriter, pad);
data = dataMemoryStream.ToArray();
throw new ArgumentException("Each matrix must have a name. Number of matrices must equal to the number of names.");
}
return data;
Store(filePath, matrices.Zip(names, Pack));
}
/// <summary>
/// Writes the compressed data.
/// </summary>
/// <param name="data">The data to write.</param>
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);
}
/// <summary>
/// Writes the file header.
/// </summary>
void WriteHeader()
/// <typeparam name="TDataType">The data type of the Matrix. It can be either: double, float, Complex, or Complex32.</typeparam>
public static void Write<TDataType>(Stream stream, IEnumerable<KeyValuePair<string, Matrix<TDataType>>> matrices)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
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);
Store(stream, matrices.Select(kv => Pack(kv.Value, kv.Key)));
}
/// <summary>
/// Pads the data with the given byte.
/// </summary>
/// <param name="writer">Where to write the pad values.</param>
/// <param name="bytes">The number of bytes to pad.</param>
/// <param name="pad">What value to pad with.</param>
static void PadData(BinaryWriter writer, int bytes, byte pad = (byte)0)
/// <typeparam name="TDataType">The data type of the Matrix. It can be either: double, float, Complex, or Complex32.</typeparam>
public static void Write<TDataType>(string filePath, IEnumerable<KeyValuePair<string, Matrix<TDataType>>> matrices)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
for (var i = 0; i < bytes; i++)
{
writer.Write(pad);
}
Store(filePath, matrices.Select(kv => Pack(kv.Value, kv.Key)));
}
}
}

143
src/Data/Matlab/Parser.cs

@ -59,9 +59,79 @@ namespace MathNet.Numerics.Data.Matlab
const int SmallBlockSize = 4;
/// <summary>
/// Extracts all matrix blocks in a format we support.
/// Parse a matrix block byte array
/// </summary>
internal static List<MatlabMatrix> ParseAll(Stream stream)
internal static Matrix<T> ParseMatrix<T>(byte[] data)
where T : struct, IEquatable<T>, IFormattable
{
using (var stream = new MemoryStream(data))
using (var reader = new BinaryReader(stream))
{
// skip tag - doesn't tell us anything we don't already know
reader.BaseStream.Seek(8, SeekOrigin.Current);
var arrayClass = (ArrayClass)reader.ReadByte();
var flags = reader.ReadByte();
var isComplex = (flags & (byte)ArrayFlags.Complex) == (byte)ArrayFlags.Complex;
// skip unneeded bytes
reader.BaseStream.Seek(10, SeekOrigin.Current);
var numDimensions = reader.ReadInt32()/8;
if (numDimensions > 2)
{
throw new NotSupportedException(Resources.MoreThan2D);
}
var rows = reader.ReadInt32();
var columns = reader.ReadInt32();
// skip name and unneeded bytes
reader.BaseStream.Seek(2, SeekOrigin.Current);
int size = reader.ReadInt16();
var smallBlock = true;
if (size == 0)
{
size = reader.ReadInt32();
smallBlock = false;
}
reader.BaseStream.Seek(size, SeekOrigin.Current);
AlignData(reader.BaseStream, size, smallBlock);
var type = (DataType)reader.ReadInt16();
size = reader.ReadInt16();
if (size == 0)
{
size = reader.ReadInt32();
}
Matrix<T> matrix;
switch (arrayClass)
{
case ArrayClass.Sparse:
matrix = SparseArrayReader<T>.PopulateSparseMatrix(reader, isComplex, rows, columns, size);
break;
case ArrayClass.Function:
case ArrayClass.Character:
case ArrayClass.Object:
case ArrayClass.Structure:
case ArrayClass.Cell:
case ArrayClass.Unknown:
throw new NotSupportedException();
default:
matrix = NumericArrayReader<T>.PopulateDenseMatrix(type, reader, isComplex, rows, columns, size);
break;
}
return matrix;
}
}
/// <summary>
/// Extracts all matrix blocks in a format we support from a stream.
/// </summary>
internal static List<MatlabMatrix> ParseFile(Stream stream)
{
var matrices = new List<MatlabMatrix>();
@ -122,7 +192,7 @@ namespace MathNet.Numerics.Data.Matlab
var matrixName = Encoding.ASCII.GetString(matrixReader.ReadBytes(matrixSize));
matrices.Add(new MatlabMatrix(matrixName, matrixSize, matrixDim, data));
matrices.Add(new MatlabMatrix(matrixName, data));
}
}
}
@ -176,72 +246,5 @@ namespace MathNet.Numerics.Data.Matlab
return data;
}
internal static Matrix<TDataType> ReadMatrixBlock<TDataType>(byte[] data)
where TDataType : struct, IEquatable<TDataType>, IFormattable
{
using (var stream = new MemoryStream(data))
using (var reader = new BinaryReader(stream))
{
// skip tag - doesn't tell us anything we don't already know
reader.BaseStream.Seek(8, SeekOrigin.Current);
var arrayClass = (ArrayClass)reader.ReadByte();
var flags = reader.ReadByte();
var isComplex = (flags & (byte)ArrayFlags.Complex) == (byte)ArrayFlags.Complex;
// skip unneeded bytes
reader.BaseStream.Seek(10, SeekOrigin.Current);
var numDimensions = reader.ReadInt32()/8;
if (numDimensions > 2)
{
throw new NotSupportedException(Resources.MoreThan2D);
}
var rows = reader.ReadInt32();
var columns = reader.ReadInt32();
// skip name and unneeded bytes
reader.BaseStream.Seek(2, SeekOrigin.Current);
int size = reader.ReadInt16();
var smallBlock = true;
if (size == 0)
{
size = reader.ReadInt32();
smallBlock = false;
}
reader.BaseStream.Seek(size, SeekOrigin.Current);
AlignData(reader.BaseStream, size, smallBlock);
var type = (DataType)reader.ReadInt16();
size = reader.ReadInt16();
if (size == 0)
{
size = reader.ReadInt32();
}
Matrix<TDataType> matrix;
switch (arrayClass)
{
case ArrayClass.Sparse:
matrix = SparseArrayReader<TDataType>.PopulateSparseMatrix(reader, isComplex, rows, columns, size);
break;
case ArrayClass.Function:
case ArrayClass.Character:
case ArrayClass.Object:
case ArrayClass.Structure:
case ArrayClass.Cell:
case ArrayClass.Unknown:
throw new NotSupportedException();
default:
matrix = NumericArrayReader<TDataType>.PopulateDenseMatrix(type, reader, isComplex, rows, columns, size);
break;
}
return matrix;
}
}
}
}

134
src/DataUnitTests/Matlab/MatlabWriterTests.cs

@ -28,158 +28,120 @@ using System;
using System.IO;
using System.Numerics;
using MathNet.Numerics.Data.Matlab;
using MathNet.Numerics.LinearAlgebra;
using NUnit.Framework;
namespace MathNet.Numerics.Data.UnitTests.Matlab
{
/// <summary>
/// Matlab matrix writer tests.
/// MATLAB matrix writer tests.
/// </summary>
[TestFixture]
public class MatlabWriterTests
{
/// <summary>
/// Invalid constructor throws <c>ArgumentException</c>.
/// </summary>
[Test]
public void InvalidConstructorThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new MatlabWriter(string.Empty));
Assert.Throws<ArgumentException>(() => new MatlabWriter(null));
}
/// <summary>
/// Write bad matrices throws <c>ArgumentException</c>.
/// </summary>
[Test]
public void WriteBadMatricesThrowsArgumentException()
{
var matrix = new LinearAlgebra.Single.DenseMatrix(1, 1);
var writer = new MatlabWriter("somefile3");
Assert.Throws<ArgumentException>(() => writer.WriteMatrices(new[] { matrix }, new[] { string.Empty }));
Assert.Throws<ArgumentException>(() => writer.WriteMatrices(new[] { matrix }, new string[] { null }));
Assert.Throws<ArgumentException>(() => writer.WriteMatrices(new[] { matrix, matrix }, new[] { "matrix" }));
Assert.Throws<ArgumentException>(() => writer.WriteMatrices(new[] { matrix }, new[] { "some matrix" }));
writer.Dispose();
}
/// <summary>
/// Write <c>null</c> matrices throws <c>ArgumentNullException</c>.
/// </summary>
[Test]
public void WriteNullMatricesThrowsArgumentNullException()
{
var writer = new MatlabWriter("somefile4");
Assert.Throws<ArgumentNullException>(() => writer.WriteMatrices(new LinearAlgebra.Single.Matrix[] { null }, new[] { "matrix" }));
var matrix = new LinearAlgebra.Single.DenseMatrix(1, 1);
Assert.Throws<ArgumentNullException>(() => writer.WriteMatrices(new LinearAlgebra.Single.Matrix[] { matrix }, null));
writer.Dispose();
Matrix<float> matrix = Matrix<float>.Build.Dense(1, 1);
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile3", matrix, string.Empty));
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile3", matrix, null));
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile3", matrix, "some matrix"));
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile3", new[] { matrix }, new[] { string.Empty }));
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile3", new[] { matrix }, new string[] { null }));
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile3", new[] { matrix, matrix }, new[] { "matrix" }));
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile3", new[] { matrix }, new[] { "some matrix" }));
}
/// <summary>
/// Can write double matrices.
/// </summary>
[Test]
public void CanWriteDoubleMatrices()
{
var mat1 = new LinearAlgebra.Double.DenseMatrix(5, 3);
Matrix<double> mat1 = Matrix<double>.Build.Dense(5, 5);
for (var i = 0; i < mat1.ColumnCount; i++)
{
mat1[i, i] = i + .1;
}
var mat2 = new LinearAlgebra.Double.DenseMatrix(4, 5);
Matrix<double> mat2 = Matrix<double>.Build.Dense(4, 5);
for (var i = 0; i < mat2.RowCount; i++)
{
mat2[i, i] = i + .1;
}
var mat3 = new LinearAlgebra.Double.SparseMatrix(5, 4);
Matrix<double> mat3 = Matrix<double>.Build.Sparse(5, 4);
mat3[0, 0] = 1.1;
mat3[0, 2] = 2.2;
mat3[4, 3] = 3.3;
var mat4 = new LinearAlgebra.Double.SparseMatrix(3, 5);
Matrix<double> mat4 = Matrix<double>.Build.Sparse(3, 5);
mat4[0, 0] = 1.1;
mat4[0, 2] = 2.2;
mat4[2, 4] = 3.3;
var write = new LinearAlgebra.Double.Matrix[] { mat1, mat2, mat3, mat4 };
Matrix<double>[] write = { mat1, mat2, mat3, mat4 };
string[] names = { "mat1", "dense_matrix_2", "s1", "sparse2" };
var names = new[] { "mat1", "dense_matrix_2", "s1", "sparse2" };
if (File.Exists("testd.mat"))
{
File.Delete("testd.mat");
}
var writer = new MatlabWriter("testd.mat");
writer.WriteMatrices(write, names);
writer.Dispose();
MatlabWriter.Write("testd.mat", write, names);
var read = MatlabReader.ReadAll<double>("testd.mat", names);
Assert.AreEqual(write.Length, read.Count);
for (var i = 0; i < write.Length; i++)
{
var w = write[i];
var r = read[names[i]];
Assert.AreEqual(w.RowCount, r.RowCount);
Assert.AreEqual(w.ColumnCount, r.ColumnCount);
Assert.IsTrue(w.Equals(r));
}
}
/// <summary>
/// Can write float matrices.
/// </summary>
[Test]
public void CanWriteFloatMatrices()
{
var mat1 = new LinearAlgebra.Single.DenseMatrix(5, 3);
Matrix<float> mat1 = Matrix<float>.Build.Dense(5, 3);
for (var i = 0; i < mat1.ColumnCount; i++)
{
mat1[i, i] = i + .1f;
}
var mat2 = new LinearAlgebra.Single.DenseMatrix(4, 5);
Matrix<float> mat2 = Matrix<float>.Build.Dense(4, 5);
for (var i = 0; i < mat2.RowCount; i++)
{
mat2[i, i] = i + .1f;
}
var mat3 = new LinearAlgebra.Single.SparseMatrix(5, 4);
Matrix<float> mat3 = Matrix<float>.Build.Sparse(5, 4);
mat3[0, 0] = 1.1f;
mat3[0, 2] = 2.2f;
mat3[4, 3] = 3.3f;
var mat4 = new LinearAlgebra.Single.SparseMatrix(3, 5);
Matrix<float> mat4 = Matrix<float>.Build.Sparse(3, 5);
mat4[0, 0] = 1.1f;
mat4[0, 2] = 2.2f;
mat4[2, 4] = 3.3f;
var write = new LinearAlgebra.Single.Matrix[] { mat1, mat2, mat3, mat4 };
Matrix<float>[] write = { mat1, mat2, mat3, mat4 };
string[] names = { "mat1", "dense_matrix_2", "s1", "sparse2" };
var names = new[] { "mat1", "dense_matrix_2", "s1", "sparse2" };
if (File.Exists("tests.mat"))
{
File.Delete("tests.mat");
}
var writer = new MatlabWriter("tests.mat");
writer.WriteMatrices(write, names);
writer.Dispose();
MatlabWriter.Write("tests.mat", write, names);
var read = MatlabReader.ReadAll<float>("tests.mat", names);
Assert.AreEqual(write.Length, read.Count);
for (var i = 0; i < write.Length; i++)
{
var w = write[i];
var r = read[names[i]];
Assert.AreEqual(w.RowCount, r.RowCount);
Assert.AreEqual(w.ColumnCount, r.ColumnCount);
Assert.IsTrue(w.Equals(r));
@ -192,40 +154,37 @@ namespace MathNet.Numerics.Data.UnitTests.Matlab
[Test]
public void CanWriteComplex32Matrices()
{
var mat1 = new LinearAlgebra.Complex32.DenseMatrix(5, 3);
Matrix<Complex32> mat1 = Matrix<Complex32>.Build.Dense(5, 3);
for (var i = 0; i < mat1.ColumnCount; i++)
{
mat1[i, i] = new Complex32(i + .1f, i + .1f);
}
var mat2 = new LinearAlgebra.Complex32.DenseMatrix(4, 5);
Matrix<Complex32> mat2 = Matrix<Complex32>.Build.Dense(4, 5);
for (var i = 0; i < mat2.RowCount; i++)
{
mat2[i, i] = new Complex32(i + .1f, i + .1f);
}
var mat3 = new LinearAlgebra.Complex32.SparseMatrix(5, 4);
Matrix<Complex32> mat3 = Matrix<Complex32>.Build.Sparse(5, 4);
mat3[0, 0] = new Complex32(1.1f, 1.1f);
mat3[0, 2] = new Complex32(2.2f, 2.2f);
mat3[4, 3] = new Complex32(3.3f, 3.3f);
var mat4 = new LinearAlgebra.Complex32.SparseMatrix(3, 5);
Matrix<Complex32> mat4 = Matrix<Complex32>.Build.Sparse(3, 5);
mat4[0, 0] = new Complex32(1.1f, 1.1f);
mat4[0, 2] = new Complex32(2.2f, 2.2f);
mat4[2, 4] = new Complex32(3.3f, 3.3f);
var write = new LinearAlgebra.Complex32.Matrix[] { mat1, mat2, mat3, mat4 };
Matrix<Complex32>[] write = { mat1, mat2, mat3, mat4 };
string[] names = { "mat1", "dense_matrix_2", "s1", "sparse2" };
var names = new[] { "mat1", "dense_matrix_2", "s1", "sparse2" };
if (File.Exists("testc.mat"))
{
File.Delete("testc.mat");
}
var writer = new MatlabWriter("testc.mat");
writer.WriteMatrices(write, names);
writer.Dispose();
MatlabWriter.Write("testc.mat", write, names);
var read = MatlabReader.ReadAll<Complex32>("testc.mat", names);
Assert.AreEqual(write.Length, read.Count);
@ -234,7 +193,6 @@ namespace MathNet.Numerics.Data.UnitTests.Matlab
{
var w = write[i];
var r = read[names[i]];
Assert.AreEqual(w.RowCount, r.RowCount);
Assert.AreEqual(w.ColumnCount, r.ColumnCount);
Assert.IsTrue(w.Equals(r));
@ -247,49 +205,45 @@ namespace MathNet.Numerics.Data.UnitTests.Matlab
[Test]
public void CanWriteComplexMatrices()
{
var mat1 = new LinearAlgebra.Complex.DenseMatrix(5, 3);
Matrix<Complex> mat1 = Matrix<Complex>.Build.Dense(5, 3);
for (var i = 0; i < mat1.ColumnCount; i++)
{
mat1[i, i] = new Complex(i + .1, i + .1);
}
var mat2 = new LinearAlgebra.Complex.DenseMatrix(4, 5);
Matrix<Complex> mat2 = Matrix<Complex>.Build.Dense(4, 5);
for (var i = 0; i < mat2.RowCount; i++)
{
mat2[i, i] = new Complex(i + .1, i + .1);
}
var mat3 = new LinearAlgebra.Complex.SparseMatrix(5, 4);
Matrix<Complex> mat3 = Matrix<Complex>.Build.Sparse(5, 4);
mat3[0, 0] = new Complex(1.1, 1.1);
mat3[0, 2] = new Complex(2.2, 2.2);
mat3[4, 3] = new Complex(3.3, 3.3);
var mat4 = new LinearAlgebra.Complex.SparseMatrix(3, 5);
Matrix<Complex> mat4 = Matrix<Complex>.Build.Sparse(3, 5);
mat4[0, 0] = new Complex(1.1, 1.1);
mat4[0, 2] = new Complex(2.2, 2.2);
mat4[2, 4] = new Complex(3.3, 3.3);
var write = new LinearAlgebra.Complex.Matrix[] { mat1, mat2, mat3, mat4 };
Matrix<Complex>[] write = { mat1, mat2, mat3, mat4 };
string[] names = { "mat1", "dense_matrix_2", "s1", "sparse2" };
var names = new[] { "mat1", "dense_matrix_2", "s1", "sparse2" };
if (File.Exists("testz.mat"))
{
File.Delete("testz.mat");
}
var writer = new MatlabWriter("testz.mat");
writer.WriteMatrices(write, names);
writer.Dispose();
MatlabWriter.Write("testz.mat", write, names);
var read = MatlabReader.ReadAll<Complex>("testz.mat", names);
Assert.AreEqual(write.Length, read.Count);
for (var i = 0; i < write.Length; i++)
{
var w = write[i];
var r = read[names[i]];
Assert.AreEqual(w.RowCount, r.RowCount);
Assert.AreEqual(w.ColumnCount, r.ColumnCount);
Assert.IsTrue(w.Equals(r));
@ -302,11 +256,9 @@ namespace MathNet.Numerics.Data.UnitTests.Matlab
[Test]
public void WriteBadMatrixThrowsArgumentException()
{
var matrix = new LinearAlgebra.Single.DenseMatrix(1, 1);
var writer = new MatlabWriter("somefile1");
Assert.Throws<ArgumentException>(() => writer.WriteMatrix(matrix, string.Empty));
Assert.Throws<ArgumentException>(() => writer.WriteMatrix(matrix, null));
writer.Dispose();
var matrix = Matrix<float>.Build.Dense(1, 1);
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile1", matrix, string.Empty));
Assert.Throws<ArgumentException>(() => MatlabWriter.Write("somefile1", matrix, null));
}
/// <summary>
@ -315,9 +267,7 @@ namespace MathNet.Numerics.Data.UnitTests.Matlab
[Test]
public void WriteNullMatrixThrowsArgumentNullException()
{
var writer = new MatlabWriter("somefile2");
Assert.Throws<ArgumentNullException>(() => writer.WriteMatrix<double>(null, "matrix"));
writer.Dispose();
Assert.Throws<ArgumentNullException>(() => MatlabWriter.Write<double>("somefile2", null, "matrix"));
}
}
}

Loading…
Cancel
Save