// FILE. . . . . d:/hak/hlt/src/hlt/fot/fuz/SignatureSimilarity.java
// EDIT BY . . . Hassan Ait-Kaci
// ON MACHINE. . Hak-Laptop
// STARTED ON. . Sat Jul 14 07:23:57 2018

/** *
 * @version     Last modified on Mon Dec 16 09:57:31 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>
 */

package hlt.fot.fuz;

import hlt.fot.*;

import hlt.math.fuzzy.FuzzyMatrix;
import hlt.math.fuzzy.FuzzyMatrix;

import hlt.language.util.Stack;
import hlt.language.util.Queue;
import hlt.language.util.SetOf;

import java.util.ArrayList; // hlt.language.util.ArrayList causes a bug - check this out later
import java.util.HashMap;
import java.util.Iterator;

/**
 * This is a class for a similarity on a first-order term constructor
 * signature. It extends the class <a
 * href="../../math/fuzzy/FuzzyMatrix.html"><tt>FuzzyMatrix</tt></a>
 * with fields and operations dealing with the functor names and arities
 * and how argument positions of each pair of similar functors
 * correspond. It also provides fuzzy <a
 * href="#unification">unification</a> and <a
 * href="#generalization">generalization</a> lattice operations modulo
 * the subsumption ordering that the similarity and argument-position
 * correspondances induce on <tt>FirstOrderTerm</tt>s. The formal
 * details are reported in <a
 * href="https://hassan-ait-kaci.net/pdf/fuzfotlat-preprint.pdf">this
 * article</a> and its references.
 *
 * @see         ../Signature
 * @see         ../FirstOrderTerm
 * @see		SimilarFunctorSignature
 * @see         ../../math/fuzzy/FuzzyMatrix
 */

public class SignatureSimilarity extends FuzzyMatrix
{
  /* ************************************************************************ */
  /**
   * <center><b>FOT COMPONENTS ACCESSORS</b></center>
   */

  /**
   * Contains the functors. Initialized in the constructor
   * <tt>SignatureSimilarity(SimilarFunctorSignature signature,
   * double[][] data)</tt>.
   */
  private SimilarFunctorSignature signature;

  /**
   * Returns the signature of this similarity.
   */
  public SimilarFunctorSignature signature ()
  {
    return signature;
  }

  /**
   * Returns the functor at index <tt>functorIndex</tt> in this
   * similarity's <tt>signature</tt>, where <b></b><tt>1</tt> &le;
   * <tt>functorIndex</tt> &le; <tt>signature.size()</tt>. If this
   * signature is empty, returns <tt>null</tt>. <b>Important</b>: for a
   * functor <tt>f</tt>, the index of <tt>f</tt> in its signature
   * (<i>i.e.</i>, the value of <tt>f.index()</tt>) is numbered from
   * <tt>0</tt> to <tt>signature.size()-1</tt>, whereas
   * <tt>this.functor(functorIndex)</tt> and
   * <tt>signature.functor(functorIndex)</tt> expect their argument
   * numbered from <tt>1</tt> to <tt>signature.size()</tt>. In other
   * words, <tt>this.functor(functorIndex).index()</tt> =
   * <tt>functorIndex-1</tt>.
   */
  public Functor functor (int functorIndex)
  {
    return signature.functor(functorIndex);
  }

  /**
   * Returns the functor called <tt>name</tt> in this similarity's
   * signature.
   */
  public Functor functor (String name)
  {
    return signature.functor(name);
  }

  /* ************************************************************************ */
  /**
   * <center><b>CLASS COMPONENTS ACCESSORS</b></center>
   */
  /**
   * This keeps a global record of a unique representation per each
   * similarity class for this similarity as an <tt>ArrayList</tt> of
   * sets of functors. It is initialized in the
   * <tt>SignatureSimilarity</tt> constructor, and is updated by need
   * upon construction of a new functor similarity class.
   */
  private ArrayList similarityClasses;

  /**
   * This is a private cache for a set-of-functors object representing
   * the similariry class of functors consisting of the full signature,
   * and is its unique representative.
   */
  private SetOf fullSignatureClass;

  /**
   * This is a private tool that returns the unique set of functors that
   * is the full signature, creating it and recording it if needed. It
   * is invoked by need upon building the functor partition consisting
   * of the full signature in <tt>fullSignaturePartition()</tt>.
   */
  private SetOf fullSignatureClass ()
  {
    // if already built, return it
    if (fullSignatureClass != null)
      return fullSignatureClass;

    // otherwise, create it
    fullSignatureClass = new SetOf(signature.functors());
    // populate it will all the existing functors
    for (int i = 1; i <= signature.size(); i++)
      fullSignatureClass.add(signature.functor(i));
    // record it
    sayIfTracing(3,
		 "recording full signature class "+fullSignatureClass+
		 " in classes "+similarityClasses);
    similarityClasses.add(fullSignatureClass);
    sayIfTracing(3,
		 "now classes = "+similarityClasses);

    // and return it
    return fullSignatureClass;
  }

  /**
   * This is a private tool that returns the (unique) canonical
   * similarity class corresponding to the specified set of functors
   * <tt>similarityClass</tt>. It is invoked in the private tool
   * <tt>partition(degreeIndex)</tt>.
   */
  private SetOf canonicalClass (SetOf similarityClass)
  {
    int degreeIndex = similarityClasses.indexOf(similarityClass);

    if (degreeIndex != -1)
      {
	sayIfTracing(2,
		     "found canonical class for "+similarityClass+
		     " in known classes "+similarityClasses+
		     " at index "+degreeIndex+": "+(SetOf)similarityClasses.get(degreeIndex));

	// found it; return it
	return (SetOf)similarityClasses.get(degreeIndex);
      }

    // otherwise, add it as a canonical class and return it
    sayIfTracing(2,
		 "recording it as a new signature class "+similarityClass+" in classes "+similarityClasses);

    similarityClasses.add(similarityClass);

    sayIfTracing(2,
		 "now classes = "+similarityClasses);
    return similarityClass;
  }

  /* ************************************************************************ */
  /**
   * <center><b>CONSTRUCTOR</b></center>
   */
  
  /**
   * Constructs a <tt>SignatureSimilarity</tt> on the given signature
   * using the specified square matrix of fuzzy degrees.
   */ 
  public SignatureSimilarity (SimilarFunctorSignature signature, double[][] data)
  {
    super(data);
    this.signature = signature;
    signature.setSimilarity(this);
    similarityClasses = new ArrayList();
    
    // // This is just to see the seed pairs as a matrix before computing the closure:
    // System.out.println(">>> Declared pairs of functors in this signature:\n");
    // show();

    this.i_similarity_closure();
    computeDegrees();
  }

  /* ************************************************************************ */
  /**
   * <center><b>PARTITIONS</b></center>
   */

  /**
   * A private cache for the one-element array that is the unique
   * representative of the partition consisting of the full functor
   * signature.
   */
  private SetOf[] fullSignaturePartition;

  /**
   * This is a private tool that returns the partition of the functor
   * signature as a unique one-element array containing only one class
   * that is unique set of functors representing the full signature,
   * creating it and recording it if needed. It is invoked by need upon
   * building all the partitions when invoking
   * <tt>partition(degreeIndex)</tt> for each of the indices in the
   * <tt>degrees</tt> array.
   */
  private SetOf[] fullSignaturePartition ()
  {
    if (fullSignaturePartition != null)
      return fullSignaturePartition;

    fullSignaturePartition = new SetOf[signature.size()];

    for (int i = 0; i < fullSignaturePartition.length; i++)
      fullSignaturePartition[i] = fullSignatureClass();

    return fullSignaturePartition;
  }

  /**
   * An array of caches for the partition arrays per degree so
   * partitions share the same unique set objects many times over. This
   * is an array of length <tt>degrees.size()</tt> of arrays of length
   * <tt>signature.size()</tt> of sets of functors representing the
   * <tt>&alpha;</tt>-partitions for each cut <tt>&alpha;</tt> &isin;
   * <tt>degrees</tt>.
   */
  private SetOf[][] partitions;

  /**
   * Returns the partitions (an array of arrays of sets of functors),
   * creating it if needed.
   */
  public SetOf[][] partitions ()
  {
    // initialize partitions if needed as an array of similarity classes (sets of functors)
    // of the size of the set of degrees.
    if (partitions == null)
      partitions = new SetOf[degrees.size()][];

    return partitions;
  }

  /**
   * Returns the partition (an array of sets of functors) at the
   * greatest degree in <tt>degrees</tt> that is &le; than the specified
   * cut.
   */
  public SetOf[] getPartition (double degreeCut)
  {
    return getPartition(getDegreeIndex(degreeCut));
  }

  /**
   * Returns the partition (an array of sets of functors) at the
   * approximation degree <tt>degreeIndex</tt> located at the specified
   * index in <tt>degrees</tt>.
   */
  public SetOf[] getPartition (int degreeIndex)
  {
    // SetOf[] partition;
    
    //    System.err.println("%%% partitions:");

    // for (int i = 0; i < partitions().length; i++)
    //   partition = partitions[i];
    //   {
    // 	partition = partitions[i];
    // 	if (partition != null)
    // 	  {
    // 	    // System.err.println("%%% partitions["+i+"] contains "+partition.length+
    // 	    // 		   " class"+(+partition.length==0?"":"es")+": "+
    // 	    // 		   partitionToString(partition));
    // 	    System.err.println("%%% for degree "+degrees.elementAt(i)+
    // 			       ", partition["+i+"]: "+partitionToString(partition));
    // 	  }
    //   }
    
    // if partitions[degreeIndex] is there, return it
    if (partitions()[degreeIndex] != null) // need the () to initialize partitions if needed
      return partitions[degreeIndex];
    // {
    // 	System.err.println("%%% partition at index "+degreeIndex);

    // 	partition = partitions[degreeIndex];

    // 	System.err.println("%%% evaluated partition corresponding to degree "+
    // 			   degrees.elementAt(degreeIndex)+" at index "+degreeIndex+": "+
    // 			   partitionToString(partition));

    // 	System.err.println("%%% getPartition("+degreeIndex+") returning "+partitionToString(partition));

    // 	return partition;
    // }
    
    // otherwise set partition[degreeIndex] to the partition at the degree at this index and return it

    sayIfTracing(3,
		 "partitioning for index "+degreeIndex+" corresponding to degree "+
		 degrees.elementAt(degreeIndex));

    // System.err.println("%%% returning partition["+degreeIndex+"] = partition("+degreeIndex+")");
    return partitions[degreeIndex] = partition(degreeIndex);
  }

