All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.integratedmodelling.engine.geospace.kmeans.KMeansFrame Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *  Copyright (C) 2007, 2014:
 *  
 *    - Ferdinando Villa 
 *    - integratedmodelling.org
 *    - any other authors listed in @author annotations
 *
 *    All rights reserved. This file is part of the k.LAB software suite,
 *    meant to enable modular, collaborative, integrated 
 *    development of interoperable data and model components. For
 *    details, see http://integratedmodelling.org.
 *    
 *    This program is free software; you can redistribute it and/or
 *    modify it under the terms of the Affero General Public License 
 *    Version 3 or any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but without any warranty; without even the implied warranty of
 *    merchantability or fitness for a particular purpose.  See the
 *    Affero General Public License for more details.
 *  
 *     You should have received a copy of the Affero General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *     The license is also available at: https://www.gnu.org/licenses/agpl.html
 *******************************************************************************/
package org.integratedmodelling.engine.geospace.kmeans;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Random;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 * Swing-based program for testing the versions of K-Means on 
 * randomly generated data.
 */
public class KMeansFrame extends JFrame implements ActionListener, KMeansListener {

    JPanel contentPane;
    BorderLayout borderLayout1 = new BorderLayout();
    JScrollPane mMessageAreaSP = new JScrollPane();
    JTextArea mMessageArea = new JTextArea();
    JButton mRunButton = new JButton();
    JPanel mTopPanel = new JPanel();
    JLabel mImplementationLabel = new JLabel();
    JTextField mRandomSeedTF = new JTextField();
    JLabel mRandomSeedLabel = new JLabel();
    JTextField mClusterCountTF = new JTextField();
    JLabel mClusterCountLabel = new JLabel();
    JTextField mCoordCountTF = new JTextField();
    JLabel mCountLabel = new JLabel();
    JComboBox mImplementationCB = new JComboBox();
    GridBagLayout gridBagLayout1 = new GridBagLayout();
    JLabel mThreadCountLabel = new JLabel();
    JTextField mThreadCountTF = new JTextField();

    private boolean mRunning;
    private KMeans mKMeans;

    private static final String BASIC_KMEANS = "Basic K-Means Clustering";
    private static final String BENCHMARKED_KMEANS = "Benchmarked K-Means Clustering";
    private static final String CONCURRENT_KMEANS = "Concurrent K-Means Clustering";

