| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.samoa.moa.cluster; |
| |
| import java.util.List; |
| import java.util.Random; |
| |
| import org.apache.samoa.instances.DenseInstance; |
| import org.apache.samoa.instances.Instance; |
| |
| /** |
| * 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 static final long serialVersionUID = 1L; |
| |
| 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 |
| * |
| * 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[] 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); |
| } |
| |
| 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; |
| 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; |
| // 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' |
| * |
| * @param other |
| * the potentially overlapping cluster |
| * @return true if cluster can potentially overlap |
| */ |
| 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; |
| |
| return diff > 0; |
| } |
| |
| 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(List<String> infoTitle, List<String> infoValue) { |
| super.getClusterSpecificInfo(infoTitle, infoValue); |
| infoTitle.add("Radius"); |
| infoValue.add(Double.toString(getRadius())); |
| } |
| |
| } |