// FILE. . . . . d:/hak/hlt/src/hlt/math/fuzzy/FuzzyMatrix.java
// EDIT BY . . . Hassan Ait-Kaci
// ON MACHINE. . Hp-Zbook
// STARTED ON. . Wed Mar 21 11:24:34 2018

package hlt.math.fuzzy;

// For the set of fuzzy degrees used by this fuzzy matrix (see end of file):
import hlt.language.util.DoubleArrayList;

/**
 * This is a generic class defining linear algebra operations on
 * matrices of fuzzy degrees in <tt>[0.0,1.0]</tt> in terms of two
 * redefinable binary fuzzy operations on <tt>[0.0,1.0]</tt>: one called
 * <tt>sup</tt> (standing for <i>supremum</i>) as an additive law; and
 * one called <tt>inf</tt> (standing for <i>infimum</i>) as a
 * multiplicative law. If not overridden, the default implementations
 * for <tt>sup</tt> and <tt>inf</tt> are respectively <tt>Math.max</tt>
 * and <tt>Math.min</tt>.
 *
 * @see         FuzzyAlgebra
 * @see         SquareFuzzyMatrix
 *
 * @version     Last modified on Tue Apr 30 07:05:27 2019 by hak
 * @author      <a href="mailto:hak@acm.org">Hassan A&iuml;t-Kaci</a>
 * @copyright   &copy; <a href="http://www.hassan-ait-kaci.net/">by the author</a>
 */

public class FuzzyMatrix extends FuzzyAlgebra
{
  /* ******************************************************************** */
  /*                                FIELDS                                */
  /* ******************************************************************** */

  protected final int rownum;        // number of rows
  protected final int colnum;        // number of columns
  protected final double[][] data;   // rownum-by-colnum array

  /**
   * Returns the number of rows
   */
  public final int rownum ()
  {
    return rownum;
  }

  /**
   * Returns the number of columns
   */
  public final int colnum ()
  {
    return colnum;
  }

  /**
   * Returns the data array
   */
  public final double[][] data ()
  {
    return data;
  }

  /* ************************************************************************ */

  /**
   * <a id="degrees"/>
   * Contains the set of degrees of this fuzzy matrix in ascending order.
   */
  final protected DoubleArrayList degrees = new DoubleArrayList(); // the degrees of this fuzzy matrix


  /**
   * Just a convenience to remind that making a copy is in effect (can
   * then be used instead of <tt>true</tt>).
   */
  static boolean COPY = true;

  /* ******************************************************************** */
  /*                             CONSTRUCTORS                             */
  /* ******************************************************************** */

  /**
   * Create a new fuzzy matrix with a new
   * <tt>rownum</tt>-by-<tt>colnum</tt> data array of <tt>0.0</tt>'s.
   */
  public FuzzyMatrix (int rownum, int colnum)
  {
    this.rownum = rownum;
    this.colnum = colnum;
    data = new double[rownum][colnum];
  }

  /**
   * Create a new <tt>FuzzyMatrix</tt> sharing the given array
   * <tt>data</tt> of <tt>double</tt>s in <tt>[0.0,0.1]</tt> (trusting
   * them to be so).
   */
  public FuzzyMatrix (double[][] data)
  {
    this(data,!COPY);
  }

  /**
   * If the flag <tt>copy</tt> is <tt>false</tt>, create a fuzzy matrix
   * sharing the given data array; otherwise, create a fuzzy matrix with
   * a new copy of the data array, in which case it also verifies that
   * all the <tt>double</tt> degrees in <tt>data</tt> are indeed in the
   * continuous interval <tt>[0.0,0.1]</tt>.
   * 
   */
  public FuzzyMatrix (double[][] data, boolean copy)
  {
    rownum = data.length;
    colnum = data[0].length;

    if (copy)
      {
	this.data = new double[rownum][colnum];
	for (int i = 0; i < rownum; i++)
	  for (int j = 0; j < colnum; j++)
	    this.data[i][j] = FuzzyTools.checkFuzzyValue(data[i][j]);
      }
    else
      this.data = data;
  }

  /* ******************************************************************** */
  /*                               METHODS                                */
  /* ******************************************************************** */

