/* @(#) Allocate, and return a pointer to, a DOUBLEMASK representing the LU
 * @(#) decomposition of the matrix in DOUBLEMASK *mat.  Give it the filename
 * @(#) member *name.  Returns NULL on error.  Scale and offset are ignored.
 * @(#)
 * @(#) DOUBLEMASK *im_lu_decomp( const DOUBLEMASK *mat, const char *name );
 * @(#) 
 * @(#) Solve the system of linear equations Ax=b, where matrix A has already
 * @(#) been decomposed into LU form in DOUBLEMASK *lu.  Input vector b is in
 * @(#) vec and is overwritten with vector x.
 * @(#) 
 * @(#) int im_lu_solve( const DOUBLEMASK *lu, double *vec );
 * @(#) 
 * @(#) Allocate, and return a pointer to, a DOUBLEMASK representing the 
 * @(#) inverse of the matrix represented in mat.  Give it the filename
 * @(#) member *name.  Returns NULL on error.  Scale and offset are ignored.
 * @(#) 
 * @(#) DOUBLEMASK *im_matinv( const DOUBLEMASK *mat, const char *name );
 * @(#) 
 * @(#) Invert the matrix represented by the DOUBLEMASK *mat, and store 
 * @(#) it in the place of *mat.  Returns -1 on error.  Scale and offset
 * @(#)  are ignored.
 * @(#) 
 * @(#) int im_matinv_inplace( DOUBLEMASK *mat );
 *
 * Author: Tom Vajzovic
 * Copyright: 2006, Tom Vajzovic
 * Written on: 2006-09-08
 *
 * undated:
 *  - page 43-45 of numerical recipes in C 1998
 *
 * 2006-09-08 tcv:
 *  - complete rewrite; algorithm unchanged
 */

/*

    This file is part of VIPS.
    
    VIPS is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2 of the License, 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 Lesser General Public License for more details.

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

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/** HEADERS **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <float.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <vips/vips.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/


/** CONSTANTS **/

#define TOO_SMALL             ( 2.0 * DBL_MIN )
/* DBL_MIN is smallest *normalized* double precision float */


/** MACROS **/

#define MATRIX( mask, i, j )   ( (mask)-> coeff[ (j) + (i) * (mask)-> xsize ] )
/* use DOUBLEMASK or INTMASK as matrix type */


/** LOCAL FUNCTION DECLARATIONS **/

static int 
mat_inv_lu(
    DOUBLEMASK *inv, 
    const DOUBLEMASK *lu 
  );

static int 
mat_inv_direct( 
    DOUBLEMASK *inv, 
    const DOUBLEMASK *mat, 
    const char *function_name 
  );


/** EXPORTED FUNCTION DEFINITIONS **/

