org.sf.xrime.algorithms.layout.gfr.RepulsiveForceDisp.java Source code

Java tutorial

Introduction

Here is the source code for org.sf.xrime.algorithms.layout.gfr.RepulsiveForceDisp.java

Source

/*
 * Copyright (C) IBM Corp. 2009.
 * 
 * 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 org.sf.xrime.algorithms.layout.gfr;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.GzipCodec;
import org.apache.hadoop.mapred.FileInputFormat;
import org.apache.hadoop.mapred.FileOutputFormat;
import org.apache.hadoop.mapred.JobClient;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.Mapper;
import org.apache.hadoop.mapred.OutputCollector;
import org.apache.hadoop.mapred.Reducer;
import org.apache.hadoop.mapred.Reporter;
import org.apache.hadoop.mapred.SequenceFileInputFormat;
import org.apache.hadoop.mapred.SequenceFileOutputFormat;
import org.sf.xrime.ProcessorExecutionException;
import org.sf.xrime.algorithms.GraphAlgorithm;
import org.sf.xrime.algorithms.layout.ConstantLabels;
import org.sf.xrime.algorithms.utils.GraphAlgorithmMapReduceBase;
import org.sf.xrime.model.vertex.AdjSetVertex;
import org.sf.xrime.model.vertex.LabeledAdjSetVertex;

/**
 * This algorithm is used to calculate the displacement caused by repulsive force
 * for each vertex. The parallelism is at the grid box level.
 * @author xue
 */
public class RepulsiveForceDisp extends GraphAlgorithm {
    /**
     * Default constructor.
     */
    public RepulsiveForceDisp() {
        super();
    }

    /**
     * Mapper. Aggregate vertexes according to grid boxes, so that all vertexes involved in
     * calculating repulsive force are collected. Then we can calculate repulsive forces in
     * reducer. 
     * @author xue
     */
    public static class MapClass extends GraphAlgorithmMapReduceBase
            implements Mapper<Text, LabeledAdjSetVertex, Text, LabeledAdjSetVertex> {

        @Override
        public void map(Text key, LabeledAdjSetVertex value, OutputCollector<Text, LabeledAdjSetVertex> output,
                Reporter reporter) throws IOException {
            // Get the specified size of the display frame.
            int max_x = Integer.parseInt(context.getParameter(ConstantLabels.MAX_X_COORDINATE));
            int max_y = Integer.parseInt(context.getParameter(ConstantLabels.MAX_Y_COORDINATE));
            // Get the number of vertexes.
            int num_of_vertexes = Integer.parseInt(context.getParameter(ConstantLabels.NUM_OF_VERTEXES));
            // Calculate the parameter k.
            double k = UtilityFunctions.k(max_x, max_y, num_of_vertexes);
            // Calculate the maximum grid x and grid y indexes.
            int max_grid_x_index = UtilityFunctions.grid_x_index(max_x - 1, k);
            int max_grid_y_index = UtilityFunctions.grid_y_index(max_y - 1, k);

            // Get the coordinates of this vertex.
            double x_coordinate = ((DoubleWritable) value.getLabel(ConstantLabels.X_COORDINATE)).get();
            double y_coordinate = ((DoubleWritable) value.getLabel(ConstantLabels.Y_COORDINATE)).get();
            // Get the x index of the grid surrounding this vertex.
            int grid_x_index = UtilityFunctions.grid_x_index(x_coordinate, k);
            int grid_y_index = UtilityFunctions.grid_y_index(y_coordinate, k);

            // Collect this vertex first.
            output.collect(new Text(UtilityFunctions.grid_index_as_string(grid_x_index, grid_y_index)), value);

            // Create a temp vertex as notifier.
            LabeledAdjSetVertex temp_vertex = new LabeledAdjSetVertex();
            temp_vertex.setId(key.toString());
            temp_vertex.setLabel(ConstantLabels.X_COORDINATE, new DoubleWritable(x_coordinate));
            temp_vertex.setLabel(ConstantLabels.Y_COORDINATE, new DoubleWritable(y_coordinate));
            temp_vertex.setStringLabel(ConstantLabels.SURROUNDING, "true");

            // Upper left.
            if (grid_x_index > 0 && grid_y_index > 0) {
                String upper_left = UtilityFunctions.grid_index_as_string(grid_x_index - 1, grid_y_index - 1);
                output.collect(new Text(upper_left), temp_vertex);
            }
            // Upper.
            if (grid_y_index > 0) {
                String upper = UtilityFunctions.grid_index_as_string(grid_x_index, grid_y_index - 1);
                output.collect(new Text(upper), temp_vertex);
            }
            // Upper right.
            if (grid_x_index < max_grid_x_index && grid_y_index > 0) {
                String upper_right = UtilityFunctions.grid_index_as_string(grid_x_index + 1, grid_y_index - 1);
                output.collect(new Text(upper_right), temp_vertex);
            }
            // Left.
            if (grid_x_index > 0) {
                String left = UtilityFunctions.grid_index_as_string(grid_x_index - 1, grid_y_index);
                output.collect(new Text(left), temp_vertex);
            }
            // Right.
            if (grid_x_index < max_grid_x_index) {
                String right = UtilityFunctions.grid_index_as_string(grid_x_index + 1, grid_y_index);
                output.collect(new Text(right), temp_vertex);
            }
            // Lower left.
            if (grid_x_index > 0 && grid_y_index < max_grid_y_index) {
                String lower_left = UtilityFunctions.grid_index_as_string(grid_x_index - 1, grid_y_index + 1);
                output.collect(new Text(lower_left), temp_vertex);
            }
            // Lower.
            if (grid_y_index < max_grid_y_index) {
                String lower = UtilityFunctions.grid_index_as_string(grid_x_index, grid_y_index + 1);
                output.collect(new Text(lower), temp_vertex);
            }
            // Lower right.
            if (grid_x_index < max_grid_x_index && grid_y_index < max_grid_y_index) {
                String lower_right = UtilityFunctions.grid_index_as_string(grid_x_index + 1, grid_y_index + 1);
                output.collect(new Text(lower_right), temp_vertex);
            }
        }
    }