  /**
   * Returns a string version of the specified partition (<i>i.e.</i>,
   * as a set of similarity classes with no class repeated).
   */ 
  public String partitionToString (SetOf[] partition)
  {
    ArrayList dejavu = new ArrayList();
    StringBuffer buf = new StringBuffer("{ ");
    
    for (int i = 0; i < partition.length; i++)
      if (!dejavu.contains(partition[i]))
	{
	  buf.append((i==0?"":", ")+partition[i]);
	  dejavu.add(partition[i]);
	}

    return buf.append(" }").toString();
  }

  /**
   * Invoking <tt>partition(degreeIndex)</tt>, where <tt>0</tt> &le;
   * <tt>degreeIndex</tt> &le; <tt>degrees.size()</tt>, will return an
   * array of sets of functors from this similarity's signature, one
   * array per functor. Hence, this array has the same size as the
   * signature.  Each entry in this array is the canonical set of
   * functors that is a similarity class at the fuzzy approximation
   * degree at this index in the <tt>DoubleArrayList</tt>
   * <tt>degrees</tt>. Each class whose index is an entry in this array
   * corresponding to a functor <tt>f</tt> in the signature contains
   * <tt>f</tt> and all the other functors that are
   * <tt>degrees.get(degreeIndex)</tt>-similar to <tt>f</tt>. Each class
   * is uniquely represented and shared in all partitions (it is the
   * same functor-set object in all the partitions it belongs to). For
   * example, if for a signature equal to <tt>{a,b,c,f,g,h}</tt>, the
   * similarity's data matrix is:
   *
   * <p>
   *
   * <pre>
   *      a   b   c   f   g   h
   *     --- --- --- --- --- ---
   * a | 1.0 0.5 0.0 0.5 0.4 0.2
   * b | 0.5 1.0 0.0 0.5 0.4 0.0
   * c | 0.0 0.0 1.0 0.0 0.0 0.0
   * f | 0.5 0.5 0.0 1.0 0.4 1.2
   * g | 0.4 0.4 0.0 0.4 1.0 0.2
   * h | 0.2 0.2 0.0 0.2 0.2 1.0
   * </pre>
   *
   * then, its <tt>degrees</tt> is the <tt>DoubleArrayList</tt>
   * <tt>[0.0, 0.2, 0.4, 0.5, 1.0]</tt> and its partition at degree
   * <tt>0.4</tt>, <i>i.e.</i>, at degree <tt>degreeIndex</tt>
   * <tt>2</tt>, is the set of functor classes {{<tt>a</tt>, <tt>b</tt>,
   * <tt>f</tt>, <tt>g</tt>}, {<tt>c</tt>} {<tt>h</tt>}} which is
   * represented by the 6-element array given by <tt>partition(2)</tt> =
   * <tt>partitions[2]</tt>:
   *
   * <pre>
   * a: A
   * b: A
   * c: C
   * f: A
   * g: A
   * h: H
   * </pre>
   *
   * where <tt>A={a,b,f,g}</tt>, <tt>C={c}</tt>, and <tt>H={h}</tt> are
   * canonical similarity classes.
   */
  private SetOf[] partition (int degreeIndex)
  {
    if (degreeIndex == 0)
      return fullSignaturePartition();

    double degreeCut = degrees.get(degreeIndex);

    SetOf[] partition = new SetOf[signature.size()];

    sayIfTracing(3,
		 "created an array of similarity classes for "+
		 signature.size()+" functors: "+signature.functors()+
		 " at approximation degree "+degreeCut+" (degreeIndex "+degreeIndex+")");
    
    for (int i = 1; i <= signature.size(); i++)
      {
	SetOf similarityClass = new SetOf(signature.functors());

	for (int j = 1; j <= signature.size(); j++)
	  if (similarityDegree(functor(i),functor(j)) >= degreeCut)
	    similarityClass.add(functor(j));

	sayIfTracing(3,
		     "");
	sayIfTracing(3,
		     "computed class of functor "+functor(i)+": "+similarityClass);

	similarityClass = canonicalClass(similarityClass);
	partition[i-1] = similarityClass;
      }

    sayIfTracing(3,
		 "partition("+degreeCut+") :");
    if (tracing)
      {
	for (int i = 0; i < partition.length; i++)
	  sayIfTracing(3,
		       "@@@ the set of functors "+degreeCut+"-similar to "+functor(i+1)+" is "+partition[i]);
      }

    return partition;
  }

  /* ************************************************************************ */
  /**
   * <center><b>CLASS REPRESENTATIVES</b></center>
   */ 

  /**
   * For any <tt>degreeCut</tt> &isin; <tt>[0.0,0.1]</tt>, this returns
   * the index of the largest <tt>degree</tt> &isin; <tt>degrees</tt>
   * such that <tt>degree</tt> &le; <tt>degreeCut</tt>. Since
   * <tt>1.0</tt> is always the largest degree in <tt>degrees</tt> and
   * corresponds to the finest partition, it is always the case that
   * <tt>degrees.get(degrees.size()-1)</tt> = <tt>1.0</tt>. Because
   * <tt>degrees</tt> is an <tt>IntArrayList</tt> sorted in increasing
   * order, in order to find the <i>largest index, <b>this sweeps it
   * down starting at its highest index</b></i>.
   */
  public int getDegreeIndex (double degreeCut)
  {
    boolean found = false;
    int degreeIndex = degrees.size(); // start at the end
    // System.err.println("### defined degrees are "+degrees);
    double degree = 1.0;

    // as long as it hasn't been found and we're not at 0 yet
    while (!found && --degreeIndex >= 0)
      // if this degree is <= degreeCut, this is the one we want
      found = degrees.elementAt(degreeIndex) <= degreeCut;

    // if at this point found is still false, then degreeIndex must be 0; this
    // means that no degree in degrees is <= degreeCut
    if (!found)
      sayIfTracing(1,
		   "no degree in this similarity is <= "+degreeCut);
    // therefore, the partition consists only one similarity class
    // containing all the functors in the signature and is always the
    // value of partitions[0].

    return degreeIndex;
  }