DOUBLEMASK *
im_lu_decomp( 
    const DOUBLEMASK *mat,
    const char *name
){
#define FUNCTION_NAME "im_lu_decomp"
/* This function takes any square NxN DOUBLEMASK treats it as a matrix.
 * It allocates a DOUBLEMASK which is (N+1)xN.
 *
 * It calculates the PLU decomposition, storing the upper and diagonal parts
 * of U, together with the lower parts of L, as an NxN matrix in the first
 * N rows of the new matrix.  The diagonal parts of L are all set to unity 
 * and are not stored.  
 *
 * The final row of the new DOUBLEMASK has only integer entries, which 
 * represent the row-wise permutations made by the permuatation matrix P.
 *
 * The scale and offset members of the input DOUBLEMASK are ignored.
 *
 * See:
 * PRESS, W. et al, 1992.  Numerical Recipies in C; The Art of Scientific 
 * Computing, 2nd ed.  Cambridge: Cambridge University Press, pp. 43-50.
 */
  int i, j, k;
  double *row_scale;
  DOUBLEMASK *lu;

  if( mat-> xsize != mat-> ysize ){
    im_error( FUNCTION_NAME, "non-square matrix" );
    return NULL;
  }
#define   N   ( mat -> xsize )

  lu= im_create_dmask( name, N, N + 1 );
  row_scale= IM_ARRAY( NULL, N, double );

  if( ! row_scale || ! lu ){
    im_free_dmask( lu );
    im_free( row_scale );
    return NULL;
  }
  /* copy all coefficients and then perform decomposition in-place */

  memcpy( lu-> coeff, mat-> coeff, N * N * sizeof( double ) );

#define   LU( i, j )      MATRIX( lu,  (i), (j) )
#define   perm            ( lu-> coeff + N * N )

  for( i= 0; i < N; ++i ){

    row_scale[ i ]= 0.0;

    for( j= 0; j < N; ++j ){
      double abs_val= fabs( LU( i, j ) );

      /* find largest in each ROW */

      if( abs_val > row_scale[ i ] )
        row_scale[ i ]= abs_val;
    }
    if( ! row_scale[ i ] ){
      im_error( FUNCTION_NAME, "singular matrix" );
      im_free_dmask( lu );
      im_free( row_scale );
      return NULL;
    }
    /* fill array with scaling factors for each ROW */

    row_scale[ i ]= 1.0 / row_scale[ i ];
  }
  for( j= 0; j < N; ++j ){  /* loop over COLs */
    double max= -1.0;
    int i_of_max;

    /* not needed, but stops a compiler warning */
    i_of_max= 0;

    /* loop over ROWS in upper-half, except diagonal */
    
    for( i= 0; i < j; ++i )      
      for( k= 0; k < i; ++k )
        LU( i, j )-= LU( i, k ) * LU( k, j );

    /* loop over ROWS in diagonal and lower-half */     
    
    for( i= j; i < N; ++i ){ 
      double abs_val;

      for( k= 0; k < j; ++k )
        LU( i, j )-= LU( i, k ) * LU( k, j );
      
      /* find largest element in each COLUMN scaled so that */
      /* largest in each ROW is 1.0 */
      
      abs_val= row_scale[ i ] * fabs( LU( i, j ) );

      if( abs_val > max ){
        max= abs_val;
        i_of_max= i;
      }
    }
    if( fabs( LU( i_of_max, j ) ) < TOO_SMALL ){  
      /* divisor is near zero */      
      im_error( FUNCTION_NAME, "singular or near-singular matrix" );
      im_free_dmask( lu );
      im_free( row_scale );
      return NULL;
    }
    if( i_of_max != j ){
      /* swap ROWS */

      for( k= 0; k < N; ++k ){
        double temp= LU( j, k );
        LU( j, k )= LU( i_of_max, k );
        LU( i_of_max, k )= temp;
      }
      row_scale[ i_of_max ]= row_scale[ j ];
      /* no need to copy this scale back up - we won't use it */
    }
    /* record permutation */
    perm[ j ]= i_of_max;

    /* divide by best (largest scaled) pivot found */
    for( i= j + 1; i < N; ++i )
      LU( i, j )/= LU( j, j );   
  }
  im_free( row_scale );
  
  return lu;

#undef N
#undef LU
#undef perm
#undef FUNCTION_NAME
}

int 
im_lu_solve( 
  const DOUBLEMASK *lu, 
  double *vec 
){
#define FUNCTION_NAME "im_lu_solve"
  int i, j;

  if( lu-> xsize + 1 != lu-> ysize ){
    im_error( FUNCTION_NAME, "not an LU decomposed matrix" );
    return -1;
  }
#define   N            ( lu -> xsize )
#define   LU( i, j )   MATRIX( lu,  (i), (j) )
#define   perm         ( lu-> coeff + N * N )
  
  for( i= 0; i < N; ++i ){
    int i_perm= perm[ i ];

    if( i_perm != i ){
      double temp= vec[ i ];
      vec[ i ]= vec[ i_perm ];
      vec[ i_perm ]= temp;
    }
    for( j= 0; j < i; ++j )
      vec[ i ]-= LU( i, j ) * vec [ j ];
  }

  for( i= N - 1; i >= 0; --i ){

    for( j= i + 1; j < N; ++j )
      vec[ i ]-= LU( i, j ) * vec [ j ];

    vec[ i ]/= LU( i, i );
  }
  return 0;

#undef LU
#undef perm
#undef N
#undef FUNCTION_NAME
}

