package tr.gov.ulakbim.jDenetX.cluster; import weka.core.DenseInstance; import weka.core.Instance; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * A simple implementation of the <code>Cluster</code> interface representing * spherical clusters. The inclusion probability is one inside the sphere and zero * everywhere else. */ public class SphereCluster extends Cluster { private double[] center; private double radius; private double weight; public SphereCluster(double[] center, double radius) { this(center, radius, 1.0); } public SphereCluster() { } public SphereCluster(double[] center, double radius, double weightedSize) { this(); this.center = center; this.radius = radius; this.weight = weightedSize; } public SphereCluster(int dimensions, double radius, Random random) { this(); this.center = new double[dimensions]; this.radius = radius; // Position randomly but keep hypersphere inside the boundaries double interval = 1.0 - 2 * radius; for (int i = 0; i < center.length; i++) { this.center[i] = (random.nextDouble() * interval) + radius; } this.weight = 0.0; } public SphereCluster(List<? extends Instance> instances, int dimension) { this(); if (instances == null || instances.size() <= 0) return; weight = instances.size(); Miniball mb = new Miniball(dimension); mb.clear(); for (Instance instance : instances) { mb.check_in(instance.toDoubleArray()); } mb.build(); center = mb.center(); radius = mb.radius(); mb.clear(); } /** * Checks whether two <code>SphereCluster</code> overlap based on radius * NOTE: overlapRadiusDegree only calculates the overlap based * on the centers and the radi, so not the real overlap * <p/> * TODO: should we do this by MC to get the real overlap??? * * @param other * @return */ public double overlapRadiusDegree(SphereCluster other) { double[] center0 = getCenter(); double radius0 = getRadius(); double[] center1 = other.getCenter(); double radius1 = other.getRadius(); double radiusBig; double radiusSmall; if (radius0 < radius1) { radiusBig = radius1; radiusSmall = radius0; } else { radiusBig = radius0; radiusSmall = radius1; } double dist = 0; for (int i = 0; i < center0.length; i++) { double delta = center0[i] - center1[i]; dist += delta * delta; } dist = Math.sqrt(dist); if (dist > radiusSmall + radiusBig) return 0; if (dist + radiusSmall <= radiusBig) { //one lies within the other return 1; } else { return (radiusSmall + radiusBig - dist) / (2 * radiusSmall); } } public void combine(SphereCluster cluster) { //double max_radius = distance(center, cluster.getCenter()) + radius + cluster.getRadius(); double[] center = getCenter(); double[] newcenter = new double[center.length]; double[] other_center = cluster.getCenter(); double other_weight = cluster.getWeight(); double other_radius = cluster.getRadius(); for (int i = 0; i < center.length; i++) { newcenter[i] = (center[i] * getWeight() + other_center[i] * other_weight) / (getWeight() + other_weight); } //double overlap = overlapDegree(cluster); center = newcenter; double r_0 = getRadius() + Math.abs(distance(center, newcenter)); double r_1 = other_radius + Math.abs(distance(other_center, newcenter)); radius = Math.max(r_0, r_1); weight += other_weight; } public void merge(SphereCluster cluster) { double[] c0 = getCenter(); double w0 = getWeight(); double r0 = getRadius(); double[] c1 = cluster.getCenter(); double w1 = cluster.getWeight(); double r1 = cluster.getRadius(); //vector double[] v = new double[c0.length]; //center distance double d = 0; for (int i = 0; i < c0.length; i++) { v[i] = c0[i] - c1[i]; d += v[i] * v[i]; } d = Math.sqrt(d); double r = 0; double[] c = new double[c0.length]; //one lays within the others if (d + r0 <= r1 || d + r1 <= r0) { if (d + r0 <= r1) { r = r1; c = c1; } else { r = r0; c = c0; } } else { r = (r0 + r1 + d) / 2.0; for (int i = 0; i < c.length; i++) { c[i] = c1[i] - v[i] / d * (r1 - r); } } setCenter(c); setRadius(r); setWeight(w0 + w1); } @Override public double[] getCenter() { double[] copy = new double[center.length]; System.arraycopy(center, 0, copy, 0, center.length); return copy; } public void setCenter(double[] center) { this.center = center; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } @Override public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } @Override public double getInclusionProbability(Instance instance) { if (getCenterDistance(instance) <= getRadius()) { return 1.0; } return 0.0; } public double getCenterDistance(Instance instance) { double distance = 0.0; //get the center through getCenter so subclass have a chance double[] center = getCenter(); for (int i = 0; i < center.length; i++) { double d = center[i] - instance.value(i); distance += d * d; } return Math.sqrt(distance); } public double getCenterDistance(SphereCluster other) { return distance(getCenter(), other.getCenter()); } /* * the minimal distance between the surface of two clusters. * is negative if the two clusters overlap */ public double getHullDistance(SphereCluster other) { double distance = 0.0; //get the center through getCenter so subclass have a chance double[] center0 = getCenter(); double[] center1 = other.getCenter(); distance = distance(center0, center1); distance = distance - getRadius() - other.getRadius(); return distance; } /* * When a clusters looses points the new minimal bounding sphere can be * partly outside of the originating cluster. If a another cluster is * right next to the original cluster (without overlapping), the new * cluster can be overlapping with this second cluster. OverlapSave * will tell you if the current cluster can degenerate so much that it * overlaps with cluster 'other' */ public boolean overlapSave(SphereCluster other) { //use basic geometry to figure out the maximal degenerated cluster //comes down to Max(radius *(sin alpha + cos alpha)) which is double minDist = Math.sqrt(2) * (getRadius() + other.getRadius()); double diff = getCenterDistance(other) - minDist; // double maxRadius = Math.max(getRadius(),other.getRadius()); // double minDist = (Math.sqrt(2)-1)*maxRadius; // double diff = getHullDistance(other) - minDist; if (diff > 0) return true; else return false; } private double distance(double[] v1, double[] v2) { double distance = 0.0; double[] center = getCenter(); for (int i = 0; i < center.length; i++) { double d = v1[i] - v2[i]; distance += d * d; } return Math.sqrt(distance); } public double[] getDistanceVector(Instance instance) { return distanceVector(getCenter(), instance.toDoubleArray()); } public double[] getDistanceVector(SphereCluster other) { return distanceVector(getCenter(), other.getCenter()); } private double[] distanceVector(double[] v1, double[] v2) { double[] v = new double[v1.length]; for (int i = 0; i < v1.length; i++) { v[i] = v2[i] - v1[i]; } return v; } /** * Samples this cluster by returning a point from inside it. * * @param random a random number source * @return a point that lies inside this cluster */ public Instance sample(Random random) { // Create sample in hypersphere coordinates //get the center through getCenter so subclass have a chance double[] center = getCenter(); final int dimensions = center.length; final double sin[] = new double[dimensions - 1]; final double cos[] = new double[dimensions - 1]; final double length = random.nextDouble() * getRadius(); double lastValue = 1.0; for (int i = 0; i < dimensions - 1; i++) { double angle = random.nextDouble() * 2 * Math.PI; sin[i] = lastValue * Math.sin(angle); // Store cumulative values cos[i] = Math.cos(angle); lastValue = sin[i]; } // Calculate cartesian coordinates double res[] = new double[dimensions]; // First value uses only cosines res[0] = center[0] + length * cos[0]; // Loop through 'middle' coordinates which use cosines and sines for (int i = 1; i < dimensions - 1; i++) { res[i] = center[i] + length * sin[i - 1] * cos[i]; } // Last value uses only sines res[dimensions - 1] = center[dimensions - 1] + length * sin[dimensions - 2]; return new DenseInstance(1.0, res); } @Override protected void getClusterSpecificInfo(ArrayList<String> infoTitle, ArrayList<String> infoValue) { super.getClusterSpecificInfo(infoTitle, infoValue); infoTitle.add("Radius"); infoValue.add(Double.toString(getRadius())); } }