  /**
   * To each similarity class of functors is associated a unique
   * <tt>functor</tt> representative at each degree <tt>&alpha;</tt>
   * &isin; <tt>degrees</tt>. It is always the functor of least index
   * among the functors of least arity in the class. The array
   * <tt>functorReps</tt> is a cache structure for these representatives
   * that eliminates useless repeated recomputation each time the
   * <tt>functorRep</tt> method is invoked.
   */
  private Functor[][] functorReps;

  /**
   * This returns the unique representative of the specified functor at
   * the specified cut.
   */
  public Functor functorRep (Functor functor, double degreeCut)
  {
    return functorRep(functor,getDegreeIndex(degreeCut));
  }

  /**
   * This returns the unique representative of the specified functor for
   * the degree at the specified index in <tt>degrees</tt>
   */
  private Functor functorRep (Functor functor, int degreeIndex)
  {
    if (functorReps == null)
      functorReps = new Functor[signature().size()][degrees.size()];

    Functor rep = functor;

    for (Iterator it = functorClass(functor,degreeIndex).iterator(); it.hasNext();)
      {
	Functor F = (Functor)it.next();

	if (F.arity() < rep.arity())
	  {
	    rep = F;
	    continue;
	  }

	if (F.arity() == rep.arity() && F.index() <= rep.index())
	  rep = F;
      }

    return functorReps[functor.index()][degreeIndex] = rep;
  }

  /**
   * This returns the <tt>degreeCut</tt>-similarity class of the specified
   * functor.
   */
  public SetOf functorClass (Functor functor, double degreeCut)
  {
    // return functorClass(functor,getDegreeIndex(degreeCut));
    // equivalent to:
    return getPartition(degreeCut)[functor.index()];
  }

  /**
   * This returns the <tt>degrees.get(degreeIndex)</tt>-similarity class of
   * the specified functor.
   */
  public SetOf functorClass (Functor functor, int degreeIndex)
  {
    return getPartition(degreeIndex)[functor.index()];
    
    // System.err.println("&&& retrieving the "+degrees.elementAt(degreeIndex)+
    // 		       "-similarity functor class of "+functor+" (degree index "+degreeIndex+")");

    // SetOf[] partition = getPartition(degreeIndex);

    // System.err.println("!!! getPartition("+degreeIndex+") is "+partitionToString(partition));
    // System.err.println("!!! the index of functor "+functor+" is "+functor.index());

    // SetOf functorclass = partition[functor.index()];

    // System.err.println();
    // System.err.println("&&& returning class partition["+degreeIndex+
    // 		       "] for functor "+functor+" from class partition: "+
    // 		       partitionToString(partition)+
    // 		       " (functor index: "+functor.index()+");"+
    // 		       " result is: "+functorclass);

    // return functorclass;
  }


  /**
   * This returns the unique term-similarity class representation for
   * the given term at the given degree cut.
   */
  public FirstOrderTerm termRep (FirstOrderTerm term, double degreeCut)
  {
    return termRep(term.deref(),getDegreeIndex(degreeCut));
  }

  /**
   * This is a private tools that returns the unique term-similarity
   * class representation for the given term at the given degree index.
   */
  private FirstOrderTerm termRep (FirstOrderTerm term, int degreeIndex)
  {
    if (term.isVariable())
      return term;

    FirstOrderTermStructure T = (FirstOrderTermStructure)term;

    Functor functor = functorRep(T.functor(),degreeIndex);

    if (T.isConstant())
      return new FirstOrderTermStructure(functor);
    
    FirstOrderTerm[] arguments = new FirstOrderTerm[functor.arity()];

    int minArity = Math.min(T.functor().arity(),functor.arity());

    for (int i = 0; i < minArity; i++)
      arguments[i] = termRep(T.arguments()[i],degreeIndex);

    // we don't care about the left-over arguments since a functor rep
    // has always least arity in its similarity class

    return new FirstOrderTermStructure(functor,arguments);
  }

  /**
   * This returns the <tt>degreeCut</tt>-similarity class of a dereferenced
   * copy of the specified first-order term as an <tt>ArrayList</tt> of
   * first-order terms.
   */
  public ArrayList termClass (FirstOrderTerm term, double degreeCut)
  {
    return termClass(term.deref(),getDegreeIndex(degreeCut));
  }

