## Mat4.pm -- read and write version 4 MAT-file format files.

# Copyright (C) 2002 Ralph Schleicher

# Author: Ralph Schleicher <rs@nunatak.allgaeu.org>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2,
# or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

## Commentary:

# See "MAT-File Format, Version 5" from The MathWorks, for detailed
# information about the MAT-file format.  This document is installed
# with Matlab as `$MATLAB/help/pdf_doc/matlab/matfile_format.pdf',
# or visit <http://www.mathworks.com/support/> and follow the links
# "Documentation" -> "Matlab" -> "Printable Documentation".

## Code:

package Mat4;

require 5.000;
require Exporter;

@ISA = qw (Exporter);

@EXPORT = qw (mat4_write
	      mat4_read
	      mat4_ctor
	      mat4_rtoc
	      mat4_main);

use Config;

# The matrix type we can read or write.  We support numeric
# matrices in IEEE double precision floating-point format.
my $mat4_type = ($Config{byteorder} =~ /^1/) ? 0 : 1000;

## Write matrix to file.

sub mat4_write
{
  # Variable name.
  my $name = shift;

  # Number of rows.
  my $m = shift;

  # Number of columns.
  my $n = shift;

  # Matrix elements in column major layout, this is the native
  # Matlab storage layout for matrices.
  my @a = splice (@_, 0, $m * $n);

  # File handle.
  my $h = shift;

  # Type flag.
  my $type = $mat4_type;

  # No imaginary part.
  my $imag = 0;

  # Length of variable name including terminating null character.
  my $len = length ($name) + 1;

  # Encode and print matrix.
  print ($h pack ('l5Z*d*', $type, $m, $n, $imag, $len, $name, @a));
}

## Read matrix from file.

sub mat4_read
{
  # File handle.
  my $h = shift;

  # Input buffer.
  my $buf;

  # Read and decode matrix header.
  return undef if read ($h, $buf, 20) != 20;
  my ($type, $m, $n, $imag, $len) = unpack ('l5', $buf);
  return undef if $type != $mat4_type || $imag != 0;

  # Read and decode variable name.
  return undef if read ($h, $buf, $len) != $len;
  my $name = unpack ('Z*', $buf);

  # Read and decode matrix elements.
  $len = $m * $n * 8;
  return undef if read ($h, $buf, $len) != $len;
  my @a = unpack ('d*', $buf);

  # Return values.
  ($name, $m, $n, @a);
}

## Convert column major layout to row major layout.

sub mat4_ctor
{
  # Number of rows.
  my $m = shift;

  # Number of columns.
  my $n = shift;

  # Array elements.
  my @a = splice (@_, 0, $m * $n);

  # Row major layout.
  my @b = ();

  for (my $j = 0; $j < $n; ++$j)
    {
      for (my $i = 0; $i < $m; ++$i)
	{
	  push (@b, $a[$i * $n + $j]);
	}
    }

  @b;
}

## Convert row major layout to column major layout.

sub mat4_rtoc
{
  # Number of rows.
  my $m = shift;

  # Number of columns.
  my $n = shift;

  # Array elements.
  my @a = splice (@_, 0, $m * $n);

  # Column major layout.
  my @b = ();

  for (my $i = 0; $i < $m; ++$i)
    {
      for (my $j = 0; $j < $n; ++$j)
	{
	  push (@b, $a[$j * $m + $i]);
	}
    }

  @b;
}

## Test program.

# $ perl -e 'use Mat4; mat4_main ()'
# foo[3][2] = {0, 1, 2, 3, 4, 5}
# bar[1][4] = {6, 7, 8, 9}
# baz[0][0] = {}
#
# >> load mat4
# >> whos
#   Name      Size           Bytes  Class
#
#   bar       1x4               32  double array
#   baz       0x0                0  double array
#   foo       3x2               48  double array
#
# Grand total is 10 elements using 80 bytes
#
# >> foo
# foo =
#      0     3
#      1     4
#      2     5
# >> bar
# bar =
#      6     7     8     9
# >> baz
# baz =
#      []
# >>

sub mat4_main
{
  open (MAT, '> mat4.mat')
    || die ("mat4.mat: $!\n");

  mat4_write ('foo', 3, 2, (0, 1, 2, 3, 4, 5), *MAT);
  mat4_write ('bar', 1, 4, (6, 7, 8, 9), *MAT);
  mat4_write ('baz', 0, 0, (), *MAT);

  open (MAT, '< mat4.mat')
    || die ("mat4.mat: $!\n");

  while ((($name, $m, $n, @a) = mat4_read (*MAT)) >= 3)
    {
      printf ("%s[%d][%d] = {%s}\n", $name, $m, $n, join (', ', @a));
    }
}

1;

## Mat4.pm ends here
