/* (non-Javadoc)
   * @see datamining.clustering.protoype.AbstractPrototypeClusteringAlgorithm#getObjectiveFunctionValue()
   */
  @Override
  public double getObjectiveFunctionValue() {
    if (!this.initialized)
      throw new AlgorithmNotInitializedException("Prototypes not initialized.");

    int i, j;
    // i: index for clusters
    // j: index for data objects
    // k: index for dimensions, others

    double objectiveFunctionValue = 0.0d;

    double distanceSum =
        0.0d; // the sum_i dist[i][l]^{2/(1-fuzzifier)}: the sum of all parametrised distances for
              // one cluster l
    double doubleTMP = 0.0d; // a temporarly variable for multiple perpuses
    double[] fuzzDistances = new double[this.getClusterCount()];
    double[] distancesSq = new double[this.getClusterCount()];
    double minDistValue = 0.0d;

    for (j = 0; j < this.getDataCount(); j++) {
      distanceSum = 0.0d;
      minDistValue = Double.MAX_VALUE;

      for (i = 0; i < this.getClusterCount(); i++) {
        doubleTMP =
            this.dist.distanceSq(this.data.get(j).element, this.prototypes.get(i).getPosition());
        distancesSq[i] = doubleTMP;
        if (minDistValue > doubleTMP) minDistValue = doubleTMP;
      }
      minDistValue *= this.distanceMultiplierConstant;

      for (i = 0; i < this.getClusterCount(); i++) {
        doubleTMP = distancesSq[i] - minDistValue;
        if (doubleTMP <= 0.0d) {
          fuzzDistances[i] = 1.0d;
        } else {
          doubleTMP = 1.0d / doubleTMP;
          fuzzDistances[i] = doubleTMP;
          distanceSum += doubleTMP;
        }
      }

      // don't check for distance sum to be zero.. that would just be rediculus!!

      for (i = 0; i < this.getClusterCount(); i++) {
        doubleTMP = fuzzDistances[i] / distanceSum;

        objectiveFunctionValue +=
            MyMath.pow(doubleTMP, this.fuzzifier) * (distancesSq[i] - minDistValue);
      }
    }

    return objectiveFunctionValue;
  }
  @Override
  public void apply(int steps) {
    if (!this.initialized)
      throw new AlgorithmNotInitializedException("Prototypes not initialized.");

    int i, j, k, t;
    // i: index for clusters
    // j: index for data objects
    // k: index for dimensions, others
    // t: index for iterations

    double distanceSum =
        0.0d; // the sum_i dist[i][l]^{2/(1-fuzzifier)}: the sum of all parametrised distances for
              // one cluster l
    double doubleTMP = 0.0d; // a temporarly variable for multiple perpuses
    double maxPrototypeMovement = 0.0d;
    ArrayList<T> newPrototypePosition = new ArrayList<T>(this.getClusterCount());
    for (i = 0; i < this.getClusterCount(); i++)
      newPrototypePosition.add(this.vs.getNewAddNeutralElement());
    double[] fuzzDistances = new double[this.getClusterCount()];
    double[] membershipValues = new double[this.getClusterCount()];
    double[] membershipSum = new double[this.getClusterCount()];
    T tmpX = this.vs.getNewAddNeutralElement();
    double minDistValue = 0.0d;

    int[] zeroDistanceIndexList = new int[this.getClusterCount()];
    int zeroDistanceCount;

    for (t = 0; t < steps; t++) {
      // reset values
      maxPrototypeMovement = 0.0d;

      for (i = 0; i < this.getClusterCount(); i++) {
        this.vs.resetToAddNeutralElement(newPrototypePosition.get(i));
        membershipSum[i] = 0.0d;
      }

      // update membership values
      for (j = 0; j < this.getDataCount(); j++) {
        for (i = 0; i < this.getClusterCount(); i++) zeroDistanceIndexList[i] = -1;
        zeroDistanceCount = 0;
        distanceSum = 0.0d;
        minDistValue = Double.MAX_VALUE;

        for (i = 0; i < this.getClusterCount(); i++) {
          doubleTMP =
              this.dist.distanceSq(this.data.get(j).element, this.prototypes.get(i).getPosition());
          fuzzDistances[i] = doubleTMP;
          if (minDistValue > doubleTMP) minDistValue = doubleTMP;
        }
        minDistValue *= this.distanceMultiplierConstant;

        for (i = 0; i < this.getClusterCount(); i++) {
          doubleTMP = fuzzDistances[i] - minDistValue;
          if (doubleTMP <= 0.0d) {
            doubleTMP = 0.0d;
            zeroDistanceIndexList[zeroDistanceCount] = i;
            zeroDistanceCount++;
          } else {
            doubleTMP = 1.0d / doubleTMP;
            fuzzDistances[i] = doubleTMP;
            distanceSum += doubleTMP;
          }
        }

        // special case handling: if one (or more) prototype sits on top of a data object
        if (zeroDistanceCount > 0) {
          for (i = 0; i < this.getClusterCount(); i++) {
            membershipValues[i] = 0.0d;
          }
          doubleTMP = 1.0d / ((double) zeroDistanceCount);
          for (k = 0; k < zeroDistanceCount; k++) {
            membershipValues[zeroDistanceIndexList[k]] = doubleTMP;
          }
        } else {
          for (i = 0; i < this.getClusterCount(); i++) {
            doubleTMP = fuzzDistances[i] / distanceSum;
            membershipValues[i] = doubleTMP;
          }
        }

        for (i = 0; i < this.getClusterCount(); i++) {

          doubleTMP = MyMath.pow(membershipValues[i], this.fuzzifier);
          membershipSum[i] += doubleTMP;

          this.vs.copy(tmpX, this.data.get(j).element);
          this.vs.mul(tmpX, doubleTMP);
          this.vs.add(newPrototypePosition.get(i), tmpX);
        }
      }

      // update prototype positions
      for (i = 0; i < this.getClusterCount(); i++) {
        doubleTMP = 1.0d / membershipSum[i];
        this.vs.mul(newPrototypePosition.get(i), doubleTMP);
      }

      // copy new prototype values into prototypes wrt. learning factor
      for (i = 0; i < this.getClusterCount(); i++) {
        if (Math.abs(this.learningFactor - 1.0d) > 0.01d) {
          this.vs.sub(newPrototypePosition.get(i), this.prototypes.get(i).getPosition());
          this.vs.mul(newPrototypePosition.get(i), this.learningFactor);
          this.vs.add(newPrototypePosition.get(i), this.prototypes.get(i).getPosition());
        }

        doubleTMP =
            this.dist.distanceSq(this.prototypes.get(i).getPosition(), newPrototypePosition.get(i));

        maxPrototypeMovement =
            (doubleTMP > maxPrototypeMovement) ? doubleTMP : maxPrototypeMovement;

        this.prototypes.get(i).moveTo(newPrototypePosition.get(i));
      }

      this.iterationComplete();

      if (maxPrototypeMovement < this.epsilon * this.epsilon) break;
    }
  }