  /**
   * This returns the <tt>degrees.get(degreeIndex)</tt>-similarity class
   * of the specified first-order term as an <tt>ArrayList</tt> of
   * first-order terms. It enumerates all the terms that are obtainable
   * by replacing each functor by one that is
   * <tt>degrees.get(degreeIndex)</tt>-similar to it. A variable is
   * replaced only by itself at any degree. For larger-arity functors,
   * the extra-arguments are new variables. <b>N.B.</b>, this assumes
   * that all variables in the term have been dereferenced; <i>i.e.</i>,
   * that <tt>term</tt> was the result of applying <tt>deref()</tt> to
   * an initial term possibly containing bound variables - a virginal
   * clean copy as it were since this eliminates all bound
   * variables. All the terms in the class will be so clean as well (no
   * bound variables).
   */
  private ArrayList termClass (FirstOrderTerm term, int degreeIndex)
  {
    sayIfTracing("computing the similarity class of term "+term+
		 " at degree index "+degreeIndex);
    
    // this is the list of all the terms that will be returned that gathers all
    // those similar to the specified term at the degree at specified index
    ArrayList similarTerms = new ArrayList();

    // if it's a variable, return the ArrayList that only contains it alone
    if (term.isVariable())
      {
  	similarTerms.add(term);
  	return similarTerms;
      }

    // so term is a structure: either without arguments (a constant) or
    // with arguments (its functor's arity > 0)
    FirstOrderTermStructure T = (FirstOrderTermStructure)term;

    // compute the term's functor's class

    SetOf functorClass = functorClass(T.functor(),degreeIndex);

    // for each functor in the class
    for (Iterator it = functorClass.iterator(); it.hasNext();)
      {
  	Functor functor = (Functor)it.next();

	// if this is a constant, add it and proceed with other functors
	// in the class
	if (functor.arity() == 0)
	  {
	    similarTerms.add(new FirstOrderTermStructure(functor));
	    continue;
	  }

	// it is a structure with arguments: we need to collect them

	// arrayOfArrayLists is an array of functor.arity() ArrayLists,
	// one per argument each containing the term classes of each
	// argument
	ArrayList[] arrayOfArrayLists = new ArrayList[functor.arity()];

	// fill it with the term classes of each the term's arguments
	// that are present or anonymous variables

	increaseTracingMargin(" of term "+T);

	for (int i = 1; i <= functor.arity(); i++)	  
	  if (i <= T.functor().arity())
	    arrayOfArrayLists[i-1] = termClass(T.argument(i),degreeIndex);
	  else
	    arrayOfArrayLists[i-1] = termClass(Variable.anonymousVariable(),degreeIndex);
	
	decreaseTracingMargin(" of term "+T);

	// this will contain ArrayList indices, one per argument position
	int[] listIndices = new int[functor.arity()];
	// initialize it full of 0s
	for (int i = 0; i < listIndices.length; i++)
	  listIndices[i] = 0;
	
	// pick one term from each list of terms in the array of lists
	// and build a new term to add to similarTerms
	do
	  {
	    FirstOrderTerm[] arguments = new FirstOrderTerm[functor.arity()];

	    for (int i = 0; i < functor.arity(); i++)
	      if (i < Math.min(functor.arity(),T.functor().arity()))
		arguments[i] = (FirstOrderTerm)arrayOfArrayLists[i].get(listIndices[i]);
	      else
		arguments[i] = Variable.anonymousVariable();

	    similarTerms.add(new FirstOrderTermStructure(functor,arguments));
	  }
	while (moreArguments(listIndices,arrayOfArrayLists));
      }

    return similarTerms;
  }

  /**
   * This will try to update the array of indices by incrementing the
   * one of least position that can still be and return <tt>true</tt> if
   * this is possible. Otherwise, this means that all the <tt>int</tt>
   * elements in the array of list indices have reached their maximum
   * values (<i>i.e.</i>, each is equal to the size of the corresponding
   * <tt>ArrayList</tt>), and so this will return <tt>false</tt>.
   */
  private boolean moreArguments (int[] listIndices, ArrayList[] arrayOfArraylists)
  {
    for (int i = listIndices.length-1; i >= 0 ; i--)
      {
	if (listIndices[i] < arrayOfArraylists[i].size()-1)
	  {
	    listIndices[i]++;
	    return true;
	  }

	// here, listIndices[i] = arrayOfArraylists[i].size()-1: it has
	// exhausted this list; reset it to 0 and all the previous lists
	// of indices exhausted up to position i and increment the one
	// at the highest position below that isn't exhausted; if not
	// possible, there is no more combination of arguments

	while (i >= 0 && listIndices[i] == arrayOfArraylists[i].size()-1)
	  listIndices[i--] = 0;

	if (i >= 0)
	  {
	    listIndices[i]++;
	    return true;
	  }

	// i == 0  we're out of combinations
      }
    return false;
  }

  /* ************************************************************************ */
  /**
   * <center><b>UTILITIES</b></center>
   */ 

  /**
   * Returns the similarity degree of the specified functors.
   */
  public double similarityDegree (Functor lhs, Functor rhs)
  {
    return data[lhs.index()][rhs.index()];
  }