    /**
     * Reducer. Calculate displacement of each vertex caused by repulsive force. Each time processes
     * vertexes within a grid box.
     * @author xue
     */
    public static class ReduceClass extends GraphAlgorithmMapReduceBase
            implements Reducer<Text, LabeledAdjSetVertex, Text, LabeledAdjSetVertex> {
        /**
         * A private class used by this algorithm.
         * @author xue
         */
        private static class Triple {
            @SuppressWarnings("unused")
            public String id;
            public double x;
            public double y;
        }

        /**
         * Get vertex v and u ready.
         * @param values
         * @param vertexes_in_box
         * @param ids_involved
         * @param x_involved
         * @param y_involved
         */
        private void accummulate_data(Iterator<LabeledAdjSetVertex> values,
                HashMap<String, LinkedList<LabeledAdjSetVertex>> vertexes_in_box,
                HashMap<String, LinkedList<Triple>> vertexes_involved) {

            // Get v and u ready.
            while (values.hasNext()) {
                LabeledAdjSetVertex curr_vertex = values.next();
                if (curr_vertex.getStringLabel(ConstantLabels.SURROUNDING) == null) {
                    // Here is the vertexes within this grid box.
                    LabeledAdjSetVertex new_vertex = new LabeledAdjSetVertex((AdjSetVertex) curr_vertex);
                    new_vertex.setLabel(ConstantLabels.X_COORDINATE,
                            curr_vertex.getLabel(ConstantLabels.X_COORDINATE));
                    new_vertex.setLabel(ConstantLabels.Y_COORDINATE,
                            curr_vertex.getLabel(ConstantLabels.Y_COORDINATE));

                    // Determine the coordinates.
                    double x = ((DoubleWritable) curr_vertex.getLabel(ConstantLabels.X_COORDINATE)).get();
                    double y = ((DoubleWritable) curr_vertex.getLabel(ConstantLabels.Y_COORDINATE)).get();

                    // Create a string as key.
                    String coordinate_str = "(" + x + "," + y + ")";

                    // Accummulate this vertex in the hash map.
                    if (vertexes_in_box.get(coordinate_str) == null) {
                        // Create a new linked list.
                        LinkedList<LabeledAdjSetVertex> new_list = new LinkedList<LabeledAdjSetVertex>();
                        new_list.add(new_vertex);
                        // Add it in the hash map.
                        vertexes_in_box.put(coordinate_str, new_list);
                    } else {
                        vertexes_in_box.get(coordinate_str).add(new_vertex);
                    }

                    // Create a triple.
                    Triple new_triple = new Triple();
                    new_triple.id = curr_vertex.getId();
                    new_triple.x = x;
                    new_triple.y = y;
                    // Record it.
                    if (vertexes_involved.get(coordinate_str) == null) {
                        // Create a new linked list.
                        LinkedList<Triple> new_list = new LinkedList<Triple>();
                        new_list.add(new_triple);
                        // Add the triple into the hash map.
                        vertexes_involved.put(coordinate_str, new_list);
                    } else {
                        vertexes_involved.get(coordinate_str).add(new_triple);
                    }
                } else {
                    // Vertexes within surrounding grid boxes.
                    double x = ((DoubleWritable) curr_vertex.getLabel(ConstantLabels.X_COORDINATE)).get();
                    double y = ((DoubleWritable) curr_vertex.getLabel(ConstantLabels.Y_COORDINATE)).get();

                    // Create a string as key.
                    String coordinate_str = "(" + x + "," + y + ")";

                    // Create a triple.
                    Triple new_triple = new Triple();
                    new_triple.id = curr_vertex.getId();
                    new_triple.x = x;
                    new_triple.y = y;
                    // Record it.
                    if (vertexes_involved.get(coordinate_str) == null) {
                        // Create a new linked list.
                        LinkedList<Triple> new_list = new LinkedList<Triple>();
                        new_list.add(new_triple);
                        // Add the triple into the hash map.
                        vertexes_involved.put(coordinate_str, new_list);
                    } else {
                        vertexes_involved.get(coordinate_str).add(new_triple);
                    }
                }
            }
        }

        @Override
        public void reduce(Text key, Iterator<LabeledAdjSetVertex> values,
                OutputCollector<Text, LabeledAdjSetVertex> output, Reporter reporter) throws IOException {
            // Get the specified size of the display frame.
            int max_x = Integer.parseInt(context.getParameter(ConstantLabels.MAX_X_COORDINATE));
            int max_y = Integer.parseInt(context.getParameter(ConstantLabels.MAX_Y_COORDINATE));
            // Get the number of vertexes.
            int num_of_vertexes = Integer.parseInt(context.getParameter(ConstantLabels.NUM_OF_VERTEXES));
            // Calculate the parameter k.
            double k = UtilityFunctions.k(max_x, max_y, num_of_vertexes);

            // This hash map is used to accommodate vertexes in the box. We use a hash map since multiple
            // vertexes may have the same coordinates.
            HashMap<String, LinkedList<LabeledAdjSetVertex>> vertexes_in_box = new HashMap<String, LinkedList<LabeledAdjSetVertex>>();
            // This hash map is used to accommodate vertexes involved in repulsive force calculation for
            // this box.
            HashMap<String, LinkedList<Triple>> vertexes_involved = new HashMap<String, LinkedList<Triple>>();

            // Get vertexes u and v ready.
            accummulate_data(values, vertexes_in_box, vertexes_involved);

            // Use the thread name as an indicator.
            Thread.currentThread().setName(
                    "XRIME-" + key.toString() + ":" + vertexes_in_box.size() + ":" + vertexes_involved.size());

            // Loop over all vertexes in this grid box.
            for (Iterator<String> iterator_in_box = vertexes_in_box.keySet().iterator(); iterator_in_box
                    .hasNext();) {
                // Get the list of vertexes in this box which have the same coordinates.
                LinkedList<LabeledAdjSetVertex> curr_in_box_list = vertexes_in_box.get(iterator_in_box.next());

                // Get the position of v, which represents potential a list of vertexes.
                LabeledAdjSetVertex v_vertex = curr_in_box_list.get(0);
                // String v_id = v_vertex.getId();
                double v_x = ((DoubleWritable) v_vertex.getLabel(ConstantLabels.X_COORDINATE)).get();
                double v_y = ((DoubleWritable) v_vertex.getLabel(ConstantLabels.Y_COORDINATE)).get();
                PlaneVector v_pos = new PlaneVector(v_x, v_y);

                // Newly create a displacement vector.
                PlaneVector v_disp = new PlaneVector(0, 0);

                // Loop over all vertexes in this grid box and surronding grid boxes.
                for (Iterator<String> iterator_involved = vertexes_involved.keySet().iterator(); iterator_involved
                        .hasNext();) {
                    // Get the list of vertexes involved which have the same coordinates.
                    LinkedList<Triple> curr_involved_list = vertexes_involved.get(iterator_involved.next());

                    // Get the position of u.
                    Triple u_triple = curr_involved_list.get(0);
                    // String u_id = curr_triple.id;
                    double u_x = u_triple.x;
                    double u_y = u_triple.y;
                    PlaneVector u_pos = new PlaneVector(u_x, u_y);

                    // In case when these two lists of vertexes overlap. Sometimes it just happens.
                    if (v_x == u_x && v_y == u_y) {
                        // Skip, will be taken care of at the end of outer loop.
                        continue;
                    }

                    // Since we have skipped all vertexes with same coordinates, we are sure that there is no
                    // vertexes with same identifier now. Now we calculate displacement caused by repulsive 
                    // force.

                    // Calculate the delta.
                    PlaneVector delta = v_pos.minus(u_pos);

                    // If the distance larger than 2k, ignore this replusive force.
                    if (delta.magnitude() >= (2 * k)) {
                        // Ignore this repulsive force.
                    } else {
                        // Calculate new displacement of v. Note that the base displacement will be multiplied with
                        // number of vertexes in curr_involved_list.
                        try {
                            v_disp = v_disp.plus(
                                    delta.normalize().multiply_scalar(UtilityFunctions.f_r(k, delta.magnitude()))
                                            .multiply_scalar(curr_involved_list.size()));
                        } catch (ArithmeticException e) {
                            // Possibly divide by zero. Do nothing for now.
                        }
                    }
                }

                if (curr_in_box_list.size() > 1) {
                    // We have multiple overlapped vertexes in box, need to separate them. Try to arrange them
                    // along a circle.

                    // Determine the magnitude of the displacement of this vertex.
                    double radius_base = v_disp.magnitude();
                    // The radius used to calculate the delta for each overlapped vertex. This is a tricky point.
                    double radius = (radius_base / 10) > (k / 4) ? radius_base / 10 : k / 4;

                    for (int i = 0; i < curr_in_box_list.size(); i++) {
                        // Deal with each vertex with the same coordinates.
                        LabeledAdjSetVertex vert = curr_in_box_list.get(i);
                        // Determine an adjustment vertex.
                        PlaneVector adjust_vec = new PlaneVector(
                                radius * Math.cos(i * ((2 * Math.PI) / curr_in_box_list.size())),
                                -radius * Math.sin(i * ((2 * Math.PI) / curr_in_box_list.size())));
                        // Coordinates after adjustment.
                        PlaneVector vert_disp = v_disp.plus(adjust_vec);
                        // Output the displacement of v along with its original information.
                        vert.setLabel(ConstantLabels.X_DISP, new DoubleWritable(vert_disp.getX()));
                        vert.setLabel(ConstantLabels.Y_DISP, new DoubleWritable(vert_disp.getY()));
                        // Collect this.
                        output.collect(new Text(vert.getId()), vert);
                    }
                } else {
                    // Get this only vertex.
                    LabeledAdjSetVertex vert = curr_in_box_list.get(0);
                    // Output the displacement of v along with its original information.
                    vert.setLabel(ConstantLabels.X_DISP, new DoubleWritable(v_disp.getX()));
                    vert.setLabel(ConstantLabels.Y_DISP, new DoubleWritable(v_disp.getY()));
                    // Collect this.
                    output.collect(new Text(vert.getId()), vert);
                }
            }
        }
    }

