// FILE. . . . . d:/hak/hlt/src/hlt/math/fuzzy/SquareFuzzyMatrix.java
// EDIT BY . . . Hassan Ait-Kaci
// ON MACHINE. . Hp-Zbook
// STARTED ON. . Wed Mar 21 16:55:15 2018

package hlt.math.fuzzy;

import hlt.language.util.IntArrayList;

/**
 * This is a class for a square matrix of fuzzy values (<i>i.e.</i>, of
 * <tt>double</tt>s in the closed continuous interval
 * <tt>[0.0,1.0]</tt>). It extends the class <a
 * href="FuzzyMatrix.html"><tt>FuzzyMatrix</tt></a>.
 *
 * @see         FuzzyMatrix
 *
 * @version     Last modified on Sat Dec 01 20:35:10 2018 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 SquareFuzzyMatrix extends FuzzyMatrix
{
  /**
   * Returns the number of rows and columns.
   */
  public final int rank ()
  {
    return rownum; // or colnum
  }

  /**
   * Create an <tt>N</tt>-by-<tt>N</tt> matrix of <tt>0.0</tt>'s.
   */
  public SquareFuzzyMatrix (int N)
  {
    super(N,N);
  }

  /**
   * Create a square fuzzy matrix matrix with the values in data array
   * of <tt>double</tt>s.
   */
  public SquareFuzzyMatrix (double[][] data)
  {
    super(data);
    
    if (data.length != data[0].length)
      throw new RuntimeException("Attempt to create a square fuzzy matrix with a non-square "
				 +data.length+"x"+data[0].length+" data array ");
  }

  /**
   * Create a square fuzzy matrix matrix with the values in data array
   * of the given <tt>SquareFuzzyMatrix</tt>.
   */
  public SquareFuzzyMatrix (SquareFuzzyMatrix M)
  {
    super(M.data);
  }

  /**
   * Create a square fuzzy matrix with the given data array if
   * <tt>copy</tt> is <tt>false</tt>, or with a new array containing the
   * values in <tt>data</tt> array if <tt>copy</tt> is <tt>true</tt>, in
   * which case it also verifies that all the values in <tt>data</tt>
   * are all within <tt>[0.0,0.1]</tt>.
   * 
   */
  public SquareFuzzyMatrix (double[][] data, boolean copy)
  {
    super(data,copy);

    if (data.length != data[0].length)
      throw new RuntimeException("Attempt to create a square fuzzy matrix with a non-square "
				 +data.length+"x"+data[0].length+" data array ");
  }

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

  /**
   * Create and return a new <tt>rank</tt>-by-<tt>rank</tt> identity matrix.
   */
  public static SquareFuzzyMatrix identity (int rank)
  {
    SquareFuzzyMatrix I = new SquareFuzzyMatrix(rank);

    for (int i = 0; i < rank; i++)
      I.data[i][i] = 1.0;

    return I;
  }

  /**
   * Create and return a random <tt>rank</tt>-by-<tt>rank</tt> square matrix
   * with values in <tt>[0.0,1.0]</tt>.
   */
  public static SquareFuzzyMatrix random (int rank)
  {
    SquareFuzzyMatrix M = new SquareFuzzyMatrix(rank);

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

    return M;
  }

  /**
   * Returns this matrix' in-place transpose [modifies this
   * <tt>SquareFuzzyMatrix</tt>'s <tt>data</tt> array in place]. This
   * method is not defined for the superclass <tt>FuzzyMatrix</tt> since
   * it makes sense only when it is square. (However, non-destructive
   * transpose is defined in <tt>FuzzyMatrix</tt> since it makes sense
   * for any rectangular matrice.)
   */
  public SquareFuzzyMatrix i_transpose ()
  {
    int rank = rank();
    
    for (int i = 0; i < rank; i++)
      for (int j = i+1; j < rank; j++)
	{
	  double tmp = data[i][j];
	  data[i][j] = data[j][i];
	  data[j][i] = tmp;
	}

    return this;
  }

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

  /**
   * Returns a new <tt>SquareFuzzyMatrix</tt> that is the reflexive
   * closure of this <tt>SquareFuzzyMatrix</tt>.
   */
  public SquareFuzzyMatrix reflexive_closure ()
  {
    return (new SquareFuzzyMatrix(data)).i_reflexive_closure();
  }

  /**
   * Sets this square fuzzy matrix to its reflexive closure and returns it(self).
   */
  public SquareFuzzyMatrix i_reflexive_closure ()
  {
    for (int i=0; i < rank(); i++)
      data[i][i] = 1.0;

    return this;
  }

  /**
   * Returns a new <tt>SquareFuzzyMatrix</tt> that is the symmetric
   * closure of this <tt>SquareFuzzyMatrix</tt>.
   */
  public SquareFuzzyMatrix symmetric_closure ()
  {
    return (new SquareFuzzyMatrix(data)).i_symmetric_closure();
  }

  /**
   * Sets this <tt>SquareFuzzyMatrix</tt> to its symmetric closure and returns it(self).
   */
  public SquareFuzzyMatrix i_symmetric_closure ()
  {
    int rank = rank();
    
    for (int i = 0; i < rank; i++)
      for (int j = i+1; j < rank; j++)
	{
	  data[i][j] = sup(data[i][j],data[j][i]);
	  data[j][i] = data[i][j];
	}

    return this;
  }

  /**
   * Returns and <tt>SquareFuzzyMatrix</tt> that is the transitive
   * closure of this <tt>SquareFuzzyMatrix</tt>.
   */
  public SquareFuzzyMatrix transitive_closure ()
  {
    return (new SquareFuzzyMatrix(data)).i_transitive_closure();
  }

  /**
   * Sets this <tt>SquareFuzzyMatrix</tt> to its transitive closure and
   * returns it(self). It computes it using the so-called <i>cubic</i>
   * method.
   */
  public SquareFuzzyMatrix i_transitive_closure ()
  {
    int rank = rank();

    for (int k = 0; k < rank; k++)
      for (int i = 0; i < rank; i++)
	for (int j = 0; j < rank; j++)
	  data[i][j] = sup(data[i][j],inf(data[i][k],data[k][j]));

    return this;
  }

  /**
   * Returns a <tt>SquareFuzzyMatrix</tt> that is the similarity closure
   * of this <tt>SquareFuzzyMatrix</tt>.
   */
  public SquareFuzzyMatrix similarity_closure ()
  {
    return reflexive_closure().i_symmetric_closure().i_transitive_closure();
  }

  /**
   * Sets this  <tt>SquareFuzzyMatrix</tt> to its similarity closure and returns it(self).
   */
  public SquareFuzzyMatrix i_similarity_closure ()
  {
    return i_reflexive_closure().i_symmetric_closure().i_transitive_closure();
  }

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

  /**
   * When this <tt>SquareFuzzyMatrix</tt> is a similarity relation on
   * the set <tt>{0,...,N}</tt>, <tt>partition(cut)</tt> (where
   * <tt>cut</tt> is a <tt>double</tt> in <tt>[0.0,1.0]</tt>)
   * returns an array of <tt>N</tt> <tt>IntArrayList</tt>s each
   * containing the indices in the set <tt>{0,...,N}</tt> constituting a
   * similarity class at fuzzy approximation degree greater than or
   * equal to <tt>cut</tt> (<i>i.e.</i>, it is a partition of the
   * set <tt>{0,...,N}</tt> for similarity degrees that are not less
   * than <tt>cut</tt>). Each <tt>IntArrayList</tt> at index
   * <tt>i</tt> contains the indices of the elements in the class
   * <tt>[i]</tt> ordered in ascending order. Each class is uniquely
   * represented and shared by all indices in the class (it is the same
   * <tt>IntArrayList</tt> object) for all indices it contains. For
   * example, if for <tt>N=6</tt>, the matrix is:
   *
   * <p>
   *
   * <pre>
   *      0   1   2   3   4   5
   *     --- --- --- --- --- ---
   * 0 | 1.0 0.5 0.0 0.5 0.4 0.2
   * 1 | 0.5 1.0 0.0 0.5 0.4 0.0
   * 2 | 0.0 0.0 1.0 0.0 0.0 0.0
   * 3 | 0.5 0.5 0.0 1.0 0.4 1.2
   * 4 | 0.4 0.4 0.0 0.4 1.0 0.2
   * 5 | 0.2 0.2 0.0 0.2 0.2 1.0
   * </pre>
   * then its <tt>partition(0.4)</tt> array is:
   * <pre>
   * 0: A
   * 1: A
   * 2: B
   * 3: A
   * 4: A
   * 5: C
   * </pre>
   * where <tt>A=[0,1,3,4]</tt>, <tt>B=[2]</tt>, and <tt>C=[5]</tt>.
   */
  public IntArrayList[] partition (double cut)
  {
    int rank = rank();
    
    // The array of rank classes
    IntArrayList[] classes = new IntArrayList[rank];

    // initialize all classes to singletons
    for (int i=0; i < rank; i++)
      {
	classes[i] = new IntArrayList();
	classes[i].add(i);
      }

    // sweep through the upper right triangle and merge the partitions
    // of equivalent pairs over or at the cut
    for (int i=0; i < rank-1; i++)
      // {
	for (int j=i+1; j < rank; j++)
	  if (data[i][j] >= cut)
	    // merge the classes at indices i and j and sets all related elements classes
	    merge(classes,i,j);

    return classes;
  }

  /**
   * This merges the classes <tt>classes[i]</tt> and <tt>classes[j]</tt>
   * into a new class keeping it sorted in increasing order, and sets
   * <tt>classes[k]</tt> to be this new merged class for all indices
   * <tt>k</tt> in this class.
   */
  private void merge (IntArrayList[] classes, int i, int j)
  {
    IntArrayList c1 = classes[i];
    IntArrayList c2 = classes[j];

    if (c1 != c2) // otherwise this would keep going
      if (c2.size() < c1.size())
	{
	  for (int k=0; k < c2.size(); k++)
	    insertIndex(c2.get(k),c1);
	  // update all appropriate classes to the merged class
	  for (int k=0; k < c1.size(); k++)
	    classes[c1.get(k)] = c1;
	}
      else
	{
	  for (int k=0; k < c1.size(); k++)
	    insertIndex(c1.get(k),c2);
	  // update all appropriate classes to the merged class
	  for (int k=0; k < c2.size(); k++)
	    classes[c2.get(k)] = c2;
	}
  }

  /**
   * Adds <tt>index</tt> to <tt>orderedClass</tt> keeping it in
   * increasing order.
   */
  private void insertIndex (int index, IntArrayList orderedClass)
  {
    int size = orderedClass.size();
    int i = 0;
    while (i < size && orderedClass.get(i) < index)
      i++;

    if (index != orderedClass.get(i))
      orderedClass.add(i,index);
  }

}