  /**
   * Returns the similarity degree of the two specified terms. On pairs
   * of structures, it proceeds recursively on the maximum number of
   * common argument positions (<i>i.e.</i>, the least of the two
   * functors' arities). This is the one defined in <a
   * href="http://hassan-ait-kaci.net/pdf/fuzfotlats-lopstr2017.pdf">this
   * paper</a> (see also the <a
   * href="http://hassan-ait-kaci.net/pdf/fuzfotlats-lopstr2017-slides.pdf">LOPSTR
   * 2017 conference slides</a>).
   */
 public double similarityDegree (FirstOrderTerm lhs, FirstOrderTerm rhs)
  {
    sayIfTracing(2,
		 tracingIndentMargin+"computing similarity degree of "+lhs+" and "+rhs);

    if (lhs == rhs)
      {
	sayIfTracing(2,
		     tracingIndentMargin+"results in 1.0");
	return 1.0;
      }

    if (lhs != rhs && (lhs.isVariable() || rhs.isVariable()))
      {
	sayIfTracing(2,
		     tracingIndentMargin+"results in 0.0");
	return 0.0;
      }

    // so both must be structures:
    FirstOrderTermStructure L = (FirstOrderTermStructure)lhs;
    FirstOrderTermStructure R = (FirstOrderTermStructure)rhs;

    double degree = similarityDegree(L.functor(),R.functor());

    int minArity = Math.min(L.functor().arity(),R.functor().arity());

    // System.err.println("&&& min arity of "+
    // 		       L.functor()+"/"+L.functor().arity()+
    // 		       " and "+
    // 		       R.functor()+"/"+R.functor().arity()+
    // 		       " is: "+minArity);

    // if (L.arguments() != null)
    //   {
    // 	System.err.print("&&& "+L.functor()+" has "+L.arguments().length+" arguments:");
    // 	for (int k=0; k<L.arguments().length; k++)
    // 	  System.err.print(" "+k+":"+L.arguments()[k]);
    // 	System.err.println();
    //   }

    // if (R.arguments() != null)
    //   {
    // 	System.err.print("&&& "+R.functor()+" has "+R.arguments().length+" arguments:");
    // 	for (int k=0; k<R.arguments().length; k++)
    // 	  System.err.print(" "+k+":"+R.arguments()[k]);
    // 	System.err.println();
    //   }

    int i = 1;

    while (degree > 0.0 && i <= minArity)
      {
	degree = inf(degree,
		     similarityDegree(L.argument(i),
				      R.argument(i)));
	i++;
      }

    sayIfTracing(2,
		 tracingIndentMargin+"results in "+degree);

    return degree;
  }

  /* ************************************************************************ */
  /**
   * <center><b>LATTICE OPERATIONS</b></center>
   */

  /**
   * These are generic implementations for fuzzy unification and
   * generalization of first-order terms using parameterized
   * argument-position mappings when dealing with similar functors. The
   * default argument-position mapping from any functor to another is
   * the identity on <tt>{1,...,n}</tt>, where <tt>n</tt> is the least
   * of the two functors' arities.
   */
  

  /**
   * This is the default argument-position mapping (the identity) from
   * <tt>Functor</tt> <tt>from</tt> to <tt>Functor</tt> <tt>to</tt> at
   * approximation <tt>degree</tt> for <tt>position</tt>.
   */
  int positionMapping (Functor from, Functor to, double degree, int position)
  {
    return position;
  }

  /**
   * <a id="unification"/>
   * Fuzzy Unification
   */

  /**
   * Fuzzy unify the given pair of first-order terms modulo this
   * similarity and return their similarity degree, which will be
   * greater than <tt>0</tt> iff these terms are fuzzy unifiable. This
   * will possibly bind variables that occur in either term.
   */
  public double unify (FirstOrderTerm lhs, FirstOrderTerm rhs)
  {
    unifyStack.clear();

    unifyStack.push(lhs);
    unifyStack.push(rhs);

    double degree = 1.0;

    while (degree > 0.0 && !unifyStack.isEmpty())
      {
	lhs = (FirstOrderTerm)unifyStack.pop();
	rhs = (FirstOrderTerm)unifyStack.pop();

	if (lhs.isVariable())
	  lhs = ((Variable)lhs).deref();

	if (rhs.isVariable())
	  rhs = ((Variable)rhs).deref();

	if (lhs == rhs)
	  continue;

	if (lhs.isVariable())
	  {
	    Variable V = ((Variable)lhs);

	    if (V.occursIn(rhs))
	      degree = 0.0;
	    else
	      V.bind(rhs);

	    continue;
	  }

	if (rhs.isVariable())
	  {
	    Variable V = ((Variable)rhs);

	    if (V.occursIn(lhs))
	      degree = 0.0;
	    else
	      V.bind(lhs);

	    continue;
	  }

	// both are structures:
	FirstOrderTermStructure L = (FirstOrderTermStructure)lhs;
	FirstOrderTermStructure R = (FirstOrderTermStructure)rhs;

	degree = inf(degree,
		     similarityDegree(L.functor(),R.functor()));
	
	if (degree > 0.0)
	  {
	    int minArity = Math.min(L.functor().arity(),R.functor().arity());

	    for (int i=1; i <= minArity; i++)
	      {
		int j = positionMapping(L.functor(),R.functor(),degree,i);
		unifyStack.push(L.argument(i));
		unifyStack.push(R.argument(j));
	      }
	  }
      }

    return degree;
  }

  // Unification accessories
  
  /**
   * Unification stack.
   */
  static Stack unifyStack = new Stack();


