moa.cluster.SphereCluster.java Source code

Java tutorial

Introduction

Here is the source code for moa.cluster.SphereCluster.java

Source

/*
 *    SphereCluster.java
 *    Copyright (C) 2010 RWTH Aachen University, Germany
 *    @author Jansen (moa@cs.rwth-aachen.de)
 *
 *    Licensed 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 moa.cluster;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import weka.core.DenseInstance;
import weka.core.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 = 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'
     * 
     * @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;

        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()));
    }

}