DOUBLEMASK *
im_matinv( 
  const DOUBLEMASK *mat, 
  const char *name
){
#define FUNCTION_NAME "im_matinv"

  DOUBLEMASK *inv;

  if( mat-> xsize != mat-> ysize ){
    im_error( FUNCTION_NAME, "non-square matrix" );
    return NULL;
  } 
#define   N                ( mat -> xsize )
  inv= im_create_dmask( name, N, N );
  if( ! inv )
    return NULL;

  if( N < 4 ){
    if( mat_inv_direct( inv, (const DOUBLEMASK *) mat, FUNCTION_NAME ) ){
      im_free_dmask( inv );
      return NULL;    
    }
    return inv;
  }
  else {
    DOUBLEMASK *lu= im_lu_decomp( mat, "temp" );
    
    if( ! lu || mat_inv_lu( inv, (const DOUBLEMASK*) lu ) ){
      im_free_dmask( lu );
      im_free_dmask( inv );
      return NULL;
    }
    im_free_dmask( lu );

    return inv;
  }
#undef N
#undef FUNCTION_NAME
}

int
im_matinv_inplace( 
  DOUBLEMASK *mat 
){
#define FUNCTION_NAME "im_matinv_inplace"
  int to_return= 0;

  if( mat-> xsize != mat-> ysize ){
    im_error( FUNCTION_NAME, "non-square matrix" );
    return -1;
  }
#define   N                ( mat -> xsize )
  if( N < 4 ){
    DOUBLEMASK *dup= im_dup_dmask( mat, "temp" );
    if( ! dup )
      return -1;

    to_return= mat_inv_direct( mat, (const DOUBLEMASK*) dup, FUNCTION_NAME );

    im_free_dmask( dup );
      
    return to_return;
  }
  {
    DOUBLEMASK *lu;

    lu= im_lu_decomp( mat, "temp" );
    
    if( ! lu || mat_inv_lu( mat, (const DOUBLEMASK*) lu ) )
      to_return= -1;
    
    im_free_dmask( lu );
      
    return to_return;
  }
#undef N
#undef FUNCTION_NAME
}

/* Invert a square  size x size matrix stored in matrix[][]
 * result is returned in the same matrix
 */
int 
im_invmat( 
    double **matrix, 
    int size 
  ){

  DOUBLEMASK *mat= im_create_dmask( "temp", size, size );
  int i;
  int to_return= 0;

  for( i= 0; i < size; ++i )
    memcpy( mat-> coeff + i * size, matrix[ i ], size * sizeof( double ) );

  to_return= im_matinv_inplace( mat );

  if( ! to_return )
    for( i= 0; i < size; ++i )
      memcpy( matrix[ i ], mat-> coeff + i * size, size * sizeof( double ) );

  im_free_dmask( mat );

  return to_return;
}


/** LOCAL FUNCTION DEFINITIONS **/

static int
mat_inv_lu(
  DOUBLEMASK *inv, 
  const DOUBLEMASK *lu
){
#define   N                ( lu-> xsize )
#define   INV( i, j )      MATRIX( inv, (i), (j) )

  int i, j;
  double *vec= IM_ARRAY( NULL, N, double );

  if( ! vec )
    return -1;

  for( j= 0; j < N; ++j ){
    
    for( i= 0; i < N; ++i )
      vec[ i ]= 0.0;

    vec[ j ]= 1.0;

    im_lu_solve( lu, vec );

    for( i= 0; i < N; ++i )
      INV( i, j )= vec[ i ];
  }
  im_free( vec );

  inv-> scale= 1.0;
  inv-> offset= 0.0;

  return 0;

#undef N
#undef INV
}