  /* ************************************************************************ */
  /**
   * <a id="generalization"/>
   * Fuzzy Generalization
   */
  /**
   * Given a pair of terms <tt>lhs</tt> and <tt>rhs</tt> and a prior
   * approximation degree <tt>degree</tt>, this returns a fuzzy term
   * made of the term generalizing the pair, and the maximal
   * approximation degree at which it generalizes them.
   */
  public FuzzyFirstOrderTerm generalize (FirstOrderTerm lhs, FirstOrderTerm rhs, double degree)
  {
    // Fuzzy Equal Variables
    if (lhs.isVariable() && lhs == rhs)
      return new FuzzyFirstOrderTerm(lhs,degree);

    // Fuzzy Variable-Term:
    if ((lhs.isVariable() || rhs.isVariable()) && !lhs.equal(rhs))
      {
	Variable newVariable = Variable.anonymousVariable();
	lSubstitution.put(newVariable,lhs);
	rSubstitution.put(newVariable,rhs);
	generatedVariables.add(newVariable);
	return new FuzzyFirstOrderTerm(newVariable,degree);
      }

    // So both terms are structures:
    FirstOrderTermStructure L = (FirstOrderTermStructure)lhs;
    FirstOrderTermStructure R = (FirstOrderTermStructure)rhs;
    
    // For dissimilar functors, introduce a new generalization variable
    // and extend the left and right substitutions.

    // Dissimilar Functors:
    if (similarityDegree(L.functor(),R.functor()) == 0.0)
      {
	Variable newVariable = Variable.anonymousVariable();
	lSubstitution.put(newVariable,lhs);
	rSubstitution.put(newVariable,rhs);
	generatedVariables.add(newVariable);
	return new FuzzyFirstOrderTerm(newVariable,degree);
      }

    // For similar functors, proceed recursively by generalizing each
    // pair of arguments having corresponding positions in each term,
    // and simultaneously build up the left and right generalizing
    // substitutions as needed along with refining the generalizing
    // degree as required.

    // Similar Functors:
    Functor F = L.functor();
    Functor G = R.functor();

    int M = F.arity();
    int N = G.arity();

    // generalize only common-position arguments
    int genArity = Math.min(M,N);

    // the functor retained for the generalization is the one with
    // least-arity
    Functor genFunctor = M <= N ? F : G;

    double functorsSimilarity = similarityDegree(F,G);

    sayIfTracing(1,
		 "generalizing lhs term "+L+" and rhs term "+R+" at degree "+degree);

    // initialize the generalization degree to the infimum of the prior
    // one and the functors' similarity degree
    double genDegree = inf(degree,functorsSimilarity);

    if (functorsSimilarity < degree)
      sayIfTracing(1,
		   "since "+F+" ~ "+G+" ["+functorsSimilarity+"]"+
		   " the new generalization degree is now "+genDegree);
    
    if (genArity == 0)
      // there are no arguments to generalize: we're done!
      return new FuzzyFirstOrderTerm(new FirstOrderTermStructure(genFunctor),
				     genDegree);

    // there are arguments to generalize:

    // first build an argumentless generalized term structure:
    FirstOrderTermStructure genStructure
      = new FirstOrderTermStructure(genFunctor,
				    new FirstOrderTerm[genArity]);

    increaseTracingMargin(" of a term with functor "+genFunctor+"/"+genFunctor.arity());

    // then build each generalized argument:
    for (int i = 1; i <= genArity; i++)
      {
	sayIfTracing(2,
		     "argument "+i);

	int j = positionMapping(L.functor(),R.functor(),genDegree,i);

	// we first compute the fuzzy-unapplied pair:
	FuzzyFirstOrderTermPair unappliedPair = unapply(L.argument(i),
							R.argument(j),
							genDegree);

	// we next fuzzy-generalize the unapplied arguments at the unapplied degree:
	FuzzyFirstOrderTerm fuzzyGenArg = generalize(unappliedPair.left(),
						     unappliedPair.right(),
						     unappliedPair.degree());

	// we finally set the ith argument to the generalized argument:
	genStructure.setArgument(i,fuzzyGenArg.term());
	// and update the generalization degree to the one resulting from generalizing the ith arguments:
	genDegree = fuzzyGenArg.degree();
      }

    decreaseTracingMargin((" of "+genStructure));

    // return the fuzzy-generalized structure and ultimate generalization degree:
    return new FuzzyFirstOrderTerm(genStructure,genDegree);
  }

  // Generalization accessories

  /**
   * The generated generalizing variables.
   */
  public static ArrayList generatedVariables  = new ArrayList();

  /**
   * The substitution mapping generated variables to the subterms of the
   * generalized lhs.
   */
  public static HashMap lSubstitution = new HashMap();

  /**
   * The substitution mapping generated variables to the subterms of the
   * generalized rhs.
   */
  public static HashMap rSubstitution = new HashMap();

  /**
   * Clears all substitutions and generated variables.
   */
  public static void resetGeneralizer ()
  {
    generatedVariables.clear();
    lSubstitution.clear();
    rSubstitution.clear();
  }
  
  /**
   * Unapply the current left and right fuzzy-generalizing substitutions
   * to the specified terms at the specified approximation degree, and
   * return the resulting pair of terms and approximation degree as a
   * <tt>FuzzyFirstOrderTermPair</tt>.
   */
  private FuzzyFirstOrderTermPair unapply (FirstOrderTerm lhs, FirstOrderTerm rhs, double degree)
  {
    Variable V;
    FirstOrderTerm L;
    FirstOrderTerm R;

    sayIfTracing(2,
		 "unapplying left substitution "+lSubstitution+
		 " and right substitution "     +rSubstitution+
		 " to lhs term "+lhs+" and rhs term "+rhs+" at degree "+degree);

    for (int i = 0; i < generatedVariables.size(); i++)
      {
	double unapplyDegree = degree;

	V = (Variable)generatedVariables.get(i);
	L = (FirstOrderTerm)lSubstitution.get(V);
	R = (FirstOrderTerm)rSubstitution.get(V);

	double lhsDegree = similarityDegree(lhs,L);

	sayIfTracing(2,
		     "the lhs argument "+i+"'s unapplied variable "+V+
		     " gives term: "+L+" resulting in degree "+lhsDegree);

	double rhsDegree = similarityDegree(rhs,R);

	sayIfTracing(2,
		     "the rhs argument "+i+"'s unapplied variable "+V+
		     " gives term: "+R+" resulting in degree "+rhsDegree);

	unapplyDegree = inf(unapplyDegree,
			    inf(lhsDegree,
				rhsDegree));

	sayIfTracing(2,
		     "so the resulting unapplication degree is "+unapplyDegree);

	if (unapplyDegree > 0.0)
	  {
	    FuzzyFirstOrderTermPair fuzzyTermPair = new FuzzyFirstOrderTermPair(V,V,unapplyDegree);

	    sayIfTracing(2,
			 "therefore the final unapplied fuzzy pair is "+fuzzyTermPair);

	    return fuzzyTermPair;
	  }
      }

    FuzzyFirstOrderTermPair fuzzyTermPair = new FuzzyFirstOrderTermPair(lhs,rhs,degree);

    sayIfTracing(2,
		 "there is no similar unapplication for "+lhs+" and "+rhs+" at degree "+degree);
    sayIfTracing(2,
		 "the final unapplied fuzzy pair is the original unchanged "+fuzzyTermPair);

    return fuzzyTermPair;
  }


