package org.chesmapper.view.cluster;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JOptionPane;
import org.chesmapper.map.dataInterface.DefaultNumericProperty;
import org.chesmapper.map.main.Settings;
import org.mg.javalib.util.DoubleArraySummary;
public class SALIProperty extends DefaultNumericProperty
{
String target;
Type type;
public static enum Type
{
Mean, Max, StdDev;
public String nice()
{
switch (this)
{
case StdDev:
return "Standard-Deviation";
case Mean:
return "Mean";
case Max:
return "Maximum";
default:
return null;
}
}
}
private SALIProperty(Double[] values, String target, Type type)
{
super(Settings.text("props.sali", type.nice()), Settings.text("props.sali.desc", type.nice(), target), values);
this.target = target;
this.type = type;
}
public static List<SALIProperty> create(Double[] endpointVals, double[][] featureDistanceMatrix, String target)
{
List<SALIProperty> l = new ArrayList<SALIProperty>();
HashMap<Type, Double[]> vals = computeSali(endpointVals, featureDistanceMatrix);
for (Type t : Type.values())
l.add(new SALIProperty(vals.get(t), target, t));
return l;
}
public static final double MIN_ENDPOINT_DEV = 0.1;
public static final double IDENTICAL_FEATURES_SALI = 1000.0;
public static final String MIN_ENDPOINT_DEV_STR = ((int) (MIN_ENDPOINT_DEV * 100)) + "%";
private static class EqualFeatureTuple
{
private Double id;
private Boolean isCliff;
private Set<Integer> indices = new HashSet<Integer>();
public boolean isCliff(double[][] featureDistanceMatrix, Double[] endpointVals, double minEndpointDiff)
{
if (isCliff == null)
{
isCliff = false;
for (Integer idx1 : indices)
{
if (endpointVals[idx1] == null)
continue;
for (Integer idx2 : indices)
{
if (endpointVals[idx2] == null)
continue;
if (idx1 == idx2)
continue;
if (featureDistanceMatrix[idx1][idx2] != 0)
throw new IllegalStateException("distance measure not transitiv");
double endpointDist = Math.abs(endpointVals[idx1] - endpointVals[idx2]);
if (endpointDist >= minEndpointDiff)
{
isCliff = true;
break;
}
}
if (isCliff)
break;
}
}
return isCliff;
}
}
private static HashMap<Type, Double[]> computeSali(Double[] endpointVals, double[][] featureDistanceMatrix)
{
if (endpointVals.length != featureDistanceMatrix.length
|| endpointVals.length != featureDistanceMatrix[0].length)
throw new IllegalArgumentException();
List<EqualFeatureTuple> eqTuplesList = new ArrayList<EqualFeatureTuple>();
EqualFeatureTuple eqTuplesArray[] = new EqualFeatureTuple[endpointVals.length];
for (int i = 0; i < eqTuplesArray.length - 1; i++)
{
for (int j = i + 1; j < eqTuplesArray.length; j++)
{
if (featureDistanceMatrix[i][j] != featureDistanceMatrix[j][i])
throw new IllegalStateException("distance measure not symmetric");
if (featureDistanceMatrix[i][j] == 0)
{
if (eqTuplesArray[i] != null && eqTuplesArray[j] != null && eqTuplesArray[i] != eqTuplesArray[j])
throw new IllegalStateException();
EqualFeatureTuple n = eqTuplesArray[i];
if (n == null)
n = eqTuplesArray[j];
if (n == null)
{
n = new EqualFeatureTuple();
eqTuplesList.add(n);
}
n.indices.add(i);
n.indices.add(j);
eqTuplesArray[i] = n;
eqTuplesArray[j] = n;
}
}
}
double minEndpointDiff = MIN_ENDPOINT_DEV;
int identicalFeatsCompounds = 0;
int identicalFeatsCliffs = 0;
for (EqualFeatureTuple n : eqTuplesList)
if (n.isCliff(featureDistanceMatrix, endpointVals, minEndpointDiff))
{
n.id = IDENTICAL_FEATURES_SALI + identicalFeatsCliffs;
identicalFeatsCliffs++;
identicalFeatsCompounds += n.indices.size();
}
HashMap<Type, Double[]> res = new HashMap<Type, Double[]>();
for (Type t : Type.values())
res.put(t, new Double[endpointVals.length]);
for (int i = 0; i < endpointVals.length; i++)
{
if (eqTuplesArray[i] != null
&& eqTuplesArray[i].isCliff(featureDistanceMatrix, endpointVals, minEndpointDiff))
{
for (Type t : Type.values())
res.get(t)[i] = eqTuplesArray[i].id;
continue;
}
if (endpointVals[i] == null)
continue;
List<Double> allSalis = new ArrayList<Double>();
for (int j = 0; j < endpointVals.length; j++)
{
if (i == j)
continue;
if (endpointVals[j] == null)
continue;
if (endpointVals[i] < 0 || endpointVals[i] > 1)
throw new IllegalStateException("please normalize!");
double endpointDist = Math.abs(endpointVals[i] - endpointVals[j]);
if (endpointDist < minEndpointDiff)
continue;
if (featureDistanceMatrix[i][j] == 0)
throw new IllegalStateException();
double tmpSali = endpointDist / featureDistanceMatrix[i][j];
allSalis.add(tmpSali);
}
DoubleArraySummary stats = DoubleArraySummary.create(allSalis);
for (Type t : Type.values())
switch (t)
{
case Max:
res.get(t)[i] = stats.getMax();
break;
case Mean:
res.get(t)[i] = stats.getMean();
break;
case StdDev:
res.get(t)[i] = stats.getStdev();
break;
}
}
if (identicalFeatsCompounds > 0)
{
String warning = Settings.text("props.sali.identical-warning", identicalFeatsCompounds + "",
identicalFeatsCliffs + "", MIN_ENDPOINT_DEV_STR, IDENTICAL_FEATURES_SALI + "") + "\n\n";
warning += "Details:\n";
warning += Settings.text("props.sali.detail", MIN_ENDPOINT_DEV_STR);
JOptionPane.showMessageDialog(Settings.TOP_LEVEL_FRAME, warning, "Warning", JOptionPane.WARNING_MESSAGE);
}
return res;
}
public SALIProperty.Type getSALIPropertyType()
{
return type;
}
}