// ---------------------------------------------------------------------------
// - Cramer.cpp                                                              -
// - afnix:mth module - cramer system solver class implementation            -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - 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.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2011 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Cramer.hpp"
#include "Exception.hpp"
 
namespace afnix {
  // -------------------------------------------------------------------------
  // - private section                                                       -
  // -------------------------------------------------------------------------

  // the row cofactor
  struct s_rcf {
    // the factor value
    t_real d_fval;
    // the factor column
    t_long d_fcol;
    // the column cofactor list
    s_rcf* p_ccfl;
    // the row cofactor list
    s_rcf* p_rcfl;
    // initialize the row cofactor
    s_rcf (const t_real val, const t_long col) {
      d_fval = val;
      d_fcol = col;
      p_ccfl = nilp;
      p_rcfl = nilp;
    }
    // destroy this row cofactor
    ~s_rcf (void) {
      delete p_rcfl;
      delete p_ccfl;
    }
    // link a new row cofactor
    void link (const t_real val, const t_long col) {
      // check if last
      if (p_ccfl == nilp) {
	p_ccfl = new s_rcf (val, col);
      } else {
	p_ccfl->link (val, col);
      }
    }
    // push a new cofactor by size, factor and column
    void push (const t_real val, const t_long col, const t_long row,
	       const t_long cfs, const t_long cfd) {
      // check for consistency
      if ((cfs <= 0) || (col < 0) || (col > cfs)) return;
      if ((cfd <= 0) || (row < 0) || (row < cfd)) return;
      // check for rejection
      if (val == 0.0) return;
      // compute local cofactor column index
      t_long lci = col;
      if (lci == d_fcol) return;
      if (lci > d_fcol) lci--;
      // check for insertion
      if ((cfd == row) && (p_rcfl == nilp)) {
	p_rcfl = new s_rcf (val, lci);
	return;
      }
      if ((cfd == row) && (p_rcfl != nilp)) {
	p_rcfl->link (val, lci);
	return;
      }
      // check if we continue to push in the row list
      if ((cfd < row) && (p_rcfl != nilp)) {
	p_rcfl->prop (val, lci, row, cfs-1, cfd+1);
      }
    }
    // propagate a new cofactor by size, factor and column
    void prop (const t_real val, const t_long col, const t_long row,
	       const t_long cfs, const t_long cfd) {
      s_rcf* rcf = this;
      while (rcf != nilp) {
	rcf->push (val, col, row, cfs, cfd);
	rcf = rcf->p_ccfl;
      }
    }
  };

  // this function compute the row structure determinant
  static t_real crm_rcf_get (s_rcf* rcf, const t_long cfd) {
    // check for nil and initialize
    if ((rcf == nilp) || (cfd <= 0)) return 0.0;
    t_real result = 0.0;
    // loop in the rcf column list
    while (rcf != nilp) {
      if (rcf->p_rcfl == nilp) {
	if ((rcf->d_fcol & 1) == 1) {
	  result -= (cfd == 1) ? rcf->d_fval : 0.0;
	} else {
	  result += (cfd == 1) ? rcf->d_fval : 0.0;
	}
      } else {
	if ((rcf->d_fcol & 1) == 1) {
	  result -= rcf->d_fval * crm_rcf_get (rcf->p_rcfl, cfd - 1);
	} else {
	  result += rcf->d_fval * crm_rcf_get (rcf->p_rcfl, cfd - 1);
	}
      }
      rcf = rcf->p_ccfl;
    }
    return result;
  }

  // -------------------------------------------------------------------------
  // - public section                                                        -
  // -------------------------------------------------------------------------

  // solve a system with the cramer method

  bool Cramer::solve (Rvi& x, const Rmi& m, const Rvi& b) {
    // check for square matrix and vector consistency
    t_long size = x.getsize ();
    if ((m.getrsiz () != size) || (m.getcsiz () != size) || 
	(b.getsize () != size)) {
      throw Exception ("cramer-error", 
		       "incompatible matrix/vector in cramer solver");
    }
    // compute the matrix determinant
    Cramer cs (m);
    t_real md = cs.det ();
    // check for null
    if (md == 0.0) return false;
    // loop in the row
    for (t_long row = 0LL; row < size; row++) {
      // set the unknow cofactor
      cs.set (m, b, row);
      // get the associated determinant
      t_real xd = cs.det ();
      // set the result vector
      x.set (row, xd / md);
    }
    return true;
  }
  