  // /* ************************************************************************ */
  // /**
  //  * <center><b>DISPLAY</b></center>
  //  */

  // /**
  //  * Prints this <tt>SimilaritySignature</tt> to standard output at the
  //  * default precision <tt>FuzzyMatrix.precision</tt>.
  //  */
  // public void show ()
  // {
  //   show(FuzzyMatrix.precision);
  // }

  // /**
  //  * Prints this <tt>SimilaritySignature</tt> to standard output at the
  //  * specified precision. <b>NB</b>: this works nicely only for
  //  * one-letter functor names and one-digit arities. This is just a
  //  * quick-and-dirty method for now since to print a matrix with
  //  * correctly lined up entries for headers that could be any
  //  * <tt>String/int</tt> functor/arity, the precision width should
  //  * depend on the max-width of the functor/arity.
  //  */
  // public void show (String precision)
  // {
  //   FuzzyMatrix.precision = precision;

  //   System.out.print("     ");
  //   for (int i = 1; i <= rank(); i++)
  //     System.out.print(functor(i)+"/"+functor(i).arity()+"  ");
  //   System.out.println();

  //   System.out.print("     ");
  //   for (int i = 1; i <= rank(); i++)
  //     System.out.print("---  ");
  //   System.out.println();

  //   for (int i = 1; i <= rank(); i++)
  //     {
  // 	System.out.print(functor(i).name()+" | ");
  // 	for (int j = 1; j <= rank(); j++)
  // 	  System.out.printf(precision, data[i-1][j-1]);
  // 	System.out.println("\n");
  //     }
  // }

  /* ************************************************************************ */
  /**
   * <center><b>TRACING</b></center>
   */

  /**
   * Default tracing indentation.
   */
  private static int tracingIndent = 3;

  /**
   * Sets the current value of the tracing indentation to
   * <tt>indent</tt> blank spaces.
   */
  public static void setTracingIndent (int indentValue)
  {
    tracingIndent = indentValue;
  }

  /**
   * Default tracing indentation margin.
   */
  private static String tracingIndentMargin = "";

  /**
   * Increase the current tracing indentation margin.
   */
  public static void increaseTracingMargin (String msg)
  {
    sayIfTracing("process the arguments"+msg);

    for (int i = 0; i < tracingIndent; i++)
      tracingIndentMargin = tracingIndentMargin+" ";
  }

  /**
   * Decrease the current tracing indentation margin.
   */
  public static void decreaseTracingMargin (String msg)
  {
    if (tracingIndentMargin.length() > 0)
      tracingIndentMargin = tracingIndentMargin.substring(0,tracingIndentMargin.length()-tracingIndent);

    sayIfTracing("finished processsing the arguments"+msg);   
  }

  /**
   * Tracing flag.
   */
  private static boolean tracing = false;

  /**
   * If already tracing, this disables it; otherwise, it enables it.
   */
  public static void toggleTracing (int verbosityLevel)
  {
    setTracingVerbosity(verbosityLevel);

    tracing = verbosityLevel == 0 ? false : true;
    
    System.err.println("@@@ tracing in now turned "+
		       (tracing?("ON at level "+verbosityLevel):"OFF"));
  }

  /**
   * The maximum level of tracing detail verbosity.
   */
  final private static int maxTracingVerbosity = 3;

  /**
   * The current level of tracing detail verbosity.
   */
  private static int tracingVerbosity = 0;

  /**
   * Set the current level of tracing detail verbosity.
   */
  public static void setTracingVerbosity (int verbosityLevel)
  {
    tracingVerbosity = Math.max(0,Math.min(verbosityLevel,
					   maxTracingVerbosity));
  }

  /**
   * If tracing, report <tt>message</tt> on a new line indented by the
   * current tracing indentation margin on the error output stream
   * giving details at the current level of tracing verbosity.
   */
  public static void sayIfTracing (String message)
  {
    sayIfTracing(tracingVerbosity,message);
  }

  /**
   * If tracing, report <tt>message</tt> on a new line indented by the
   * current tracing indentation margin on the error output stream at
   * the specified level of tracing verbosity.
   */
  public static void sayIfTracing (int verbosity, String message)
  {
    if (tracing && verbosity >= tracingVerbosity)
      System.err.println("@@@ "+tracingIndentMargin+message);
  }

}