  /**
   * Returns the set of degrees of this fuzzy matrix.
   */
  public final DoubleArrayList degrees ()
  {
    if (degrees.isEmpty())
      computeDegrees();

    return degrees;
  }

  /**
   * Recomputes and returns the set of degrees of this fuzzy matrix.
   */
  public final void computeDegrees ()
  {
    degrees.setSize(0); // erase all entries if any

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < colnum; j++)
	addDegree(data[i][j]);
  }

  /**
   * Inserts a double <tt>degree</tt> into this matrix' set of degree
   * keeping this set sorted in increasing order, and returns the set.
   */
  protected final DoubleArrayList addDegree (double degree)
  {
    int i = 0;

    while (i < degrees.size() && degree > degrees.get(i))
      i++;

    if (degree != degrees.get(i))
      degrees.add(i,degree); // will shift degrees at indices to the right and insert degree at i

    return degrees;
  }

  /* ************************************************************************ */

  /**
   * Returns a new <tt>FuzzyMatrix</tt> with a data array that is
   * a copy of this one's.
   */
  public FuzzyMatrix copy ()
  {
    return copy(data);
  }

  /**
   * Returns a new <tt>FuzzyMatrix</tt> having a different data array
   * than that of the given one, but containg equal array degrees as the
   * given one.
   */
  public FuzzyMatrix copy (FuzzyMatrix M)
  {
    return copy(M.data);
  }

  /**
   * Returns a new <tt>FuzzyMatrix</tt> whose data array is a copy
   * of the given <tt>data</tt> array.
   */
  public FuzzyMatrix copy (double[][] data)
  {
    return new FuzzyMatrix(data,COPY);
  }

  /* ******************************************************************** */

  /**
   * Sets the <tt>i,j</tt> entry in <tt>data</tt> to <tt>degree</tt> (NB:
   * <tt>i</tt> and <tt>j</tt> indices start at <tt>1</tt> and end at
   * <tt>rownum</tt> and <tt>colnum</tt>). Throws a
   * <tt>NonFuzzyValueException</tt> exception if <tt>degree</tt> is not
   * within <tt>[0.0,1.0]</tt>.
   */
  public FuzzyMatrix set (int i, int j, double degree)
  {
    if (i < 1 || i > rownum || j < 1 || j > colnum)
      throw new RuntimeException("Row/Column index out of bounds: ("+i+","+j+")");    

    // NB: adjusting indices to start at 0 for internal data array:
    data[i-1][j-1] = FuzzyTools.checkFuzzyValue(degree);

    return this;
  }

  /**
   * Returns <tt>true</tt> if this fuzzy matrix is pointwise-equal to
   * the given one.
   */
  public boolean equals (FuzzyMatrix B)
  {
    FuzzyMatrix A = this;

    if (A == B)
      return true;

    if (A.data == B.data)
      return true;

    if (B.rownum != A.rownum || B.colnum != A.colnum)
      throw new RuntimeException("Incompatible matrix dimensions: <"
				 +A.rownum+","+A.colnum+"> =/= <"+B.rownum+","+B.colnum+">");


    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < colnum; j++)
	if (A.data[i][j] != B.data[i][j])
	  return false;

    return true;
  }

  /**
   * This is an in-place pointwise update that modifies each entry of
   * this fuzzy matrix to the value of the corresponding entry in the
   * given matrix.
   */
  public FuzzyMatrix update (FuzzyMatrix M)
  {
    if (rownum != M.rownum || colnum != M.colnum)
      throw new RuntimeException("Incompatible matrix dimensions: <"
				 +rownum+","+colnum+"> =/= <"+M.rownum+","+M.colnum+">");

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < colnum; j++)
	data[i][j] = M.data[i][j];

    return this;
  }

  /* ******************************************************************** */

  /**
   * Returns a random <tt>double</tt> in <tt>[0.0,1.0]</tt>.
   */
  public static double random ()
  {
    double r = Math.random(); // this returns a double in [0.,1.0) - does not include 1.0

    // when r is kinda close to 1.0 we toss a coin heavily biased towards not returning 1.0
    if (Math.ceil(10*r) == 10.0)
      if (Math.random() > 0.75)
	return 1.0;

    // when r is kinda close to 0.0 we toss a coin heavily biased towards not returning 0.0
    if (Math.floor(10*r) == 0.0)
      if (Math.random() > 0.75)
	return 0.0;

    // otherwise return r as is
    return r;
  }

  /**
   * Create and return a random <tt>rownum</tt>-by-<tt>colnum</tt> matrix with
   * degrees in <tt>[0.0,1.0]</tt>
   */
  public static FuzzyMatrix random (int rownum, int colnum)
  {
    FuzzyMatrix M = new FuzzyMatrix(rownum,colnum);

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < colnum; j++)
	M.data[i][j] = FuzzyMatrix.random();

    return M;
  }

  /* ******************************************************************** */

  /**
   * Returns a new fuzzy matrix corresponding the transpose of this one.
   */
  public FuzzyMatrix transpose ()
  {
    FuzzyMatrix M = new FuzzyMatrix(colnum,rownum);

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < colnum; j++)
	M.data[j][i] = data[i][j];

    return M;
  }

  /**
   * Returns a new <tt>FuzzyMatrix</tt> equal to <tt>this</tt> plus
   * <tt>M</tt> (does not modify <tt>this</tt>).
   */
  public FuzzyMatrix plus (FuzzyMatrix M)
  {
    if (rownum != M.rownum || colnum != M.colnum)
      throw new RuntimeException("Incompatible matrix dimensions: <"
				 +rownum+","+colnum+"> =/= <"+M.rownum+","+M.colnum+">");

    FuzzyMatrix N = new FuzzyMatrix(rownum,colnum);

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < colnum; j++)
	N.data[i][j] = sup(data[i][j],M.data[i][j]);

    return N;
  }

  /**
   * Modifies in place the entries of <tt>this</tt> to those of
   * <tt>this</tt> plus <tt>M</tt>.
   */
  public FuzzyMatrix i_plus (FuzzyMatrix M)
  {
    if (rownum != M.rownum || colnum != M.colnum)
      throw new RuntimeException("Incompatible matrix dimensions: <"
				 +rownum+","+colnum+"> =/= <"+M.rownum+","+M.colnum+">");

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < colnum; j++)
	data[i][j] = sup(data[i][j],M.data[i][j]);

    return this;
  }

  /**
   * Returns a new <tt>FuzzyMatrix</tt> equal to <tt>this</tt> times
   * <tt>M</tt> (does not modify <tt>this</tt>).
   */
  public FuzzyMatrix times (FuzzyMatrix M)
  {
    if (colnum != M.rownum)
      throw new RuntimeException("Incompatible col/rom matrix dimensions: "+colnum+" =/= "+M.rownum);

    FuzzyMatrix N = new FuzzyMatrix(rownum,M.colnum);

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < M.colnum; j++)
	{
	  double accumulator = 0.0;
	  for (int k = 0; k < colnum; k++)
	    accumulator = sup(accumulator,inf(data[i][k],M.data[k][j]));
	  N.data[i][j] = accumulator;
	}

    return N;
  }

  /**
   * Modifies in place the entries of <tt>this</tt> to those of
   * <tt>this</tt> times <tt>M</tt>.
   */
  public FuzzyMatrix i_times (FuzzyMatrix M)
  {
    if (colnum != M.rownum)
      throw new RuntimeException("Incompatible col/rom matrix dimensions: "+colnum+" =/= "+M.rownum);

    for (int i = 0; i < rownum; i++)
      for (int j = 0; j < M.colnum; j++)
	{
	  double accumulator = 0.0;
	  for (int k = 0; k < colnum; k++)
	    accumulator = sup(accumulator,inf(data[i][k],M.data[k][j]));
	  data[i][j] = accumulator;
	}

    return this;
  }

  /* ************************************************************************ */

  /**
   * Default <tt>double</tt> precision for <tt>printf</tt>.
   */
  public static String precision = "%9.4f ";

  /**
   * Prints this fuzzy matrix to standard output
   */
  public void show ()
  {
    for (int i = 0; i < rownum; i++)
      {
	for (int j = 0; j < colnum; j++)
	  System.out.printf(precision, data[i][j]);
	System.out.println();
      }
  }

}