  // -------------------------------------------------------------------------
  // - protected section                                                     -
  // -------------------------------------------------------------------------

  // set the system in unlock mode

  void Cramer::iset (const t_real val, const t_long row, const t_long col) {
    // never set a null value
    if (val == 0.0) return;
    // cannot be called if nil with row other than 0
    if ((p_rcf == nilp) && (row != 0LL)) {
      throw Exception ("cramer-error", "non zero row at first access");
    }
    // check for nil first
    if (p_rcf == nilp) {
      p_rcf = new s_rcf (val, col);
      return;
    }
    // add row 0 element
    if (row == 0LL) {
      p_rcf->link (val, col);
      return;
    }
    // propagate in the rcf
    p_rcf->prop (val, col, row, d_size-1LL, 1LL); 
  }

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create an empty system

  Cramer::Cramer (void) {
    d_size = 0LL;
    p_rcf  = nilp;
  }

  // create a system by matrix

  Cramer::Cramer (const Rmi& m) {
    d_size = 0LL;
    p_rcf  = nilp;
    set (m);
  }

  // create a system by matrix and vectpr

  Cramer::Cramer (const Rmi& m, const Rvi& b, const t_long cfp) {
    d_size = 0LL;
    p_rcf  = nilp;
    set (m, b, cfp);
  }

  // destroy this system

  Cramer::~Cramer (void) {
    delete p_rcf;
  }

  // return the class name

  String Cramer::repr (void) const {
    return "Cramer";
  }

  // reset the system

  void Cramer::reset (void) {
    wrlock ();
    try {
      d_size = 0LL;
      delete p_rcf; p_rcf  = nilp;
      unlock ();
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // set the system by matrix

  void Cramer::set (const Rmi& m) {
    wrlock ();
    Rsi* rso = nilp;
    try {
      // reset the old system
      reset ();
      // check for square matrix
      if (m.issquare () == false) {
	throw Exception ("cramer-error", 
			 "non square matrix for determinant computation");
      }
      // set the size
      d_size = m.getrsiz ();
      // loop in the row matrix
      for (t_long row = 0LL; row < d_size; row++) {
	// get a row sparse object
	rso = m.torso (row);
	// get the rso length
	t_long len = rso->length ();
	// create the initial row cofactors
	for (t_long i = 0LL; i < len; i++) {
	  // get the sparse values
	  t_long col = rso->getidx (i);
	  t_real val = rso->getval (i);
	  // set the value
	  iset (val, row, col);
	}
	delete rso; rso = nilp;
      }
      unlock ();
    } catch (...) {
      delete rso;
      reset  ();
      unlock ();
      throw;
    }
  }

  // set the system by matrix and vector

  void Cramer::set (const Rmi& m, const Rvi& b, const t_long cfp) {
    wrlock ();
    Rsi* rso = nilp;
    try {
      // reset the old system
      reset ();
      // check for square matrix and vector consistency
      d_size = b.getsize ();
      if ((m.getrsiz () != d_size) || (m.getcsiz () != d_size)) {
	throw Exception ("cramer-error", 
			 "incompatible matrix/vector in cramer solver");
      }
      if ((cfp < 0LL) || (cfp >= d_size)) {
	throw Exception ("cramer-error", "invalid cofactor position");
      }	
      // loop in the matrix row
      for (t_long row = 0LL; row < d_size; row++) {
	// get a row sparse object
	rso = m.torso (row);
	// update it with the vector value
	t_real bv = b.get (row);
	rso->set (cfp, bv);
	// get the rso length
	t_long len = rso->length ();
	// create the initial row cofactors
	for (t_long i = 0; i < len; i++) {
	  // get the sparse values
	  t_long col = rso->getidx (i);
	  t_real val = rso->getval (i);
	  // set the value
	  iset (val, row, col);
	}
	delete rso; rso = nilp;
      }
      unlock ();
    } catch (...) {
      delete rso;
      reset  ();
      unlock ();
      throw;
    }
  }

  // compute the determinant of the system

  t_real Cramer::det (void) const {
    rdlock ();
    try {
      t_real result = crm_rcf_get (p_rcf, d_size);
      unlock ();
      return result;
    } catch (...) {
      unlock ();
      throw;
    }
  }
}