static int 
mat_inv_direct( 
  DOUBLEMASK *inv, 
  const DOUBLEMASK *mat, 
  const char *function_name 
){
#define   N                ( mat -> xsize )
#define   MAT( i, j )      MATRIX( mat, (i), (j) )
#define   INV( i, j )      MATRIX( inv, (i), (j) )

  inv-> scale= 1.0;
  inv-> offset= 0.0;

  switch( N ){
    case 1: {
      if( fabs( MAT( 0, 0 ) ) < TOO_SMALL ){  
        im_error( function_name, "singular or near-singular matrix" );
        return -1;
      }
      INV( 0, 0 )= 1.0 / MAT( 0, 0 );
      return 0;
    } 
    case 2: {
      double det= MAT( 0, 0 ) * MAT( 1, 1 ) - MAT( 0, 1 ) * MAT( 1, 0 );
      
      if( fabs( det ) < TOO_SMALL ){  
        im_error( function_name, "singular or near-singular matrix" );
        return -1;
      }
      INV( 0, 0 )= MAT( 1, 1 ) / det;
      INV( 0, 1 )= -MAT( 0, 1 ) / det;
      INV( 1, 0 )= -MAT( 1, 0 ) / det;
      INV( 1, 1 )= MAT( 0, 0 ) / det;
      
      return 0;
    }
    case 3: {
      double det= MAT( 0, 0 ) * ( MAT( 1, 1 ) * MAT( 2, 2 ) - MAT( 1, 2 ) * MAT( 2, 1 ) ) 
                - MAT( 0, 1 ) * ( MAT( 1, 0 ) * MAT( 2, 2 ) - MAT( 1, 2 ) * MAT( 2, 0 ) ) 
                + MAT( 0, 2 ) * ( MAT( 1, 0 ) * MAT( 2, 1 ) - MAT( 1, 1 ) * MAT( 2, 0 ) );

      if( fabs( det ) < TOO_SMALL ){  
        im_error( function_name, "singular or near-singular matrix" );
        return -1;
      }
      INV( 0, 0 )= ( MAT( 1, 1 ) * MAT( 2, 2 ) - MAT( 1, 2 ) * MAT( 2, 1 ) ) / det;
      INV( 0, 1 )= ( MAT( 0, 2 ) * MAT( 2, 1 ) - MAT( 0, 1 ) * MAT( 2, 2 ) ) / det;
      INV( 0, 2 )= ( MAT( 0, 1 ) * MAT( 1, 2 ) - MAT( 0, 2 ) * MAT( 1, 1 ) ) / det;
      
      INV( 1, 0 )= ( MAT( 1, 2 ) * MAT( 2, 0 ) - MAT( 1, 0 ) * MAT( 2, 2 ) ) / det;
      INV( 1, 1 )= ( MAT( 0, 0 ) * MAT( 2, 2 ) - MAT( 0, 2 ) * MAT( 2, 0 ) ) / det;
      INV( 1, 2 )= ( MAT( 0, 2 ) * MAT( 1, 0 ) - MAT( 0, 0 ) * MAT( 1, 2 ) ) / det;
      
      INV( 2, 0 )= ( MAT( 1, 0 ) * MAT( 2, 1 ) - MAT( 1, 1 ) * MAT( 2, 0 ) ) / det;
      INV( 2, 1 )= ( MAT( 0, 1 ) * MAT( 2, 0 ) - MAT( 0, 0 ) * MAT( 2, 1 ) ) / det;
      INV( 2, 2 )= ( MAT( 0, 0 ) * MAT( 1, 1 ) - MAT( 0, 1 ) * MAT( 1, 0 ) ) / det;

      return 0;
    }
    default:  
      return -1;
  }

#undef N
#undef MAT
#undef INV
}