    @Override
    public void execute() throws ProcessorExecutionException {
        JobConf conf = new JobConf(context, RepulsiveForceDisp.class);
        conf.setJobName("RepulsiveForceDisp");

        conf.setOutputKeyClass(Text.class);
        conf.setOutputValueClass(LabeledAdjSetVertex.class);
        conf.setMapperClass(MapClass.class);
        // No combiner is permitted, since the logic of reducer depends on the completeness
        // of information.
        conf.setReducerClass(ReduceClass.class);
        // makes the file format suitable for machine processing.
        conf.setInputFormat(SequenceFileInputFormat.class);
        conf.setOutputFormat(SequenceFileOutputFormat.class);
        // Enable compression.
        conf.setCompressMapOutput(true);
        conf.setMapOutputCompressorClass(GzipCodec.class);
        try {
            FileInputFormat.setInputPaths(conf, getSource().getPath());
            FileOutputFormat.setOutputPath(conf, getDestination().getPath());
        } catch (IllegalAccessException e1) {
            throw new ProcessorExecutionException(e1);
        }
        conf.setNumMapTasks(getMapperNum());
        conf.setNumReduceTasks(getReducerNum());

        try {
            this.runningJob = JobClient.runJob(conf);
        } catch (IOException e) {
            throw new ProcessorExecutionException(e);
        }
    }
}