    public KMeansFrame() {

        setDefaultCloseOperation(EXIT_ON_CLOSE);

        contentPane = (JPanel) getContentPane();
        contentPane.setLayout(borderLayout1);
        setSize(new Dimension(620, 760));
        setTitle("KMeans Test");
        mMessageArea.setText("");
        mRunButton.setText("Run KMeans");
        mRunButton.addActionListener(this);
        mImplementationLabel.setText("KMeans Implementation:");
        mImplementationCB.addActionListener(this);
        mRandomSeedTF.setText("1234");
        mRandomSeedTF.setColumns(10);
        mRandomSeedLabel.setText("Random Seed:");
        mClusterCountTF.setText("300");
        mClusterCountTF.setColumns(10);
        mClusterCountLabel.setText("Number of Clusters (K):");
        mCoordCountTF.setText("25000");
        mCoordCountTF.setColumns(10);
        mCountLabel.setText("Number of Coordinates (N):");
        mThreadCountLabel.setEnabled(false);
        mThreadCountLabel.setText("Number of Threads:");
        mThreadCountTF.setEnabled(false);
        // Initialize the thread count textfield with the
        // number of available processors.
        mThreadCountTF.setText(String.valueOf(Runtime.getRuntime().availableProcessors()));
        mThreadCountTF.setColumns(10);
        mTopPanel.setLayout(gridBagLayout1);
        contentPane.add(mMessageAreaSP, java.awt.BorderLayout.CENTER);
        contentPane.add(mTopPanel, java.awt.BorderLayout.NORTH);
        mMessageAreaSP.getViewport().add(mMessageArea);
        mTopPanel.add(mCountLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
                GridBagConstraints.NONE, new Insets(10, 10, 5, 0), 0, 0));
        mTopPanel.add(mCoordCountTF, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE, new Insets(10, 0, 5, 10), 0, 0));
        mTopPanel.add(mClusterCountLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
                GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 10, 5, 0), 0, 0));
        mTopPanel.add(mClusterCountTF, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE, new Insets(0, 0, 5, 10), 0, 0));
        mTopPanel.add(mRandomSeedLabel, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
                GridBagConstraints.NONE, new Insets(0, 10, 5, 0), 0, 0));
        mTopPanel.add(mRandomSeedTF, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE, new Insets(0, 0, 5, 10), 0, 0));
        mTopPanel.add(mImplementationLabel, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
                GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 10, 5, 0), 0, 0));
        mTopPanel.add(mImplementationCB, new GridBagConstraints(3, 0, 1, 1, 1.0, 0.0,
                GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 5, 10), 0, 0));
        mTopPanel.add(mThreadCountLabel, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
                GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 10, 5, 0), 0, 0));
        mTopPanel.add(mThreadCountTF, new GridBagConstraints(3, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
                GridBagConstraints.NONE, new Insets(0, 2, 5, 10), 0, 0));
        mTopPanel.add(mRunButton, new GridBagConstraints(3, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER,
                GridBagConstraints.HORIZONTAL, new Insets(0, 0, 5, 10), 0, 0));

        mImplementationCB.addItem(BASIC_KMEANS);
        mImplementationCB.addItem(BENCHMARKED_KMEANS);
        mImplementationCB.addItem(CONCURRENT_KMEANS);
    }

    /**
     * Method for validating entries typed into text fields.
     */
    private static long getEnteredValue(JTextField tf, long min, long max) {
        long value = 0L;
        String s = tf.getText().trim();
        if (s.length() == 0) {
            throw new RuntimeException("blank entry");
        }
        try {
            value = Long.parseLong(s);
            if (value < min || value > max) {
                throw new RuntimeException("not in range [" + min + " - " + max + "]");
            }
        } catch (NumberFormatException nfe) {
            throw new RuntimeException("invalid number");
        }
        return value;
    }

    /**
     * Generates the coordinates to be clustered.
     * 
     * @param coordCount the number of coordinates.
     * @param dimensions the length of the coordinates.
     * @param clusterCount the number of clusters in the distribution.
     * @param randomSeed the seed used by the random number generator.
     * @return
     */
    private static double[][] generateCoordinates(int coordCount, int dimensions, int clusterCount,
            long randomSeed) throws InsufficientMemoryException {

        // Explicit garbage collection to reduce the likelihood of 
        // having insufficient memory.
        System.gc();

        long memRequired = 8L * (long) dimensions * (long) (coordCount + clusterCount);
        if (Runtime.getRuntime().freeMemory() < memRequired) {
            throw new InsufficientMemoryException();
        }

        double[][] coordinates = new double[coordCount][dimensions];
        double[][] exemplars = new double[clusterCount][dimensions];

        Random random = new Random(randomSeed);
        for (int i = 0; i < clusterCount; i++) {
            for (int j = 0; j < dimensions; j++) {
                exemplars[i][j] = 100.0 * random.nextDouble();
            }
        }

        for (int i = 0; i < coordCount; i++) {
            int cluster = random.nextInt(clusterCount);
            double[] exemplar = exemplars[cluster];
            double[] coord = coordinates[i];
            for (int j = 0; j < dimensions; j++) {
                coord[j] = exemplar[j] + 50 * random.nextGaussian();
            }
        }

        return coordinates;
    }

    public synchronized void actionPerformed(ActionEvent e) {

        if (e.getSource() == mRunButton && !mRunning) {

            // Ensure entered parameters make sense.
            try {

                int coordCount = (int) getEnteredValue(mCoordCountTF, 1L, (long) Integer.MAX_VALUE);
                int clusterCount = (int) getEnteredValue(mClusterCountTF, 1L, (long) (coordCount - 1));
                long randomSeed = getEnteredValue(mRandomSeedTF, Long.MIN_VALUE, Long.MAX_VALUE);

                double[][] coordinates = generateCoordinates(coordCount, 100, clusterCount, randomSeed);

                String implementation = (String) mImplementationCB.getSelectedItem();
                if (implementation == BASIC_KMEANS) {
                    mKMeans = new BasicKMeans(coordinates, clusterCount, 500, randomSeed);
                } else if (implementation == BENCHMARKED_KMEANS) {
                    mKMeans = new BenchmarkedKMeans(coordinates, clusterCount, 500, randomSeed);
                } else if (implementation == CONCURRENT_KMEANS) {
                    try {
                        int threadCount = (int) getEnteredValue(mThreadCountTF, 1L, 20L);
                        mKMeans = new ConcurrentKMeans(coordinates, clusterCount, 500, randomSeed,
                                threadCount);
                    } catch (RuntimeException rte2) {
                        JOptionPane.showMessageDialog(this,
                                "The thread count entry is invalid (" + rte2.getMessage()
                                        + ").\nPlease enter a thread count in the range [1 - 20].",
                                "Invalid Entry", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                }

                if (mKMeans != null) {
                    // Force gc, so resources taken up by a previous run that haven't
                    // been freed yet, will not affect this test.
                    System.gc();

                    mMessageArea.setText("");
                    mKMeans.addKMeansListener(this);
                    mRunButton.setEnabled(false);
                    new Thread(mKMeans).start();
                    mRunning = true;
                }

            } catch (InsufficientMemoryException ime) {

                displayInsufficientMemoryDialog();

            } catch (RuntimeException rte) {

                JOptionPane.showMessageDialog(this, "One or more entries are invalid (" + rte.getMessage()
                        + ").\nPlease enter positive numbers for the number of coordinates and clusters\n"
                        + "The number of clusters must be less than the number of coordinates.",
                        "Invalid Entries", JOptionPane.ERROR_MESSAGE);

            }

        } else if (e.getSource() == mImplementationCB) {
            boolean b = mImplementationCB.getSelectedItem() == CONCURRENT_KMEANS;
            mThreadCountLabel.setEnabled(b);
            mThreadCountTF.setEnabled(b);
        }
    }

    /**
     * Displays an error dialog stating that insufficient memory is
     * available.
     */
    private void displayInsufficientMemoryDialog() {
        JOptionPane.showMessageDialog(this, "Insufficient memory is available.  Try reducing the \n"
                + "number of coordinates and/or the number of clusters.", "Insufficient Memory",
                JOptionPane.ERROR_MESSAGE);
    }

    /**
     * Cleanup after completion of k-means.
     */
    private void cleanupAfterKMeans() {
        if (mKMeans != null) {
            mKMeans.removeKMeansListener(this);
            mKMeans = null;
        }
        mRunning = false;
        mRunButton.setEnabled(true);
    }

    public void kmeansMessage(String message) {
        displayText(message);
    }

    public void kmeansComplete(Cluster[] clusters, long executionTime) {
        displayText("K-Means complete: processing time (ms) = " + executionTime);
        displayText("Number of clusters: " + clusters.length);
        cleanupAfterKMeans();
    }

    public void kmeansError(Throwable err) {
        cleanupAfterKMeans();
        if (err instanceof InsufficientMemoryException) {
            displayText("K-Means aborted because of insufficient memory.");
            displayInsufficientMemoryDialog();
        } else {
            Throwable t = (Throwable) err;
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            displayText(sw.toString());
        }
    }

    private void displayText(String text) {
        mMessageArea.append(text);
        if (!text.endsWith("\n")) {
            mMessageArea.append("\n");
        }
    }

    /**
     * Application entry point.
     *
     * @param args String[]
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception exception) {
                    exception.printStackTrace();
                }

                KMeansFrame frame = new KMeansFrame();
                frame.validate();

                // Center the window
                Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
                Dimension frameSize = frame.getSize();
                if (frameSize.height > screenSize.height) {
                    frameSize.height = screenSize.height;
                }
                if (frameSize.width > screenSize.width) {
                    frameSize.width = screenSize.width;
                }
                frame.setLocation((screenSize.width - frameSize.width) / 2,
                        (screenSize.height - frameSize.height) / 2);
                frame.setVisible(true);
            }
        });
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy