// ---------------------------------------------------------------------------
// - Algebra.cpp                                                             -
// - afnix:mth module - algebraic algorithm 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-2013 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Real.hpp"
#include "Math.hpp"
#include "Utility.hpp"
#include "Algebra.hpp"
#include "Exception.hpp"
 
namespace afnix {

  // -------------------------------------------------------------------------
  // - private section                                                        -
  // -------------------------------------------------------------------------

  // this procedure compute the absolute value
  static t_real abs (const t_real x) {
    return x < 0.0 ? -x : x;
  }

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

  // copy a vector into another one

  void Algebra::cpy (Rvi& r, const Rvi& x) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if (x.getsize () != size) {
      throw Exception ("algebra-error", "incompatible size in vector copy");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i));
  }

  // add a vector with a scalar

  void Algebra::add (Rvi& r, const Rvi& x, const t_real s) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if (x.getsize () != size) {
      throw Exception ("algebra-error", "incompatible size in vector add");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i) + s);
  }

  // add a vector with another one

  void Algebra::add (Rvi& r, const Rvi& x, const Rvi& y) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if ((x.getsize () != size) || (y.getsize () != size)) {
      throw Exception ("algebra-error", "incompatible size in vector add");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i) + y.get (i));
  }

  // add a vector with another scaled one

  void Algebra::add (Rvi& r, const Rvi& x, const Rvi& y, const t_real s) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if ((x.getsize () != size) || (y.getsize () != size)) {
      throw Exception ("algebra-error", "incompatible size in vector add");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i) + (y.get (i) * s));
  }

  // substract a vector with a scalar

  void Algebra::sub (Rvi& r, const Rvi& x, const t_real s) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if (x.getsize () != size) {
      throw Exception ("algebra-error", "incompatible size in vector sub");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i) - s);
  }

  // substract a vector with another one

  void Algebra::sub (Rvi& r, const Rvi& x, const Rvi& y) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if ((x.getsize () != size) || (y.getsize () != size)) {
      throw Exception ("algebra-error", "incompatible size in vector sub");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i) - y.get (i));
  }

  // multiply a vector with a scalar

  void Algebra::mul (Rvi& r, const Rvi& x, const t_real s) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if (x.getsize () != size) {
      throw Exception ("algebra-error", "incompatible size in vector mul");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i) * s);
  }

  // multiply a vector with another one

  void Algebra::mul (Rvi& r, const Rvi& x, const Rvi& y) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if ((x.getsize () != size) || (y.getsize () != size)) {
      throw Exception ("algebra-error", "incompatible size in vector mul");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, x.get (i) * y.get (i));
  }

  // add equal with a vector

  void Algebra::aeq (Rvi& r, const Rvi& x) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if (x.getsize () != size) {
      throw Exception ("algebra-error", "incompatible size in vector add");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, r.get (i) + x.get (i));
  }

  // add equal with a scaled vector

  void Algebra::aeq (Rvi& r, const Rvi& x, const t_real s) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if (x.getsize () != size) {
      throw Exception ("algebra-error", "incompatible size in vector aeq");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, r.get (i) + (x.get (i) * s));
  }

  // rescale equal with a vector

  void Algebra::req (Rvi& r, const Rvi& x, const t_real s) {
    // extract operating size
    t_long size = r.getsize ();
    // check target size
    if (x.getsize () != size) {
      throw Exception ("algebra-error", "incompatible size in vector req");
    }
    // check for null
    if (size == 0) return;
    // perform the operation
    for (t_long i = 0; i < size; i++) r.set (i, (r.get (i) * s) + x.get (i));
  }

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

  // add a matrix with another one

  void Algebra::add (Rmi& mr, const Rmi& mx, const Rmi& my) {
    // extract operating size
    t_long rows = mr.getrsiz ();
    t_long cols = mr.getcsiz ();
    // check target size
    if ((mx.getrsiz () != rows) || (my.getrsiz () != rows) ||
	(mx.getcsiz () != cols) || (my.getcsiz () != cols)) {
      throw Exception ("algebra-error", "incompatible size in matrix add");
    }
    // check for null
    if ((rows == 0) || (cols == 0)) return;
    // perform the operation
    for (t_long i = 0; i < rows; i++) {
      for (t_long j = 0; j < cols; j++) {
	t_real s = mx.get (i, j) + my.get (i, j);
	mr.set (i, j, s);
      }
    }	
  }

  // substract a matrix with another one

  void Algebra::sub (Rmi& mr, const Rmi& mx, const Rmi& my) {
    // extract operating size
    t_long rows = mr.getrsiz ();
    t_long cols = mr.getcsiz ();
    // check target size
    if ((mx.getrsiz () != rows) || (my.getrsiz () != rows) ||
	(mx.getcsiz () != cols) || (my.getcsiz () != cols)) {
      throw Exception ("algebra-error", "incompatible size in matrix sub");
    }
    // check for null
    if ((rows == 0) || (cols == 0)) return;
    // perform the operation
    for (t_long i = 0; i < rows; i++) {
      for (t_long j = 0; j < cols; j++) {
	t_real s = mx.get (i, j) - my.get (i, j);
	mr.set (i, j, s);
      }
    }	
  }

  // multiply a matrix with a vector and a scaling factor

  void Algebra::mul (Rvi& r, const Rmi& m, const Rvi& x, const t_real s) {
    // extract operating size
    t_long size = r.getsize ();
    t_long rows = m.getrsiz ();
    t_long cols = m.getcsiz ();
    // check target size
    if ((size != rows) || (x.getsize () != cols)) {
      throw Exception ("algebra-error", "incompatible size in matrix mul");
    }
    // check for null
    if ((rows == 0) || (cols == 0)) return;
    // perform the operation
    for (t_long i = 0; i < rows; i++) {
      t_real v = 0.0;
      for (t_long j = 0; j < cols; j++) v += m.get (i, j) * x.get (j);
      r.set (i, v*s);
    }
  }

  // multiply two matrices

  void Algebra::mul (Rmi& mr, const Rmi& mx, const Rmi& my) {
    // extract operating size
    t_long rows = mr.getrsiz ();
    t_long cols = mr.getcsiz ();
    t_long size = my.getrsiz ();
    // check target size
    if ((mx.getrsiz () != rows) || (my.getcsiz () != cols) ||
	(mx.getcsiz () != size)) {
      throw Exception ("algebra-error", "incompatible size in matrix mul");
    }
    // check for null
    if ((rows == 0) || (cols == 0)) return;
    // perform the multiplcation
    for (t_long i = 0; i < rows; i++) {
      for (t_long j = 0; j < cols; j++) {
	t_real s = 0.0;
	for (t_long k = 0; k < size; k++) s += mx.get (i, k) * my.get (k, j);
	mr.set (i, j, s);
      }
    }	
  }

 // generate a random vector

  void Algebra::random (Rvi& r, const t_real rmin, const t_real rmax) {
    // check size
    t_long size = r.getsize ();
    if (size == 0) return;
    // check bound
    if ((rmin < 0.0) || (rmax < 0.0) || (rmax < rmin)) {
      throw Exception ("vector-error", "invalid random ordering");
    }
    // clear the vector
    r.clear ();
    // fill the vector by value
    for (t_long i = 0; i < size; i++) {
      t_real rval = rmin + Utility::realrnd (true) * (rmax - rmin);
      r.set (i, rval);
    }
  }

  // generate a random matrix

  void Algebra::random (Rmi& mr, const t_real rmin, const t_real rmax) {
    // extract rows and cols
    t_long rows = mr.getrsiz ();
    t_long cols = mr.getcsiz ();
    if ((rows == 0) || (cols == 0)) return;
    // check bound
    if (rmax < rmin) {
      throw Exception ("matrix-error", "invalid random ordering");
    }
    // clear the matrix
    mr.clear ();
    // fill the matrix by value
    for (t_long i = 0; i < rows; i++) {
      for (t_long j = 0; j < cols; j++) {
	t_real rval = rmin + Utility::realrnd (true) * (rmax - rmin);
	mr.set (i, j, rval);
      }
    }
  }

  // generate a sparse random matrix

  void Algebra::sparse (Rmi& mr, const t_real rmin, const t_real rmax,
			const t_long nzsz) {
    // extract rows and cols
    t_long rows = mr.getrsiz ();
    t_long cols = mr.getcsiz ();
    if ((rows == 0) || (cols == 0)) return;
    // check bound
    if (rmax < rmin) {
      throw Exception ("matrix-error", "invalid random ordering");
    }
    // check for valid non zero size
    if ((nzsz < 0) || (nzsz > (rows * cols))) {
      throw Exception ("matrix-error", "invalid non-zero size in sparse");
    }
    // clear the matrix
    mr.clear ();
    // fill the matrix by value
    for (t_long k = 0; k < nzsz; k++) {
      // generate coordinates
      t_long i = Utility::longrnd (rows);
      t_long j = Utility::longrnd (cols);
      // check for non nil
      if (mr.get (i, j) != 0.0) {
	k--;
	continue;
      }
      // generate value and set
      t_real val = rmin + Utility::realrnd (true) * (rmax - rmin);
      mr.set (i, j, val);
    }
  }
  
  // force a matrix to be diagonaly dominant

  void Algebra::toddom (Rmi& mr, const t_real df) {
    // extract rows and cols
    t_long rows = mr.getrsiz ();
    t_long cols = mr.getcsiz ();
    // loop in the matrix
    for (t_long i = 0; i < rows; i++) {
      t_real s = 0.0;
      for (t_long j = 0; j < cols; j++) {
	if (i != j) s+= abs (mr.get (i,j));
      }
      if (s >= mr.get (i, i)) mr.set (i, i, s + df);
    }
  }
}
