diff --git a/scripts/loadbalancing.sh b/scripts/loadbalancing.sh index 035a2a6..0f30a84 100755 --- a/scripts/loadbalancing.sh +++ b/scripts/loadbalancing.sh @@ -13,6 +13,7 @@ fi exec java \ -classpath "$ESALSA_HOME/lib/"'*' \ -Dlog4j.configuration=file:"$IPL_HOME"/log4j.properties \ + -Dorg.slf4j.simpleLogger.defaultLogLevel=TRACE \ nl.esciencecenter.esalsa.tools.LoadBalancing \ "$@" diff --git a/scripts/optimize.sh b/scripts/optimize.sh new file mode 100755 index 0000000..3914f30 --- /dev/null +++ b/scripts/optimize.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script is a convenience script to automatically set the correct +# classpath for the eSalsa Tools given the location of an installation +# specified in the $ESALSA_HOME environment variable. + +# Check setting of ESALSA_HOME +if [ -z "$ESALSA_HOME" ]; then + echo "please set ESALSA_HOME to the location of your eSalsa Tools installation" 1>&2 + exit 1 +fi + +exec java \ + -classpath "$ESALSA_HOME/lib/"'*' \ + nl.esciencecenter.esalsa.util.OptimizeTopography \ + "$@" + diff --git a/src/nl/esciencecenter/esalsa/loadbalancer/LoadBalancer.java b/src/nl/esciencecenter/esalsa/loadbalancer/LoadBalancer.java index 759b180..01f6fa9 100644 --- a/src/nl/esciencecenter/esalsa/loadbalancer/LoadBalancer.java +++ b/src/nl/esciencecenter/esalsa/loadbalancer/LoadBalancer.java @@ -19,381 +19,422 @@ import java.util.ArrayList; import nl.esciencecenter.esalsa.util.Block; +import nl.esciencecenter.esalsa.util.Coordinate; import nl.esciencecenter.esalsa.util.Distribution; import nl.esciencecenter.esalsa.util.Grid; import nl.esciencecenter.esalsa.util.Layer; import nl.esciencecenter.esalsa.util.Layers; -import nl.esciencecenter.esalsa.util.Neighbours; import nl.esciencecenter.esalsa.util.Set; import nl.esciencecenter.esalsa.util.Statistics; -import nl.esciencecenter.esalsa.util.Topography; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * - * LoadBalancer is used to generate a block distribution for the Parallel Ocean Program (POP). This block distribution is based - * on the ocean topography, the desired block size, and the desired number of clusters, nodes per cluster, and cores per node. + * LoadBalancer is used to generate a block distribution for the Parallel Ocean Program (POP). This block distribution is based on + * the ocean topography, the desired block size, and the desired number of clusters, nodes per cluster, and cores per node. + * * @author Jason Maassen * @version 1.0 * @since 1.0 * */ public class LoadBalancer { - - /** Logger used for debugging */ - private static final Logger logger = LoggerFactory.getLogger(LoadBalancer.class); - - /** The number of clusters to use (default = 1) */ - private final int clusters; - - /** The number of nodes to use. */ - private final int nodes; - - /** The number of cores to use. */ - private final int cores; - - /** The topography on which the grid is defined */ - private final Topography topography; - - /** The function object used the determine block neighbours. */ - private final Neighbours neighbours; - - /** The grid containing all blocks. */ - private final Grid grid; - - /** The store for generated layers. */ - private final Layers layers; - - /** The layer at which the individual blocks are defined. */ - private final Layer blockLayer; - - /** The layer containing a single set with all blocks. */ - private final Layer combinedLayer; - - /** List of all blocks that have been created. */ - private final ArrayList allBlocks = new ArrayList(); - - /** Method to use when splitting */ - private final String splitMethod; - - /** - * Create a LoadBalancer for the provided topography. - * - * @param layers the store for the layers - * @param neighbours the function object used to determine the block neighbors. - * @param grid the grid containing all blocks. - * @param blockWidth the width of a block. - * @param blockHeight the height of a block. - * @param clusters the desired number of clusters. - * @param nodes the desired number of nodes. - * @param cores the desired number of cores. - * @throws Exception if the LoadBalancer could not be initialized. - */ - public LoadBalancer(Layers layers, Neighbours neighbours, Topography topography, Grid grid, int blockWidth, int blockHeight, - int clusters, int nodes, int cores, String splitMethod) throws Exception { - - this.layers = layers; - this.neighbours = neighbours; - this.topography = topography; - this.grid = grid; - - this.clusters = clusters; - this.nodes = nodes; - this.cores = cores; - - this.splitMethod = splitMethod; - - // Create two layers here, one containing each block in a separate set, and one containing all blocks in one set. - blockLayer = new Layer("BLOCKS"); - combinedLayer = new Layer("ALL"); - - for (int y=0;yparts) of subsets and store these subsets in the output - * layer. - * - * @param set the input to split. - * @param subsets the number of subsets to create. - * @param output the output layer in which to store the subsets. - * @throws Exception if the split failed. - */ - private void split(Set set, int subsets, Layer output) throws Exception { - - System.out.println("Splitting set of " + set.size() + " into " + subsets + " subsets"); - - // Creating a subset of size 1 is easy. - if (subsets == 1) { - output.add(new Set(set, 0)); - return; - } - - // If the set contains (less than) the amount of part we need, - // it is easy to split it. - final int size = set.size(); - - if (size <= subsets) { - for (int i=0;i next = new ArrayList(); - - Split split = null; - - if (splitMethod.equalsIgnoreCase("simple")) { - split = new SimpleSplit(set, subsets); - } else if (splitMethod.equalsIgnoreCase("roughlyrect")) { - split = new RoughlyRectangularSplit(set, subsets); - } else if (splitMethod.equalsIgnoreCase("search")) { - split = new SearchSplit(set, subsets, neighbours); - } - - if (split == null) { - throw new Exception("Unknown split method: " + splitMethod); - } - - split.split(next); - - output.addAll(next); - set.addSubSets(next); - - -/* - - // Otherwise, we try to split parts into its prime factors. - int [] primes = PrimeFactorization.factor(subsets); - - if (logger.isDebugEnabled()) { - logger.debug("Got primes " + Arrays.toString(primes)); - } - - ArrayList current = new ArrayList(); - current.add(set); - - ArrayList next = new ArrayList(); - - for (int i=0;i tmp = current; - current = next; - next = tmp; - } - - output.addAll(current); - set.addSubSets(current); -*/ - } - - /** - * Split an existing layer previousLayer into subsets subsets and store these subsets in a new layer - * newLayer. - * - * @param previousLayer the name of the existing layer to split. - * @param newLayer the name of the new layer to create. - * @param subsets the number of subsets to create. - * @throws Exception if the subsets could not be created. - */ - private void split(String previousLayer, String newLayer, int subsets) throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug("split " + previousLayer + " " + newLayer + " " + subsets); - } - - Layer current = layers.get(previousLayer); - Layer result = new Layer(newLayer); - - for (int i=0;ilayer to console. - * - * @param layer the name of the layer to print statistics for, or ALL to print statistics on all layers. - */ - public void printStatistics(String layer) throws Exception { - new Statistics(layers, neighbours).printStatistics(layer, System.out); - } - - /** - * Mark all blocks in a set with a certain value. - * - * @param set the set containing the blocks to mark. - * @param value the value to mark the blocks with. - */ - private void markAll(Set set, int value) { - for (Block b : set) { - b.setMark(value); - } - } - - /** - * Assign work to cores by recursively marking the sets in the various layers. - * - * @param set the set containing the blocks to mark. - * @return the number of new subsets encountered. - */ - private int divideWork(Set set) { - - if (set.countSubSets() == 0) { - return 1; - } - - int count = 0; - - for (Set sub : set.getSubSets()) { - - for (Block b : sub) { - b.addToMark(count); - } - - count += divideWork(sub); - } - - return count; - } - - /** - * Assign work to cores by recursively marking the sets in the various layers. - * - * This algorithm works as follows: - * - * - Initially all blocks in the combined layer are marked with 0. - * - Next, we call {@link #divideWork(Set)} to assign all work in the subsets of the combined set. - * - * The {@link #divideWork(Set)} then recursively counts the number of subsets of the combined set, adding the number of - * subsets it has seen so far to the mark of each of the blocks in the subsets. - * - * As a result: - * - * - all blocks in a CORE set will end up with the same mark - * - all blocks in NODE set will have marks ranging from (X .. X+coresPerNode), - * - all blocks in a CLUSTER set will have marks ranging from (Y ... Y+(corePerNode*nodesPerCLuster)) - * - */ - private void divideWork() { - markAll(combinedLayer.get(0), 0); - - if (layers.size() == 2) { - // No additional layers have been defined, so directly assign the blocks. - - int core = 0; - - for (int y=0;y 1) { - split(prev, "CLUSTERS", clusters); - prev = "CLUSTERS"; - } - - if (nodes > 1) { - split(prev, "NODES", nodes); - prev = "NODES"; - } - - if (cores >= 1) { - split(prev, "CORES", cores); - } - - divideWork(); - - Layer layer = layers.get("CORES"); - - if (layer == null || layer.size() != (cores*nodes*clusters)) { - throw new Exception("INTERNAL ERROR: Failed to retrieve CORE layer with " + (cores*nodes*clusters) + " cores! (" - + (layer == null ? "NULL" : "" + layer.size()) + ")"); - } - - int [] result = new int[grid.width*grid.height]; - int maxBlocksPerCore = 0; - int minBlocksPerCore = Integer.MAX_VALUE; - - for (Set s : layer) { - - int blocks = s.size(); - - if (blocks > maxBlocksPerCore) { - maxBlocksPerCore = blocks; - } - - if (blocks < minBlocksPerCore) { - minBlocksPerCore = blocks; - } - - for (Block b : s) { - result[b.coordinate.y * grid.width + b.coordinate.x] = b.getMark()+1; - } - } - - return new Distribution(topography.width, topography.height, - grid.blockWidth, grid.blockHeight, - clusters, nodes, cores, - minBlocksPerCore, maxBlocksPerCore, - grid.width * grid.height, result); - } + + /** Logger used for debugging */ + private static final Logger logger = LoggerFactory.getLogger(LoadBalancer.class); + + /** The number of clusters to use (default = 1) */ + private final int clusters; + + /** The number of nodes to use. */ + private final int nodes; + + /** The number of cores to use. */ + private final int cores; + + /** The width of topography on which the grid is defined */ + private final int topographyWidth; + + /** The width of topography on which the grid is defined */ + private final int topographyHeight; + + /** The grid containing all blocks. */ + private final Grid grid; + + /** The store for generated layers. */ + private final Layers layers; + + /** The layer at which the individual blocks are defined. */ + private final Layer blockLayer; + + /** The layer containing a single set with all blocks. */ + private final Layer combinedLayer; + + /** List of all blocks that have been created. */ + private final ArrayList allBlocks = new ArrayList(); + + /** Method to use when splitting */ + private final String splitMethod; + + /** + * Create a LoadBalancer for the provided topography. + * + * @param layers + * the store for the layers + * @param neighbours + * the function object used to determine the block neighbors. + * @param grid + * the grid containing all blocks. + * @param blockWidth + * the width of a block. + * @param blockHeight + * the height of a block. + * @param clusters + * the desired number of clusters. + * @param nodes + * the desired number of nodes. + * @param cores + * the desired number of cores. + * @throws Exception + * if the LoadBalancer could not be initialized. + */ + public LoadBalancer(Layers layers, int topographyWidth, int topographyHeight, Grid grid, int blockWidth, int blockHeight, + int clusters, int nodes, int cores, String splitMethod) throws Exception { + + this.layers = layers; + this.topographyWidth = topographyWidth; + this.topographyHeight = topographyHeight; + this.grid = grid; + + this.clusters = clusters; + this.nodes = nodes; + this.cores = cores; + + this.splitMethod = splitMethod; + + // Create two layers here, one containing each block in a separate set, and one containing all blocks in one set. + blockLayer = new Layer("BLOCKS"); + combinedLayer = new Layer("ALL"); + + for (int y = 0; y < grid.height; y++) { + for (int x = 0; x < grid.width; x++) { + + Block b = grid.get(x, y); + + if (b.ocean) { + blockLayer.add(new Set(b, y * grid.width + x)); + allBlocks.add(b); + } + } + } + + System.out.println("ALL: " + allBlocks.size()); + System.out.println("BLOCK: " + blockLayer.size()); + + combinedLayer.add(new Set(allBlocks, 0)); + + // Add both layers to the store. + layers.add(combinedLayer); + layers.add(blockLayer); + } + + /** + * Split a set into a specified number (parts) of subsets and store these subsets in the output + * layer. + * + * @param set + * the input to split. + * @param subsets + * the number of subsets to create. + * @param output + * the output layer in which to store the subsets. + * @throws Exception + * if the split failed. + */ + private void split(Set set, int subsets, Layer output) throws Exception { + + System.out.println("Splitting set of " + set.size() + " into " + subsets + " subsets"); + + // Creating a subset of size 1 is easy. + if (subsets == 1) { + output.add(new Set(set, 0)); + return; + } + + // If the set contains (less than) the amount of part we need, + // it is easy to split it. + final int size = set.size(); + + if (size <= subsets) { + for (int i = 0; i < size; i++) { + output.add(new Set(set.get(i), i)); + } + + return; + } + + ArrayList next = new ArrayList(); + + Split split = null; + + if (splitMethod.equalsIgnoreCase("simple")) { + split = new SimpleSplit(set, subsets); + } else if (splitMethod.equalsIgnoreCase("roughlyrect")) { + split = new RoughlyRectangularSplit(set, subsets); + } else if (splitMethod.equalsIgnoreCase("search")) { + split = new SearchSplit(set, subsets); + } + + if (split == null) { + throw new Exception("Unknown split method: " + splitMethod); + } + + split.split(next); + + output.addAll(next); + set.addSubSets(next); + + /* + + // Otherwise, we try to split parts into its prime factors. + int [] primes = PrimeFactorization.factor(subsets); + + if (logger.isDebugEnabled()) { + logger.debug("Got primes " + Arrays.toString(primes)); + } + + ArrayList current = new ArrayList(); + current.add(set); + + ArrayList next = new ArrayList(); + + for (int i=0;i tmp = current; + current = next; + next = tmp; + } + + output.addAll(current); + set.addSubSets(current); + */ + } + + /** + * Split an existing layer previousLayer into subsets subsets and store these subsets in a new layer + * newLayer. + * + * @param previousLayer + * the name of the existing layer to split. + * @param newLayer + * the name of the new layer to create. + * @param subsets + * the number of subsets to create. + * @throws Exception + * if the subsets could not be created. + */ + private void split(String previousLayer, String newLayer, int subsets) throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug("split " + previousLayer + " " + newLayer + " " + subsets); + } + + Layer current = layers.get(previousLayer); + Layer result = new Layer(newLayer); + + for (int i = 0; i < current.size(); i++) { + split(current.get(i), subsets, result); + } + + layers.add(result); + } + + /** + * Print statistics on work distribution and communication in layer to console. + * + * @param layer + * the name of the layer to print statistics for, or ALL to print statistics on all layers. + */ + public void printStatistics(String layer) throws Exception { + new Statistics(layers).printStatistics(layer, System.out); + } + + /** + * Mark all blocks in a set with a certain value. + * + * @param set + * the set containing the blocks to mark. + * @param value + * the value to mark the blocks with. + */ + private void markAll(Set set, int value) { + for (Block b : set) { + b.setMark(value); + } + } + + /** + * Assign work to cores by recursively marking the sets in the various layers. + * + * @param set + * the set containing the blocks to mark. + * @return the number of new subsets encountered. + */ + private int divideWork(Set set) { + + if (set.countSubSets() == 0) { + return 1; + } + + int count = 0; + + for (Set sub : set.getSubSets()) { + + for (Block b : sub) { + b.addToMark(count); + } + + count += divideWork(sub); + } + + return count; + } + + /** + * Assign work to cores by recursively marking the sets in the various layers. + * + * This algorithm works as follows: + * + * - Initially all blocks in the combined layer are marked with 0. - Next, we call {@link #divideWork(Set)} to assign all work + * in the subsets of the combined set. + * + * The {@link #divideWork(Set)} then recursively counts the number of subsets of the combined set, adding the number of + * subsets it has seen so far to the mark of each of the blocks in the subsets. + * + * As a result: + * + * - all blocks in a CORE set will end up with the same mark - all blocks in NODE set will have marks ranging from (X .. + * X+coresPerNode), - all blocks in a CLUSTER set will have marks ranging from (Y ... Y+(corePerNode*nodesPerCLuster)) + * + */ + private void divideWork() { + markAll(combinedLayer.get(0), 0); + + if (layers.size() == 2) { + // No additional layers have been defined, so directly assign the blocks. + + int core = 0; + + for (int y = 0; y < grid.height; y++) { + for (int x = 0; x < grid.width; x++) { + + Block b = grid.get(x, y); + + if (b.ocean) { + b.setMark(core++); + } + } + } + + } else { + divideWork(combinedLayer.get(0)); + } + } + + /** + * Distribute the available blocks over the cores, taking the topography, desired block size, number of cores per node and + * nodes per cluster into account. + * + * @return the generated distribution. + * @throws Exception + * the blocks could not be distributed. + */ + public void split() throws Exception { + + String prev = "ALL"; + + if (clusters > 1) { + split(prev, "CLUSTERS", clusters); + prev = "CLUSTERS"; + } + + if (nodes > 1) { + split(prev, "NODES", nodes); + prev = "NODES"; + } + + if (cores >= 1) { + split(prev, "CORES", cores); + } + + divideWork(); + + Layer layer = layers.get("CORES"); + + if (layer == null || layer.size() != (cores * nodes * clusters)) { + throw new Exception("INTERNAL ERROR: Failed to retrieve CORE layer with " + (cores * nodes * clusters) + " cores! (" + + (layer == null ? "NULL" : "" + layer.size()) + ")"); + } + } + + /** + * Distribute the available blocks over the cores, taking the topography, desired block size, number of cores per node and + * nodes per cluster into account. + * + * @return the generated distribution. + * @throws Exception + * the blocks could not be distributed. + */ + public Distribution getDistribution() throws Exception { + + Layer layer = layers.get("CORES"); + + if (layer == null || layer.size() != (cores * nodes * clusters)) { + throw new Exception("INTERNAL ERROR: Failed to retrieve CORE layer with " + (cores * nodes * clusters) + " cores! (" + + (layer == null ? "NULL" : "" + layer.size()) + ")"); + } + + int[] result = new int[grid.width * grid.height]; + int maxBlocksPerCore = 0; + int minBlocksPerCore = Integer.MAX_VALUE; + + for (Set s : layer) { + + int blocks = s.size(); + + if (blocks > maxBlocksPerCore) { + maxBlocksPerCore = blocks; + } + + if (blocks < minBlocksPerCore) { + minBlocksPerCore = blocks; + } + + for (Block b : s) { + if (b.blockID > 0) { + Coordinate c = b.coordinate; + result[c.y * grid.width + c.x] = b.getMark() + 1; + } + } + } + + return new Distribution(topographyWidth, topographyHeight, grid.blockWidth, grid.blockHeight, clusters, nodes, cores, + minBlocksPerCore, maxBlocksPerCore, grid.width * grid.height, result); + } } \ No newline at end of file diff --git a/src/nl/esciencecenter/esalsa/loadbalancer/RoughlyRectangularSplit.java b/src/nl/esciencecenter/esalsa/loadbalancer/RoughlyRectangularSplit.java index bf2aa3c..e9cc104 100644 --- a/src/nl/esciencecenter/esalsa/loadbalancer/RoughlyRectangularSplit.java +++ b/src/nl/esciencecenter/esalsa/loadbalancer/RoughlyRectangularSplit.java @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory; /** - * A RoughlyRectangularSplit is capable of splitting a set of blocks into a specified number of subsets that are arranged in a + * A RoughlyRectangularSplit is capable of splitting a set of blocks into a specified number of subsets that are arranged in a * roughly rectangular grid of sets. See {@link #split(Collection)} for details. * * @author Jason Maassen @@ -36,400 +36,438 @@ * */ public class RoughlyRectangularSplit extends Split { - - /** A logger used for debugging. */ - private static final Logger logger = LoggerFactory.getLogger(RoughlyRectangularSplit.class); - - /** The amount of blocks per subset. */ - protected final int workPerPart; - - /** The amount of blocks left over. */ - protected final int workLeft; - - /** - * Create a new RoughlyRectangularSplit for a given set and number of subsets. - * - * @param set the set to split - * @param subsets the number of subsets to create. - */ - public RoughlyRectangularSplit(Set set, int subsets) { - - super(set, subsets); - - if (set.size() < subsets) { - throw new IllegalArgumentException("Cannot split set with " + set.size() + " work into " + subsets + " parts!"); - } - - workPerPart = set.size() / subsets; - workLeft = set.size() % subsets; - } - - /** - * Splits a given amount of work into subsets parts. - * - * @param work the amount work to split. - * @param subsets the number of subsets to split into. - * @return an array of length parts containing the size of each subset. - */ - protected int [] splitWork(int work, int subsets) { - - int [] result = new int[subsets]; - - int workPerPart = work / subsets; - int workLeft = work % subsets; - - for (int i=0;iwork into slices.length subsets, according to the relative sizes - * described in slices. - *

- * For example, slices could contain [2,2,3] to indicate that the work should be split into 3 parts with the - * relative sizes 2,2,3 (the last row has an extra column). - *

- * If work has a value of 100. If is now up to this method to determine the division of 100 work into 7 parts, - * such that the relative sizes of the slices adhere to the 2,2,3 distribution. - * - * @param work the amount of work to split - * @param slices an array describing the relative amount of work for each row. - * @param totalParts the total number of subsets to create. - * @return an array containing the amount of work for each slice. - */ - protected int [] splitWork(int work, int [] slices, int totalParts) { - - int [] result = new int[slices.length]; - - int workPerSlice = work / totalParts; - int workLeftPerSlice = work % totalParts; - - for (int i=0;i 0) { - int assign = Math.min(slices[index], workLeftPerSlice); - result[index] += assign; - workLeftPerSlice -= assign; - index++; - } - - - return result; - } - - /** - * Splits a set into targetWork.length subsets, such that subset i contains - * targetWork[i] work. - *

- * The split is performed by traversing horizontally over the source set, moving from bottom to top in a zigzag pattern. - * - * @param s the set to split. - * @param targetWork an array containing the number of block for each subset. - * @param reverse reverse the direction of the zigzag pattern. - * @return an array containing the generates subsets. - */ - protected Set [] splitHorizontal(Set s, int [] targetWork, boolean reverse) { - - final int direction = reverse ? 1 : 0; - - Set [] result = new Set[targetWork.length]; - ArrayList tmp = new ArrayList(); - - int index = 0; - - // start bottom left and zigzag horizontally - // until we reach top right. - for (int y=s.minY;y<=s.maxY;y++) { - if (y % 2 == direction) { - // left to right - for (int x=s.minX;x<=s.maxX;x++) { - - Block b = s.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result[index] = new Set(tmp, index); - tmp.clear(); - index++; - } - } - } - } else { - // right to left - for (int x=s.maxX;x>=s.minX;x--) { - - Block b = s.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result[index] = new Set(tmp, index); - tmp.clear(); - index++; - } - } - } - } - } - - return result; - } - - /** - * Splits a set into targetWork.length subsets, such that subset i contains - * targetWork[i] work. - *

- * The split is performed by traversing vertically over the source set, left to right up in a zigzag pattern. - * - * @param s the set to split. - * @param targetWork an array containing the number of block for each subset. - * @param reverse reverse the direction of the zigzag pattern. - * @return an array containing the generates subsets. - */ - protected Set [] splitVertical(Set s, int [] targetWork, boolean reverse) { - - final int direction = reverse ? 1 : 0; - - Set [] result = new Set[targetWork.length]; - ArrayList tmp = new ArrayList(); - - int index = 0; - - for (int x=s.minX;x<=s.maxX;x++) { - if (x % 2 == direction) { - // top to bottom - for (int y=s.minY;y<=s.maxY;y++) { - - Block b = s.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result[index] = new Set(tmp, index); - tmp.clear(); - index++; - } - } - } - } else { - // right to left - for (int y=s.maxY;y>=s.minY;y--) { - - Block b = s.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result[index] = new Set(tmp, index); - tmp.clear(); - index++; - } - } - } - } - } - - return result; - } - - /** - * Split the set into subSlices.length subsets, after which each subset i is split into - * subSlices[i] subsets. These subsets are that stored in the collection. - * - * @param subSlices array describing how the set should be split. - * @param result a Collection in which the resulting subsets are stored. - */ - protected void split(int [] subSlices, Collection result) { - - if (logger.isDebugEnabled()) { - logger.debug("Splitting set of size " + set.size() + " into " + Arrays.toString(subSlices)); - } - - int [] workPerSlice = splitWork(set.size(), subSlices, parts); - - if (logger.isDebugEnabled()) { - logger.debug(" Work per slice: " + Arrays.toString(workPerSlice)); - } - - if (set.getWidth() < set.getHeight()) { - - Set [] slices = splitHorizontal(set, workPerSlice, false); - - for (int i=0;iparts*subParts+leftOver elements as evenly as possible over parts slots. - * - * Note that leftOver < parts. - * - * @param parts the number of slots to divide over. - * @param subParts the minimal number of subParts in each slot. - * @param leftOver the leftOver parts that need to be divided evenly over the slots. - * @return an array of length parts containing the number of subsets assigned to each part. - */ - protected int [] createSubParts(int parts, int subParts, int leftOver) { - - int [] result = new int[parts]; - - for (int i=0;i 0) { - for (int i=0;isubset subsets and store the result in the Collection. - *

- * By taking the square root of the desired number of subsets, an initial estimate is made on what the dimension of this grid - * should be. The possible results are: - *

- *

    - *
  • if floor(root) == ceil(root) the grid is a perfect square.
  • - * - *
  • if floor(root) * ceil(root) == subsets the grid is a perfect rectangle.
  • - *
  • otherwise the grid is an imperfect rectangle, where some rows of the grid contain more columns than others
  • - *
- *

- * Based on the initial estimation, the division onto subsets is then made by splitting the set first row wise and then - * column wise (if the set is wider than it is height), or first column wise and then row wise (if the set is higher than it - * is wide). - *

- * When the initial split is made, the number of subsets that will be created in the second split is taken into account. For - * example, when a set is split into 3 rows, which will subsequently be split into 2, 2, and 3 columns, the last row will - * receive 1/7th more blocks than the first two because of the extra column. - * - * @param result the Collection to store the result in. - */ - @Override - public void split(Collection result) { - - if (logger.isDebugEnabled()) { - logger.debug("Attempting to split set of size " + set.size() + " into " + parts + " parts."); - logger.debug("Work per part: " + workPerPart); - logger.debug("Work left over: " + workLeft); - } - - // We would like to split the set into a rectangular grid. This - // may not always be possible, due to the number of parts required, - // or the shape of the set of blocks provided. By taking the root of - // the number of parts, we can come up with an approximation of the - // grid we want. - - double root = Math.sqrt(parts); - - int lowRoot = (int) Math.floor(root); - int highRoot = (int) Math.ceil(root); - - if (logger.isDebugEnabled()) { - logger.debug("Determining grid size:"); - } - - // We have a number of possible results here: - // - // If (lowRoot == highRoot) the grid is a perfect square - // If (lowRoot * highRoot == parts) the grid is a perfect rectangle - // If (parts < lowRoot * highRoot) the grid is an imperfect rectangle - // If (parts > lowRoot * highRoot) the grid is an imperfect rectangle - - if (lowRoot == highRoot) { - // The number of parts can be split into a perfect square grid. - if (logger.isDebugEnabled()) { - logger.debug("Grid is perfect square: " + lowRoot + "x" + lowRoot); - } - - split(createSubParts(lowRoot, lowRoot, 0), result); - - } else if (parts == lowRoot*highRoot) { - // The number of parts can be split into a perfect rectangular grid. - if (logger.isDebugEnabled()) { - logger.debug("Grid is perfect rectangle: " + highRoot + "x" + lowRoot); - logger.debug(" Work per slice (low): " + (set.size() / lowRoot) + " leftover " + (set.size() % lowRoot)); - logger.debug(" Work per slice (high): " + (set.size() / highRoot) + " leftover " + (set.size() % highRoot)); - } - - split(createSubParts(highRoot, lowRoot, 0), result); - // split(createSubParts(lowRoot, highRoot, 0)); - - } else if (parts < lowRoot*highRoot) { - // The number of parts cannot be split perfectly. - int below = parts - (lowRoot * lowRoot); - int above = (lowRoot * highRoot) - parts; - - if (logger.isDebugEnabled()) { - logger.debug("Grid is imperfect, lowRoot=" + lowRoot + " highRoot=" + highRoot); - logger.debug(" Solution below: " + lowRoot + "x" + lowRoot + " + " + below); - logger.debug(" Solution above: " + highRoot + "x" + lowRoot + " - " + above); - } - - //split(createSubParts(lowRoot, lowRoot, below)); - split(createSubParts(highRoot, lowRoot, -above), result); - - } else { // (parts > lowRoot*highRoot) - - int below = parts - (lowRoot * highRoot); - int above = (highRoot * highRoot) - parts; - - if (logger.isDebugEnabled()) { - logger.debug("Grid is imperfect, lowRoot=" + lowRoot + " highRoot=" + highRoot); - logger.debug(" Solution below: " + lowRoot + "x" + highRoot + " + " + below); - logger.debug(" Solution above: " + highRoot + "x" + highRoot + " - " + above); - } - - // split(createSubParts(lowRoot, highRoot, below)); - split(createSubParts(highRoot, highRoot, -above), result); - } - } + + /** A logger used for debugging. */ + private static final Logger logger = LoggerFactory.getLogger(RoughlyRectangularSplit.class); + + /** The amount of blocks per subset. */ + protected final int workPerPart; + + /** The amount of blocks left over. */ + protected final int workLeft; + + /** + * Create a new RoughlyRectangularSplit for a given set and number of subsets. + * + * @param set + * the set to split + * @param subsets + * the number of subsets to create. + */ + public RoughlyRectangularSplit(Set set, int subsets) { + + super(set, subsets); + + if (set.size() < subsets) { + throw new IllegalArgumentException("Cannot split set with " + set.size() + " work into " + subsets + " parts!"); + } + + workPerPart = set.size() / subsets; + workLeft = set.size() % subsets; + } + + /** + * Splits a given amount of work into subsets parts. + * + * @param work + * the amount work to split. + * @param subsets + * the number of subsets to split into. + * @return an array of length parts containing the size of each subset. + */ + protected int[] splitWork(int work, int subsets) { + + int[] result = new int[subsets]; + + int workPerPart = work / subsets; + int workLeft = work % subsets; + + for (int i = 0; i < subsets; i++) { + result[i] = workPerPart; + + if (i < workLeft) { + result[i]++; + } + } + + return result; + } + + /** + * Splits a given amount of work into slices.length subsets, according to the relative sizes + * described in slices. + *

+ * For example, slices could contain [2,2,3] to indicate that the work should be split into 3 parts with the + * relative sizes 2,2,3 (the last row has an extra column). + *

+ * If work has a value of 100. If is now up to this method to determine the division of 100 work into 7 parts, + * such that the relative sizes of the slices adhere to the 2,2,3 distribution. + * + * @param work + * the amount of work to split + * @param slices + * an array describing the relative amount of work for each row. + * @param totalParts + * the total number of subsets to create. + * @return an array containing the amount of work for each slice. + */ + protected int[] splitWork(int work, int[] slices, int totalParts) { + + int[] result = new int[slices.length]; + + int workPerSlice = work / totalParts; + int workLeftPerSlice = work % totalParts; + + for (int i = 0; i < slices.length; i++) { + result[i] = workPerSlice * slices[i]; + } + + int index = 0; + + while (workLeftPerSlice > 0) { + int assign = Math.min(slices[index], workLeftPerSlice); + result[index] += assign; + workLeftPerSlice -= assign; + index++; + } + + return result; + } + + /** + * Splits a set into targetWork.length subsets, such that subset i contains + * targetWork[i] work. + *

+ * The split is performed by traversing horizontally over the source set, moving from bottom to top in a zigzag pattern. + * + * @param s + * the set to split. + * @param targetWork + * an array containing the number of block for each subset. + * @param reverse + * reverse the direction of the zigzag pattern. + * @return an array containing the generates subsets. + */ + protected Set[] splitHorizontal(Set s, int[] targetWork, boolean reverse) { + + final int direction = reverse ? 0 : 1; + + Set[] result = new Set[targetWork.length]; + ArrayList tmp = new ArrayList(); + + int index = 0; + + // start bottom left and zigzag horizontally + // until we reach top right. + for (int y = s.minY; y <= s.maxY; y++) { + if (y % 2 == direction) { + // left to right + for (int x = s.minX; x <= s.maxX; x++) { + + Block b = s.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result[index] = new Set(tmp, index); + tmp.clear(); + index++; + } + } + } + } else { + // right to left + for (int x = s.maxX; x >= s.minX; x--) { + + Block b = s.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result[index] = new Set(tmp, index); + tmp.clear(); + index++; + } + } + } + } + } + + return result; + } + + /** + * Splits a set into targetWork.length subsets, such that subset i contains + * targetWork[i] work. + *

+ * The split is performed by traversing vertically over the source set, left to right up in a zigzag pattern. + * + * @param s + * the set to split. + * @param targetWork + * an array containing the number of block for each subset. + * @param reverse + * reverse the direction of the zigzag pattern. + * @return an array containing the generates subsets. + */ + protected Set[] splitVertical(Set s, int[] targetWork, boolean reverse) { + + final int direction = reverse ? 0 : 1; + + Set[] result = new Set[targetWork.length]; + ArrayList tmp = new ArrayList(); + + int index = 0; + + for (int x = s.minX; x <= s.maxX; x++) { + if (x % 2 == direction) { + // top to bottom + for (int y = s.minY; y <= s.maxY; y++) { + + Block b = s.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result[index] = new Set(tmp, index); + tmp.clear(); + index++; + } + } + } + } else { + // right to left + for (int y = s.maxY; y >= s.minY; y--) { + + Block b = s.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result[index] = new Set(tmp, index); + tmp.clear(); + index++; + } + } + } + } + } + + return result; + } + + /** + * Split the set into subSlices.length subsets, after which each subset i is split into + * subSlices[i] subsets. These subsets are that stored in the collection. + * + * @param subSlices + * array describing how the set should be split. + * @param result + * a Collection in which the resulting subsets are stored. + */ + protected void split(int[] subSlices, Collection result) { + + if (logger.isDebugEnabled()) { + logger.debug("Splitting set of size " + set.size() + " into " + Arrays.toString(subSlices)); + } + + int[] workPerSlice = splitWork(set.size(), subSlices, parts); + + if (logger.isDebugEnabled()) { + logger.debug(" Work per slice: " + Arrays.toString(workPerSlice)); + } + + if (set.getWidth() < set.getHeight()) { + + System.out.println("HOR"); + + Set[] slices = splitHorizontal(set, workPerSlice, false); + + for (int i = 0; i < slices.length; i++) { + + int[] workPerPart = splitWork(slices[i].size(), subSlices[i]); + + Set[] parts = splitVertical(slices[i], workPerPart, false); + + for (int j = 0; j < parts.length; j++) { + result.add(parts[j]); + } + } + + } else { + + System.out.println("VER"); + + Set[] slices = splitVertical(set, workPerSlice, false); + + for (int i = 0; i < slices.length; i++) { + + int[] workPerPart = splitWork(slices[i].size(), subSlices[i]); + + Set[] parts = splitHorizontal(slices[i], workPerPart, false); + + for (int j = 0; j < parts.length; j++) { + result.add(parts[j]); + } + } + } + + int max = 0; + int sum = 0; + + for (Set set : result) { + + int comm = set.getCommunication(); + + if (comm > max) { + max = comm; + } + + sum += comm; + } + + System.out.println("Result " + sum + " " + max); + } + + /** + * Divides parts*subParts+leftOver elements as evenly as possible over parts slots. + * + * Note that leftOver < parts. + * + * @param parts + * the number of slots to divide over. + * @param subParts + * the minimal number of subParts in each slot. + * @param leftOver + * the leftOver parts that need to be divided evenly over the slots. + * @return an array of length parts containing the number of subsets assigned to each part. + */ + protected int[] createSubParts(int parts, int subParts, int leftOver) { + + int[] result = new int[parts]; + + for (int i = 0; i < parts; i++) { + result[i] = subParts; + } + + if (leftOver > 0) { + for (int i = 0; i < leftOver; i++) { + result[i]++; + } + } else if (leftOver < 0) { + for (int i = parts + leftOver; i < parts; i++) { + result[i]--; + } + } + + return result; + } + + /** + * Perform the split of the set into subset subsets and store the result in the Collection. + *

+ * By taking the square root of the desired number of subsets, an initial estimate is made on what the dimension of this grid + * should be. The possible results are: + *

+ *

    + *
  • if floor(root) == ceil(root) the grid is a perfect square.
  • + * + *
  • if floor(root) * ceil(root) == subsets the grid is a perfect rectangle.
  • + *
  • otherwise the grid is an imperfect rectangle, where some rows of the grid contain more columns than others
  • + *
+ *

+ * Based on the initial estimation, the division onto subsets is then made by splitting the set first row wise and then column + * wise (if the set is wider than it is height), or first column wise and then row wise (if the set is higher than it is + * wide). + *

+ * When the initial split is made, the number of subsets that will be created in the second split is taken into account. For + * example, when a set is split into 3 rows, which will subsequently be split into 2, 2, and 3 columns, the last row will + * receive 1/7th more blocks than the first two because of the extra column. + * + * @param result + * the Collection to store the result in. + */ + @Override + public void split(Collection result) { + + if (logger.isDebugEnabled()) { + logger.debug("Attempting to split set of size " + set.size() + " into " + parts + " parts."); + logger.debug("Work per part: " + workPerPart); + logger.debug("Work left over: " + workLeft); + } + + // We would like to split the set into a rectangular grid. This + // may not always be possible, due to the number of parts required, + // or the shape of the set of blocks provided. By taking the root of + // the number of parts, we can come up with an approximation of the + // grid we want. + + double root = Math.sqrt(parts); + + int lowRoot = (int) Math.floor(root); + int highRoot = (int) Math.ceil(root); + + if (logger.isDebugEnabled()) { + logger.debug("Determining grid size:"); + } + + // We have a number of possible results here: + // + // If (lowRoot == highRoot) the grid is a perfect square + // If (lowRoot * highRoot == parts) the grid is a perfect rectangle + // If (parts < lowRoot * highRoot) the grid is an imperfect rectangle + // If (parts > lowRoot * highRoot) the grid is an imperfect rectangle + + if (lowRoot == highRoot) { + // The number of parts can be split into a perfect square grid. + if (logger.isDebugEnabled()) { + logger.debug("Grid is perfect square: " + lowRoot + "x" + lowRoot); + } + + split(createSubParts(lowRoot, lowRoot, 0), result); + + } else if (parts == lowRoot * highRoot) { + // The number of parts can be split into a perfect rectangular grid. + if (logger.isDebugEnabled()) { + logger.debug("Grid is perfect rectangle: " + highRoot + "x" + lowRoot); + logger.debug(" Work per slice (low): " + (set.size() / lowRoot) + " leftover " + (set.size() % lowRoot)); + logger.debug(" Work per slice (high): " + (set.size() / highRoot) + " leftover " + (set.size() % highRoot)); + } + + split(createSubParts(highRoot, lowRoot, 0), result); + // split(createSubParts(lowRoot, highRoot, 0)); + + } else if (parts < lowRoot * highRoot) { + // The number of parts cannot be split perfectly. + int below = parts - (lowRoot * lowRoot); + int above = (lowRoot * highRoot) - parts; + + if (logger.isDebugEnabled()) { + logger.debug("Grid is imperfect, lowRoot=" + lowRoot + " highRoot=" + highRoot); + logger.debug(" Solution below: " + lowRoot + "x" + lowRoot + " + " + below); + logger.debug(" Solution above: " + highRoot + "x" + lowRoot + " - " + above); + } + + //split(createSubParts(lowRoot, lowRoot, below)); + split(createSubParts(highRoot, lowRoot, -above), result); + + } else { // (parts > lowRoot*highRoot) + + int below = parts - (lowRoot * highRoot); + int above = (highRoot * highRoot) - parts; + + if (logger.isDebugEnabled()) { + logger.debug("Grid is imperfect, lowRoot=" + lowRoot + " highRoot=" + highRoot); + logger.debug(" Solution below: " + lowRoot + "x" + highRoot + " + " + below); + logger.debug(" Solution above: " + highRoot + "x" + highRoot + " - " + above); + } + + // split(createSubParts(lowRoot, highRoot, below)); + split(createSubParts(highRoot, highRoot, -above), result); + } + } } diff --git a/src/nl/esciencecenter/esalsa/loadbalancer/SearchSplit.java b/src/nl/esciencecenter/esalsa/loadbalancer/SearchSplit.java index 69d7659..fe7e6aa 100644 --- a/src/nl/esciencecenter/esalsa/loadbalancer/SearchSplit.java +++ b/src/nl/esciencecenter/esalsa/loadbalancer/SearchSplit.java @@ -19,16 +19,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; -import nl.esciencecenter.esalsa.util.Neighbours; import nl.esciencecenter.esalsa.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A SearchSplit is an extension of a RoughlyRectangularSplit that generates all valid roughly rectangular grid of sets and tests - * each of them to see which solution offers the minimal amount of communication between nodes. + * A SearchSplit is an extension of a RoughlyRectangularSplit that generates all valid roughly rectangular grid of sets and tests + * each of them to see which solution offers the minimal amount of communication between nodes. * * @author Jason Maassen * @version 1.0 @@ -36,237 +36,413 @@ * */ public class SearchSplit extends RoughlyRectangularSplit { - - /** A logger used for debugging. */ - private static final Logger logger = LoggerFactory.getLogger(SearchSplit.class); - - /** The neighbour function to use */ - protected final Neighbours neighbours; - - private class Solution { - final Set [] solution; - final int [] permutation; - - Solution(Set [] solution, int [] permutation) { - this.solution = solution; - this.permutation = permutation; - } - } - - /** - * Create a new SearchSplit for a given set, number of subsets and neigbour function. - * - * @param set the set to split - * @param subsets the number of subsets to create. - */ - public SearchSplit(Set set, int subsets, Neighbours neigbours) { - - super(set, subsets); - - this.neighbours = neigbours; - - if (set.size() < subsets) { - throw new IllegalArgumentException("Cannot split set with " + set.size() + " work into " + subsets + " parts!"); - } - } - - private int getCommunication(Set [] sets) { - - int communication = 0; - - for (int i=0;i, <, ^, v - Set [][] solutions = new Set[4][]; - - solutions[0] = splitHorizontal(set, workPerSlice, false); - solutions[1] = splitHorizontal(set, workPerSlice, true); - solutions[2] = splitVertical(set, workPerSlice, false); - solutions[3] = splitVertical(set, workPerSlice, true); - - // Now select the best of the four - Set [] best = solutions[0]; - int bestCommunication = getCommunication(solutions[0]); - - if (logger.isDebugEnabled()) { - logger.debug(" solution[0] " + bestCommunication); - } - - for (int i=1;i<4;i++) { - - int tmp = getCommunication(solutions[i]); - - if (logger.isDebugEnabled()) { - logger.debug(" solution[" + i + "] " + tmp); - } - - if (tmp < bestCommunication) { - best = solutions[i]; - bestCommunication = tmp; - } - } - - if (logger.isDebugEnabled()) { - logger.debug(" best solution -- " + bestCommunication); - } - - return best; - } - - @SuppressWarnings("rawtypes") - private Solution findBestSplit(Set set, int [] workPerSlice) { - - Set [] best = null; - int [] bestPerm = null; - int bestCommunication = Integer.MAX_VALUE; - - // We should test all permutations of workPerSlice here. - ArrayList permutations = new ArrayList(); - - getIndexPermutations(workPerSlice.length, permutations); - // getPermutations(workPerSlice, permutations); - - for (int i=0;isubSlices.length subsets, after which each subset i is split into - * subSlices[i] subsets. These subsets are that stored in the collection. - * - * @param subSlices array describing how the set should be split. - * @param result a Collection in which the resulting subsets are stored. - */ - protected void split(int [] subSlices, Collection result) { - - if (logger.isDebugEnabled()) { - logger.debug("Splitting set of size " + set.size() + " into " + Arrays.toString(subSlices)); - } - - int [] workPerSlice = splitWork(set.size(), subSlices, parts); - - if (logger.isDebugEnabled()) { - logger.debug(" Work per slice: " + Arrays.toString(workPerSlice)); - } - - Solution solution = findBestSplit(set, workPerSlice); - - for (int i=0;i max) { + max = tmp; + } + } + + return max; + } + +/* + private Set[] findBestSplitFourWays(Set set, int[] workPerSlice) { + + // We now have 4 options here: >, <, ^, v + Set[][] solutions = new Set[4][]; + + solutions[0] = splitHorizontal(set, workPerSlice, false); + solutions[1] = splitHorizontal(set, workPerSlice, true); + solutions[2] = splitVertical(set, workPerSlice, false); + solutions[3] = splitVertical(set, workPerSlice, true); + + // Now select the best of the four + Set[] best = solutions[0]; + int bestCommunication = getCommunicationSum(solutions[0]); + int bestMaxCommunication = getCommunicationMax(solutions[0]); + + if (logger.isDebugEnabled()) { + logger.debug(" solution[0] " + bestCommunication); + } + + System.out.println("Set best to " + 0); + System.out.println(" solution[0] " + bestCommunication + " " + bestMaxCommunication); + + for (int i = 1; i < 4; i++) { + + int tmpSum = getCommunicationSum(solutions[i]); + int tmpMax = getCommunicationMax(solutions[i]); + + if (logger.isDebugEnabled()) { + logger.debug(" solution[" + i + "] " + tmpSum); + } + + System.out.println(" solution[" + i + "] " + tmpSum + " " + tmpMax); + + if (tmpSum < bestCommunication) { + best = solutions[i]; + bestCommunication = tmpSum; + bestMaxCommunication = tmpMax; + + System.out.println("Set best to " + i); + + } else if (tmpSum == bestCommunication && tmpMax < bestMaxCommunication) { + best = solutions[i]; + bestCommunication = tmpSum; + bestMaxCommunication = tmpMax; + + System.out.println("Set best to " + i); + + } + } + + if (logger.isDebugEnabled()) { + logger.debug(" best solution -- " + bestCommunication); + } + + System.out.println(" best solution -- " + bestCommunication + " " + bestMaxCommunication); + + return best; + } +*/ + + @SuppressWarnings("rawtypes") + private void findBestSplit(Set set, int[] workPerSlice, Collection solutions) { + + // We should test all permutations of workPerSlice here. + ArrayList permutations = new ArrayList(); + getIndexPermutations(workPerSlice.length, permutations); + + System.out.println("Permutations generated: " + permutations.size()); + + HashMap> cache = new HashMap>(); + + for (int i = 0; i < permutations.size(); i++) { + + int[] perm = (int[]) permutations.get(i); + int[] work = new int[workPerSlice.length]; + + for (int j = 0; j < workPerSlice.length; j++) { + work[j] = workPerSlice[perm[j]]; + } + + int hashcode = Arrays.hashCode(work); + + ArrayList array = cache.get(hashcode); + + boolean seen = false; + + if (array == null) { + array = new ArrayList(); + array.add(work); + cache.put(hashcode, array); + + } else { + + for (int [] a : array) { + if (Arrays.equals(work, a)) { + seen = true; + break; + } + } + + if (!seen) { + array.add(work); + } + } + + if (!seen) { + + if (logger.isDebugEnabled()) { + logger.debug(" TESTING: " + Arrays.toString(perm) + " " + Arrays.toString(work)); + } + + System.out.println("TESTING: " + Arrays.toString(perm) + " " + Arrays.toString(work)); + + Set [] tmp = splitHorizontal(set, work, false); + int communication = getCommunicationSum(tmp); + solutions.add(new Solution(tmp, perm, communication)); + + tmp = splitHorizontal(set, work, true); + communication = getCommunicationSum(tmp); + solutions.add(new Solution(tmp, perm, communication)); + + tmp = splitVertical(set, work, false); + communication = getCommunicationSum(tmp); + solutions.add(new Solution(tmp, perm, communication)); + + tmp = splitVertical(set, work, true); + communication = getCommunicationSum(tmp); + solutions.add(new Solution(tmp, perm, communication)); + } + } + } + + private int[] swap(int[] input, int i, int j) { + int[] copy = input.clone(); + copy[i] = input[j]; + copy[j] = input[i]; + return copy; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void getPermutation(int[] input, int start, Collection output) { + + if (start == input.length) { + output.add(input.clone()); + return; + } + + getPermutation(input, start + 1, output); + + for (int i = start + 1; i < input.length; i++) { + if (input[start] != input[i]) { + input = swap(input, start, i); + getPermutation(input, start + 1, output); + } + } + } + + @SuppressWarnings("rawtypes") + private void getIndexPermutations(int len, Collection output) { + + int[] index = new int[len]; + + for (int i = 0; i < index.length; i++) { + index[i] = i; + } + + getPermutation(index, 0, output); + } + + /** + * Split the set into subSlices.length subsets, after which each subset i is split into + * subSlices[i] subsets. These subsets are that stored in the collection. + * + * @param subSlices + * array describing how the set should be split. + * @param result + * a Collection in which the resulting subsets are stored. + */ + protected void split(int[] subSlices, Collection result) { + + if (logger.isDebugEnabled()) { + logger.debug("Splitting set of size " + set.size() + " into " + Arrays.toString(subSlices)); + } + + int[] workPerSlice = splitWork(set.size(), subSlices, parts); + + if (logger.isDebugEnabled()) { + logger.debug(" Work per slice: " + Arrays.toString(workPerSlice)); + } + + ArrayList solutions = new ArrayList(); + + findBestSplit(set, workPerSlice, solutions); + + Solution best = null; + int bestCommunication = Integer.MAX_VALUE; + int maxCommunication = Integer.MAX_VALUE; + + for (Solution s : solutions) { + + System.out.println("\n\nTESTING SOLUTION " + Arrays.toString(s.permutation) + " " + s.communication); + + ArrayList solutions2 = new ArrayList(); + + for (int i = 0; i < s.solution.length; i++) { + + ArrayList solutions3 = new ArrayList(); + + if (logger.isDebugEnabled()) { + logger.debug("Splitting SUB " + i + " ---------------------"); + } + + int[] workPerPart = splitWork(s.solution[i].size(), subSlices[s.permutation[i]]); + + System.out.println("Splitting SUB " + i + " " + Arrays.toString(workPerPart)); + + findBestSplit(s.solution[i], workPerPart, solutions3); + + int bestComm = Integer.MAX_VALUE; + int bestMax = Integer.MAX_VALUE; + Solution tmp = null; + + System.out.println("Splitting SUB gave " + solutions3.size() + " results"); + + for (Solution s3 : solutions3) { + + int sum = 0; + int max = 0; + + for (Set set : s3.solution) { + + int comm = set.getCommunication(); + + if (comm > max) { + max = comm; + } + + sum += comm; + } + + if (sum < bestComm) { + bestComm = sum; + bestMax = max; + tmp = s3; + } else if (sum == bestComm) { + if (max < bestMax) { + bestComm = sum; + bestMax = max; + tmp = s3; + } + } + +// if (s3.communication < bestComm) { +// bestComm = s3.communication; +// tmp = s3; +// } + } + + System.out.println(" BEST SUB " + bestComm + " " + bestMax + " " + tmp.communication); + + solutions2.add(tmp); + +// for (int j = 0; j < tmp.solution.length; j++) { +// result.add(tmp.solution[j]); +// } + } + + int min = Integer.MAX_VALUE; + int max = 0; + int sum = 0; + int count = 0; + + for (Solution sub : solutions2) { + + for (Set set : sub.solution) { + + int comm = set.getCommunication(); + + if (comm > max) { + max = comm; + } + + if (comm < min) { + min = comm; + } + + sum += comm; + count++; + } + } + + System.out.println("RESULT OF " + Arrays.toString(s.permutation) + " " + " " + + s.communication + " " + max + " " + sum + + " " + min + " " + count + " " + bestCommunication); + + if (sum < bestCommunication) { + bestCommunication = sum; + maxCommunication = max; + best = s; + } else if (sum == bestCommunication) { + if (max < maxCommunication) { + bestCommunication = sum; + maxCommunication = max; + best = s; + } + } + } + + System.out.println("\n\n****"); + System.out.println("BEST SOLUTION: " + Arrays.toString(best.permutation) + " " + best.communication + " -> " + + bestCommunication); + + // RECOMPUTE BEST RESULT! + + ArrayList solutions2 = new ArrayList(); + + for (int i = 0; i < best.solution.length; i++) { + + ArrayList solutions3 = new ArrayList(); + + if (logger.isDebugEnabled()) { + logger.debug("Splitting SUB " + i + " ---------------------"); + } + + int[] workPerPart = splitWork(best.solution[i].size(), subSlices[best.permutation[i]]); + + findBestSplit(best.solution[i], workPerPart, solutions3); + + int bestComm = Integer.MAX_VALUE; + Solution tmp = null; + + for (Solution s3 : solutions3) { + if (s3.communication < bestComm) { + bestComm = s3.communication; + tmp = s3; + } + } + + solutions2.add(tmp); + } + + for (Solution tmp : solutions2) { + for (int j = 0; j < tmp.solution.length; j++) { + result.add(tmp.solution[j]); + } + } + } } diff --git a/src/nl/esciencecenter/esalsa/loadbalancer/SimpleSplit.java b/src/nl/esciencecenter/esalsa/loadbalancer/SimpleSplit.java index 47eb67c..1c3acbd 100644 --- a/src/nl/esciencecenter/esalsa/loadbalancer/SimpleSplit.java +++ b/src/nl/esciencecenter/esalsa/loadbalancer/SimpleSplit.java @@ -23,8 +23,8 @@ import nl.esciencecenter.esalsa.util.Set; /** - * A SimpleSplit is capable of splitting a set of blocks into a specified number of subsets that are arranged in a roughly - * linear fashion. See {@link #split(Collection)} for details. + * A SimpleSplit is capable of splitting a set of blocks into a specified number of subsets that are arranged in a roughly linear + * fashion. See {@link #split(Collection)} for details. * * @author Jason Maassen * @version 1.0 @@ -33,158 +33,165 @@ */ public class SimpleSplit extends Split { - /** The sizes of each of the subsets. */ - private final int [] targetWork; - - /** - * Create a SimpleSplit that will split the set into subsets parts. - * - * @param set the set to split. - * @param subsets the number of subsets to create. - */ - public SimpleSplit(Set set, int subsets) { - - super(set, subsets); - - targetWork = new int[subsets]; - - if (set.size() < subsets) { - throw new IllegalArgumentException("Cannot split set with " + set.size() + " work into " + subsets + " parts!"); - } - - int div = set.size() / subsets; - int mod = set.size() % subsets; - - for (int i=0;itargetWork.length subsets, such that subset i contains - * targetWork[i] work. - *

- * The split is performed by traversing horizontally over the source set, moving from bottom to top in a zigzag pattern. - * - * @param s the set to split. - * @param targetWork an array containing the number of block for each subset. - */ - private void zigzagHorizontal(Collection result) { - - int index = 0; - - ArrayList tmp = new ArrayList(); - - // start bottom left and zigzag horizontally - // until we reach top right. - for (int y=set.minY;y<=set.maxY;y++) { - if (y % 2 == 0) { - // left to right - for (int x=set.minX;x<=set.maxX;x++) { - - Block b = set.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result.add(new Set(tmp, index)); - tmp.clear(); - index++; - } - } - } - } else { - // right to left - for (int x=set.maxX;x>=set.minX;x--) { - - Block b = set.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result.add(new Set(tmp, index)); - tmp.clear(); - index++; - } - } - } - } - } - } - - /** - * Splits the set into targetWork.length subsets, such that subset i contains - * targetWork[i] work. - *

- * The split is performed by traversing vertically over the source set, left to right up in a zigzag pattern. - * - * @param s the set to split. - * @param targetWork an array containing the number of block for each subset. - */ - private void zigzagVertical(Collection result) { - - int index = 0; - - ArrayList tmp = new ArrayList(); - - for (int x=set.minX;x<=set.maxX;x++) { - if (x % 2 == 0) { - // top to bottom - for (int y=set.minY;y<=set.maxY;y++) { - - Block b = set.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result.add(new Set(tmp, index)); - tmp.clear(); - index++; - } - } - } - } else { - // right to left - for (int y=set.maxY;y>=set.minY;y--) { - - Block b = set.get(x, y); - - if (b != null) { - tmp.add(b); - - if (tmp.size() == targetWork[index]) { - result.add(new Set(tmp, index)); - tmp.clear(); - index++; - } - } - } - } - } - } - - /** - * Perform the split of the set into subsets and store these in the provided Collection. - *

- * Set is split into subsets in a linear fashion (resulting in a 1xN or Nx1 division). If the set is wider that it is high, - * it will be split horizontally (Nx1), if it is higher than it is wide, it will be split vertically (1xN). - * - * @param result the Collection to store the result in. - */ - @Override - public void split(Collection result) { - - if (set.getWidth() < set.getHeight()) { - zigzagHorizontal(result); - } else { - zigzagVertical(result); - } - } + /** The sizes of each of the subsets. */ + private final int[] targetWork; + + /** + * Create a SimpleSplit that will split the set into subsets parts. + * + * @param set + * the set to split. + * @param subsets + * the number of subsets to create. + */ + public SimpleSplit(Set set, int subsets) { + + super(set, subsets); + + targetWork = new int[subsets]; + + if (set.size() < subsets) { + throw new IllegalArgumentException("Cannot split set with " + set.size() + " work into " + subsets + " parts!"); + } + + int div = set.size() / subsets; + int mod = set.size() % subsets; + + for (int i = 0; i < subsets; i++) { + targetWork[i] = div; + + if (i < mod) { + targetWork[i]++; + } + } + } + + /** + * Splits a set into targetWork.length subsets, such that subset i contains + * targetWork[i] work. + *

+ * The split is performed by traversing horizontally over the source set, moving from bottom to top in a zigzag pattern. + * + * @param s + * the set to split. + * @param targetWork + * an array containing the number of block for each subset. + */ + private void zigzagHorizontal(Collection result) { + + int index = 0; + + ArrayList tmp = new ArrayList(); + + // start bottom left and zigzag horizontally + // until we reach top right. + for (int y = set.minY; y <= set.maxY; y++) { + if (y % 2 == 0) { + // left to right + for (int x = set.minX; x <= set.maxX; x++) { + + Block b = set.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result.add(new Set(tmp, index)); + tmp.clear(); + index++; + } + } + } + } else { + // right to left + for (int x = set.maxX; x >= set.minX; x--) { + + Block b = set.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result.add(new Set(tmp, index)); + tmp.clear(); + index++; + } + } + } + } + } + } + + /** + * Splits the set into targetWork.length subsets, such that subset i contains + * targetWork[i] work. + *

+ * The split is performed by traversing vertically over the source set, left to right up in a zigzag pattern. + * + * @param s + * the set to split. + * @param targetWork + * an array containing the number of block for each subset. + */ + private void zigzagVertical(Collection result) { + + int index = 0; + + ArrayList tmp = new ArrayList(); + + for (int x = set.minX; x <= set.maxX; x++) { + if (x % 2 == 0) { + // top to bottom + for (int y = set.minY; y <= set.maxY; y++) { + + Block b = set.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result.add(new Set(tmp, index)); + tmp.clear(); + index++; + } + } + } + } else { + // right to left + for (int y = set.maxY; y >= set.minY; y--) { + + Block b = set.get(x, y); + + if (b != null) { + tmp.add(b); + + if (tmp.size() == targetWork[index]) { + result.add(new Set(tmp, index)); + tmp.clear(); + index++; + } + } + } + } + } + } + + /** + * Perform the split of the set into subsets and store these in the provided Collection. + *

+ * Set is split into subsets in a linear fashion (resulting in a 1xN or Nx1 division). If the set is wider that it is high, it + * will be split horizontally (Nx1), if it is higher than it is wide, it will be split vertically (1xN). + * + * @param result + * the Collection to store the result in. + */ + @Override + public void split(Collection result) { + + if (set.getWidth() < set.getHeight()) { + zigzagHorizontal(result); + } else { + zigzagVertical(result); + } + } } \ No newline at end of file diff --git a/src/nl/esciencecenter/esalsa/loadbalancer/Split.java b/src/nl/esciencecenter/esalsa/loadbalancer/Split.java index ad7f817..2849833 100644 --- a/src/nl/esciencecenter/esalsa/loadbalancer/Split.java +++ b/src/nl/esciencecenter/esalsa/loadbalancer/Split.java @@ -20,38 +20,40 @@ import nl.esciencecenter.esalsa.util.Set; /** - * A Split is an abstract parent class of all splitters. + * A Split is an abstract parent class of all splitters. * - * A splitter is capable of splitting a set of blocks into a specified number of subsets. - * See {@link #split(Collection)} for details. + * A splitter is capable of splitting a set of blocks into a specified number of subsets. See {@link #split(Collection)} for + * details. * * @author Jason Maassen * @version 1.0 * @since 1.0 */ public abstract class Split { - - /** The set to split */ - protected final Set set; - - /** The number of subsets to create. */ - protected final int parts; - - /** - * Create a Split for the given set. - * - * @param set the set to split. - * @param subsets the number of subsets to split the set into. - */ - protected Split(Set set, int subsets) { - this.set = set; - this.parts = subsets; - } - - /** - * Perform the split of the set into subsets and store these in the provided Collection. - * - * @param result - */ - public abstract void split(Collection result); + + /** The set to split */ + protected final Set set; + + /** The number of subsets to create. */ + protected final int parts; + + /** + * Create a Split for the given set. + * + * @param set + * the set to split. + * @param subsets + * the number of subsets to split the set into. + */ + protected Split(Set set, int subsets) { + this.set = set; + this.parts = subsets; + } + + /** + * Perform the split of the set into subsets and store these in the provided Collection. + * + * @param result + */ + public abstract void split(Collection result); } diff --git a/src/nl/esciencecenter/esalsa/tools/CreateTestTopo.java b/src/nl/esciencecenter/esalsa/tools/CreateTestTopo.java new file mode 100644 index 0000000..df569ae --- /dev/null +++ b/src/nl/esciencecenter/esalsa/tools/CreateTestTopo.java @@ -0,0 +1,66 @@ +package nl.esciencecenter.esalsa.tools; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class CreateTestTopo { + + private static int BLOCK_SIZE_X = 100; + private static int BLOCK_SIZE_Y = 100; + + public static void main(String [] args) throws IOException { + + int [][] topo = new int[10][12]; + + for (int y=0;y<10;y++) { + for (int x=0;x<12;x++) { + topo[y][x] = 1; + } + } + + topo[0][3] = 0; + topo[0][4] = 0; + topo[0][10] = 0; + topo[0][11] = 0; + + topo[1][10] = 0; + topo[1][11] = 0; + + topo[2][11] = 0; + + topo[7][0] = 0; + topo[7][9] = 0; + topo[7][10] = 0; + + topo[8][0] = 0; + topo[8][1] = 0; + topo[8][10] = 0; + + + topo[9][0] = 0; + topo[9][1] = 0; + topo[9][2] = 0; + topo[9][3] = 0; + topo[9][4] = 0; + topo[9][10] = 0; + topo[9][11] = 0; + + DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("test.topo"))); + + for (int y=9;y>=0;y--) { + + for (int by=0;by * @version 1.0 * @since 1.0 */ public class DistributionToText { - /** - * Main entry point into application. - * - * @param args the command line arguments provided by the user. - */ - public static void main(String [] args) { - - if (args.length != 1) { - System.err.println("Usage: DistributionToText [distribution file]"); - System.exit(1); - } - - Distribution d = null; - - try { - d = new Distribution(args[0]); - } catch (Exception e) { - Utils.fatal("Failed to open ditribution " + args[0] + "\n", e); - } + /** + * Main entry point into application. + * + * @param args + * the command line arguments provided by the user. + */ + public static void main(String[] args) { - System.out.println(d.topographyWidth); - System.out.println(d.topographyHeight); + if (args.length != 1) { + System.err.println("Usage: DistributionToText [distribution file]"); + System.exit(1); + } - System.out.println(d.blockWidth); - System.out.println(d.blockHeight); + Distribution d = null; - System.out.println(d.clusters); - System.out.println(d.nodesPerCluster); - System.out.println(d.coresPerNode); + try { + d = new Distribution(args[0]); + } catch (Exception e) { + Utils.fatal("Failed to open ditribution " + args[0] + "\n", e); + } - System.out.println(d.minBlocksPerCore); - System.out.println(d.maxBlocksPerCore); + System.out.println(d.topographyWidth); + System.out.println(d.topographyHeight); - System.out.println(d.totalBlocks); + System.out.println(d.blockWidth); + System.out.println(d.blockHeight); - for (int i=0;iset with the provided colors for ocean and land. - * - * @param layer the layer at which the edge must be drawn. - * @param s the set to draw the edge for. - * @param ocean the color to use for ocean neighbors. - * @param land the color to use for land neighbors. - * @throws Exception if the edge could not be drawn. - */ - private void colorEdge(String layer, Set s, Color ocean, Color land) throws Exception { - - if (s == null) { - return; - } - - Coordinate [] tmp = s.getNeighbours(neighbours); - - for (Coordinate c : tmp) { - if (grid.get(c.x, c.y) != null) { - view.fillBlock(layer, c.x, c.y, ocean); - } else { - view.fillBlock(layer, c.x, c.y, land); - } - } - } - - /** - * Callback function for the MouseListener attached to the TopographyView. - * - * @param p the Point that has been clicked. - * @throws Exception if the mouse click could not be processed. - */ - private void clicked(Point p) throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug("Click at " + p.x + " " + p.y); - } - - double w = view.getWidth(); - double h = view.getHeight(); - - int posX = (int) ((p.x / w) * topography.width); - int posY = topography.height - (int) ((p.y / h) * topography.height); - - int bx = posX / grid.blockWidth; - int by = posY / grid.blockHeight; - - int commBlock = -1; - int commCore = -1; - int commNode = -1; - int commCluster = -1; - - int block = -1; - int core = -1; - int node = -1; - int cluster = -1; - -/* - Coordinate c = new Coordinate(bx, by); - - int [][] comm = neighbours.getCommunication(c); - - System.out.println("Communication on BLOCK layer: "); - - for (int i=0;i<3;i++) { - for (int j=0;j<3;j++) { - System.out.print(" " + comm[i][j]); - } - System.out.println(); - } -*/ - - view.clearLayer("FILL"); - - if (layers.contains("BLOCKS")) { - - Layer l = layers.get("BLOCKS"); - Set s = l.locate(bx, by); - - if (s != null) { - block = s.index; - commBlock = s.getCommunication(neighbours); - colorEdge("FILL", s, HALO_COLOR_BLOCK, LAND_COLOR_BLOCK); - } - } - - if (layers.contains("CORES")) { - Layer l = layers.get("CORES"); - Set s = l.locate(bx, by); - - if (s != null) { - core = s.index; - commCore = s.getCommunication(neighbours); - colorEdge("FILL", s, HALO_COLOR_CORE, LAND_COLOR_CORE); - } - } - - if (layers.contains("NODES")) { - Layer l = layers.get("NODES"); - Set s = l.locate(bx, by); - - if (s != null) { - node = s.index; - commNode = s.getCommunication(neighbours); - colorEdge("FILL", s, HALO_COLOR_NODE, LAND_COLOR_NODE); - } - } - - if (layers.contains("CLUSTERS")) { - Layer l = layers.get("CLUSTERS"); - Set s = l.locate(bx, by); - - if (s != null) { - cluster = s.index; - commCluster = s.getCommunication(neighbours); - colorEdge("FILL", s, HALO_COLOR_CLUSTER, LAND_COLOR_CLUSTER); - } - } - - System.out.print("Selected"); - - if (cluster >= 0) { - System.out.print(" cluster " + cluster); - } - - if (node >= 0) { - System.out.print(" node " + node); - } - - System.out.println(" core " + core + " block " + block + " (" + bx + "x" + by + ")"); - - System.out.println("Communication on layer BLOCK " + commBlock); - System.out.println("Communication on layer CORE " + commCore); - System.out.println("Communication on layer NODE " + commNode); - System.out.println("Communication on layer CLUSTER " + commCluster); - - /* - Coordinate [][] tmp = neighbours.getNeighbours(c, true); - - for (int i=0;i<3;i++) { - for (int j=0;j<3;j++) { - if (tmp[i][j] != null) { - - int nx = tmp[i][j].x; - int ny = tmp[i][j].y; - - //System.out.println("Fill neighbour " + nx + "x" + ny); - - if (grid.get(nx, ny) != null) { - view.fillBlock("FILL", nx, ny, HALO_COLOR_BLOCK); - } else { - view.fillBlock("FILL", nx, ny, LAND_COLOR_BLOCK); - } - } - } - } -*/ - - view.fillBlock("FILL", bx, by, CENTER); - view.repaint(); - } - - /** - * Creates a HashMap containing all edges (Lines) of all Blocks in the given set. - *

- * Each edge in the HashMap maps to an Integer value which indicates how often the edge was encountered in the set. The edges - * that have a count of 1 together form the outer edge of the set. - * - * @param s the set for which to collect the edges. - * @param out the HashMap to which the result will be added. - */ - private void collectLines(Set s, HashMap out) { - - for (int i=0;i1. Else the value it maps to is - * incremented by one. - * - * @param map the HashMap to add the line to. - * @param line the Line to add. - */ - private void addLine(HashMap map, Line line) { - - if (!map.containsKey(line)) { - map.put(line, 1); - return; - } - - int count = map.get(line); - map.put(line, count+1); - } - - /** - * Draw all lines in the HashMap which map to the value 1 in the specified layer. - * - * @param layer the layer to draw to. - * @param map the collection of lines. - * @param color the color to draw the lines in. - * @param lineWidth the width of the lines to draw. - * @throws Exception if the lines could not be drawn. - */ - private void drawLines(String layer, HashMap map, Color color, float lineWidth) throws Exception { - - if (view == null) { - return; - } - - // We draw each line that has only been added to the map once. - for (Line l : map.keySet()) { - - int count = map.get(l); - - if (count == 1) { - view.draw(layer, l, color, lineWidth); - } - } - } - - /** - * Draw the edges of all sets in the specified layer. - * - * @param layer the layer to draw. - * @param color the color to draw the lines in. - * @param lineWidth the width of the lines to draw. - * @throws Exception if the lines could not be drawn. - */ - private void drawLayer(Layer l, Color color, float lineWidth) throws Exception { - - if (view == null || l == null) { - return; - } - - for (int i=0;i map = new HashMap(); - - collectLines(s, map); - - drawLines(l.name, map, color, lineWidth); - } - - view.repaint(); - } - - /** - * Draw the outline of all ocean blocks in the grid. - * - * @throws Exception if the outline of the ocean blocks could not be drawn. - */ - public void drawBlocks() throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug("Showing layer BLOCKS"); - } - - drawLayer(layers.get("BLOCKS"), LINE_COLOR_BLOCK, LINE_WIDTH_BLOCK); - } - - /** - * Draw the outline of all cluster sets. - * - * @throws Exception if the outline of the cluster sets could not be drawn. - */ - public void drawClusters() throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug("Showing layer CLUSTERS"); - } - - drawLayer(layers.get("CLUSTERS"), LINE_COLOR_CLUSTER, LINE_WIDTH_CLUSTER); - } - - /** - * Draw the outline of all node sets. - * - * @throws Exception if the outline of the node sets could not be drawn. - */ - public void drawNodes() throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug("Showing layer NODES"); - } - - drawLayer(layers.get("NODES"), LINE_COLOR_NODE, LINE_WIDTH_NODE); - } - - /** - * Draw the outline of all core sets. - * - * @throws Exception if the outline of the core sets could not be drawn. - */ - public void drawCores() throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug("Showing layer CORES"); - } - - drawLayer(layers.get("CORES"), LINE_COLOR_CORE, LINE_WIDTH_CORE); - } - - /** - * Draw the outline of all blocks, cores, nodes and clusters - * - * @throws Exception if the outline of the core sets could not be drawn. - */ - public void drawAll() throws Exception { - - drawBlocks(); - - if (distribution.clusters > 1) { - drawClusters(); - } - - drawNodes(); - drawCores(); - } - - /** - * Save the current image as a png file. - * - * @param file the filename of the file to save. - * @throws IOException if the file could not be saved. - */ - public void save(String file) throws IOException { - view.save(file); - } - - /** - * Main entry point into application. - * - * @param args the command line arguments provided by the user. - */ - public static void main(String [] args) { - - if (args.length < 2) { - System.out.println("Usage: DistributionViewer topography_file distribution_file\n" + - "\n" + - "Read a topography file and work distribution file and show a graphical interface that allows " + - "the user to interactively explore the work distribution.\n" + - "\n" + - " topography_file a topography file that contains the index of the deepest ocean level at " + - "each gridpoint.\n" + - " distribution_file a work distribution file.\n"); - System.exit(1); - } - - String topographyFile = args[0]; - String distributionFile = args[1]; - - try { - Distribution d = new Distribution(distributionFile); - Topography t = new Topography(d.topographyWidth, d.topographyHeight, topographyFile); - Grid g = new Grid(t, d.blockWidth, d.blockHeight); - Neighbours n = new Neighbours(g, d.blockWidth, d.blockHeight, Neighbours.CYCLIC, Neighbours.TRIPOLE); - DistributionViewer dv = new DistributionViewer(d, t, g, n, true); - - dv.drawAll(); - - } catch (Exception e) { - Utils.fatal("Failed to run DistributionViewer ", e); - } - } + /** The color used for core land halos */ + private static final Color LAND_COLOR_CORE = new Color(0, 128, 255, 128); + + /** The line width used for cores */ + private static final float LINE_WIDTH_CORE = 13f; + + /** The line color used for blocks */ + private static final Color LINE_COLOR_BLOCK = Color.GRAY; //new Color(200, 200, 200, 255); + + /** The color used for block ocean halos */ + private static final Color HALO_COLOR_BLOCK = new Color(255, 128, 0, 255); + + /** The color used for block land halos */ + private static final Color LAND_COLOR_BLOCK = new Color(0, 128, 255, 255); + + /** The line width used for blocks */ + private static final float LINE_WIDTH_BLOCK = 5f; + + /** The color used for selected blocks */ + private static final Color CENTER = new Color(255, 0, 0, 210); + + /** The distribution to show */ + private final Distribution distribution; + + /** The topography to use. */ + private final Topography topography; + + /** A TopographyCanvas showing the topography */ + private final TopographyCanvas view; + + /** The Grid to use */ + private final Grid grid; + + /** The Layers containing the various subdivisions of blocks into subsets */ + private final Layers layers; + + /** Used the receive mouse clicks on the TopographyView */ + class MyListener implements MouseListener { + + @Override + public void mouseClicked(MouseEvent e) { + try { + clicked(e.getPoint()); + } catch (Exception ex) { + logger.warn("Failed to select block!", ex); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + } + } + + /** + * Create a new DistributionViewer for the given topography, grid and layers. + * + * @param distribution + * the distribution to show. + * @param topography + * the topography to use. + * @param grid + * the grid to use. + * @param showGUI + * should the GUI be shown ? + * @throws Exception + * if the DistributionViewer could not be initialized. + */ + public DistributionViewer(Distribution distribution, Topography topography, Grid grid, boolean showGUI) + throws Exception { + + this.distribution = distribution; + this.topography = topography; + this.grid = grid; + this.layers = distribution.toLayers(grid); + + view = new TopographyCanvas(topography, grid); + view.addLayer("BLOCKS"); + view.addLayer("CORES"); + view.addLayer("NODES"); + view.addLayer("CLUSTERS"); + view.addLayer("FILL"); + + if (showGUI) { + JFrame frame = new JFrame("Topography"); + frame.setSize(1000, 667); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().add(view); + frame.setVisible(true); + view.addMouseListener(new MyListener()); + } + + boolean showWork = true; + + if (showWork) { + for (int y = 0; y < grid.height; y++) { + for (int x = 0; x < grid.width; x++) { + + Block b = grid.get(x, y); + + if (!b.ocean) { + view.fillBlock("BLOCKS", x, y, Color.GRAY); + } else { + view.fillBlock("BLOCKS", x, y, Color.WHITE); + } + } + } + } + + } + + public void repaint() { + view.repaint(); + } + + public void addLayer(String name) throws Exception { + view.addLayer(name); + } + + public void clearLayer(String name) throws Exception { + view.clearLayer(name); + } + + public void fillBlock(String layer, int x, int y, Color color) throws Exception { + view.fillBlock(layer, x, y, color); + } + + public void draw(String layer, int x, int y, Color color) throws Exception { + view.draw(layer, x, y, color); + } + + /** + * Color the edge of the set with the provided colors for ocean and land. + * + * @param layer + * the layer at which the edge must be drawn. + * @param s + * the set to draw the edge for. + * @param ocean + * the color to use for ocean neighbors. + * @param land + * the color to use for land neighbors. + * @throws Exception + * if the edge could not be drawn. + */ + private void colorEdge(String layer, Set s, Color ocean, Color land) throws Exception { + + if (s == null) { + return; + } + + int [] tmp = s.getNeighbours(); + + for (int i=0;iMouseListener attached to the TopographyView. + * + * @param p + * the Point that has been clicked. + * @throws Exception + * if the mouse click could not be processed. + */ + private void clicked(Point p) throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug("Click at " + p.x + " " + p.y); + } + + double w = view.getWidth(); + double h = view.getHeight(); + + int posX = (int) ((p.x / w) * topography.width); + int posY = topography.height - (int) ((p.y / h) * topography.height); + + int bx = posX / grid.blockWidth; + int by = posY / grid.blockHeight; + + int commBlock = -1; + int commCore = -1; + int commNode = -1; + int commCluster = -1; + + int block = -1; + int core = -1; + int node = -1; + int cluster = -1; + + /* + Coordinate c = new Coordinate(bx, by); + + int [][] comm = neighbours.getCommunication(c); + + System.out.println("Communication on BLOCK layer: "); + + for (int i=0;i<3;i++) { + for (int j=0;j<3;j++) { + System.out.print(" " + comm[i][j]); + } + System.out.println(); + } + */ + + System.out.println("Click at " + bx + " " + by); + + Block b = grid.get(bx, by); + + System.out.println("Block " + b); + + System.out.println("Block neighbours"); + + int [][] tmp = b.getNeighbours(); + + System.out.println(" " + Arrays.toString(tmp[0])); + System.out.println(" " + Arrays.toString(tmp[1])); + System.out.println(" " + Arrays.toString(tmp[2])); + + System.out.println("Block comm"); + + tmp = b.getCommunication(); + + System.out.println(" " + Arrays.toString(tmp[0])); + System.out.println(" " + Arrays.toString(tmp[1])); + System.out.println(" " + Arrays.toString(tmp[2])); + + view.clearLayer("FILL"); + + if (layers.contains("BLOCKS")) { + + Layer l = layers.get("BLOCKS"); + Set s = l.locate(bx, by); + + if (s != null) { + block = s.index; + commBlock = s.getCommunication(); + colorEdge("FILL", s, HALO_COLOR_BLOCK, LAND_COLOR_BLOCK); + } + } + + if (layers.contains("CORES")) { + Layer l = layers.get("CORES"); + Set s = l.locate(bx, by); + + if (s != null) { + core = s.index; + commCore = s.getCommunication(); + colorEdge("FILL", s, HALO_COLOR_CORE, LAND_COLOR_CORE); + } + } + + if (layers.contains("NODES")) { + Layer l = layers.get("NODES"); + Set s = l.locate(bx, by); + + if (s != null) { + node = s.index; + commNode = s.getCommunication(); + colorEdge("FILL", s, HALO_COLOR_NODE, LAND_COLOR_NODE); + } + } + + if (layers.contains("CLUSTERS")) { + Layer l = layers.get("CLUSTERS"); + Set s = l.locate(bx, by); + + if (s != null) { + cluster = s.index; + commCluster = s.getCommunication(); + colorEdge("FILL", s, HALO_COLOR_CLUSTER, LAND_COLOR_CLUSTER); + } + } + + System.out.print("Selected"); + + if (cluster >= 0) { + System.out.print(" cluster " + cluster); + } + + if (node >= 0) { + System.out.print(" node " + node); + } + + System.out.println(" core " + core + " block " + block + " (" + bx + "x" + by + ")"); + + System.out.println("Communication on layer BLOCK " + commBlock); + System.out.println("Communication on layer CORE " + commCore); + System.out.println("Communication on layer NODE " + commNode); + System.out.println("Communication on layer CLUSTER " + commCluster); + + /* + Coordinate [][] tmp = neighbours.getNeighbours(c, true); + + for (int i=0;i<3;i++) { + for (int j=0;j<3;j++) { + if (tmp[i][j] != null) { + + int nx = tmp[i][j].x; + int ny = tmp[i][j].y; + + //System.out.println("Fill neighbour " + nx + "x" + ny); + + if (grid.get(nx, ny) != null) { + view.fillBlock("FILL", nx, ny, HALO_COLOR_BLOCK); + } else { + view.fillBlock("FILL", nx, ny, LAND_COLOR_BLOCK); + } + } + } + } + */ + + view.fillBlock("FILL", bx, by, CENTER); + view.repaint(); + } + + /** + * Creates a HashMap containing all edges (Lines) of all Blocks in the given set. + *

+ * Each edge in the HashMap maps to an Integer value which indicates how often the edge was encountered in the set. The edges + * that have a count of 1 together form the outer edge of the set. + * + * @param s + * the set for which to collect the edges. + * @param out + * the HashMap to which the result will be added. + */ + private void collectLines(Set s, HashMap out) { + + for (int i = 0; i < s.size(); i++) { + + Coordinate c = s.get(i).coordinate; + + if (c.x < grid.width) { + addLine(out, new Line(c, c.offset(1, 0))); + + if (c.y < grid.height) { + addLine(out, new Line(c.offset(0, 1), c.offset(1, 1))); + addLine(out, new Line(c.offset(1, 0), c.offset(1, 1))); + } + } + + if (c.y < grid.height) { + addLine(out, new Line(c, c.offset(0, 1))); + } + } + } + + /** + * Add a line to the HashMap. + * + * If the line was not in the HashMap yet, it is added mapping to value 1. Else the value it maps to is + * incremented by one. + * + * @param map + * the HashMap to add the line to. + * @param line + * the Line to add. + */ + private void addLine(HashMap map, Line line) { + + if (!map.containsKey(line)) { + map.put(line, 1); + return; + } + + int count = map.get(line); + map.put(line, count + 1); + } + + /** + * Draw all lines in the HashMap which map to the value 1 in the specified layer. + * + * @param layer + * the layer to draw to. + * @param map + * the collection of lines. + * @param color + * the color to draw the lines in. + * @param lineWidth + * the width of the lines to draw. + * @throws Exception + * if the lines could not be drawn. + */ + private void drawLines(String layer, HashMap map, Color color, float lineWidth) throws Exception { + + if (view == null) { + return; + } + + // We draw each line that has only been added to the map once. + for (Line l : map.keySet()) { + + int count = map.get(l); + + if (count == 1) { + view.draw(layer, l, color, lineWidth); + } + } + } + + private void drawLines(String layer, Line [] lines, Color color, float lineWidth) throws Exception { + + if (view == null) { + return; + } + + // We draw each line that has only been added to the map once. + for (Line l : lines) { + view.draw(layer, l, color, lineWidth); + } + } + + + /** + * Draw the edges of all sets in the specified layer. + * + * @param layer + * the layer to draw. + * @param color + * the color to draw the lines in. + * @param lineWidth + * the width of the lines to draw. + * @throws Exception + * if the lines could not be drawn. + */ + private void drawLayer(Layer l, Color color, float lineWidth) throws Exception { + + if (view == null || l == null) { + return; + } + + for (int i = 0; i < l.size(); i++) { + + Set s = l.get(i); + + //HashMap map = new HashMap(); + + //collectLines(s, map); + + // drawLines(l.name, map, color, lineWidth); + + drawLines(l.name, s.getEdges(), color, lineWidth); + } + + view.repaint(); + } + + /** + * Draw the outline of all ocean blocks in the grid. + * + * @throws Exception + * if the outline of the ocean blocks could not be drawn. + */ + public void drawBlocks() throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug("Showing layer BLOCKS"); + } + + drawLayer(layers.get("BLOCKS"), LINE_COLOR_BLOCK, LINE_WIDTH_BLOCK); + } + + /** + * Draw the outline of all cluster sets. + * + * @throws Exception + * if the outline of the cluster sets could not be drawn. + */ + public void drawClusters() throws Exception { + + if (distribution.clusters == 1) { + return; + } + + if (logger.isDebugEnabled()) { + logger.debug("Showing layer CLUSTERS"); + } + + drawLayer(layers.get("CLUSTERS"), LINE_COLOR_CLUSTER, LINE_WIDTH_CLUSTER); + } + + /** + * Draw the outline of all node sets. + * + * @throws Exception + * if the outline of the node sets could not be drawn. + */ + public void drawNodes() throws Exception { + + if (distribution.nodesPerCluster == 1) { + return; + } + + if (logger.isDebugEnabled()) { + logger.debug("Showing layer NODES"); + } + + drawLayer(layers.get("NODES"), LINE_COLOR_NODE, LINE_WIDTH_NODE); + } + + /** + * Draw the outline of all core sets. + * + * @throws Exception + * if the outline of the core sets could not be drawn. + */ + public void drawCores() throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug("Showing layer CORES"); + } + + drawLayer(layers.get("CORES"), LINE_COLOR_CORE, LINE_WIDTH_CORE); + } + + /** + * Draw the outline of all blocks, cores, nodes and clusters + * + * @throws Exception + * if the outline of the core sets could not be drawn. + */ + public void drawAll() throws Exception { + + drawBlocks(); + drawCores(); + drawNodes(); + + if (distribution.clusters > 1) { + drawClusters(); + } + } + + /** + * Save the current image as a png file. + * + * @param file + * the filename of the file to save. + * @throws IOException + * if the file could not be saved. + */ + public void save(String file) throws IOException { + view.save(file); + } + + /** + * Main entry point into application. + * + * @param args + * the command line arguments provided by the user. + */ + public static void main(String[] args) { + + if (args.length < 2) { + System.out.println("Usage: DistributionViewer topography_file distribution_file\n" + "\n" + + "Read a topography file and work distribution file and show a graphical interface that allows " + + "the user to interactively explore the work distribution.\n" + "\n" + + " topography_file a topography file that contains the index of the deepest ocean level at " + + "each gridpoint.\n" + " distribution_file a work distribution file.\n"); + System.exit(1); + } + + String topographyFile = args[0]; + String distributionFile = args[1]; + + try { + Distribution d = new Distribution(distributionFile); + Topography t = new Topography(d.topographyWidth, d.topographyHeight, topographyFile); + + int gridWidth = t.width / d.blockWidth; + int gridHeight = t.height / d.blockHeight; + + Neighbours n = new Neighbours(t, gridWidth, gridHeight, d.blockWidth, d.blockHeight, + Neighbours.CYCLIC, Neighbours.TRIPOLE); + + Grid g = new Grid(gridWidth, gridHeight, d.blockWidth, d.blockHeight, n); + + DistributionViewer dv = new DistributionViewer(d, t, g, true); + + dv.drawAll(); + + dv.save("test.png"); + + } catch (Exception e) { + Utils.fatal("Failed to run DistributionViewer ", e); + } + } } \ No newline at end of file diff --git a/src/nl/esciencecenter/esalsa/tools/LoadBalancing.java b/src/nl/esciencecenter/esalsa/tools/LoadBalancing.java index 141bfb2..9377f14 100644 --- a/src/nl/esciencecenter/esalsa/tools/LoadBalancing.java +++ b/src/nl/esciencecenter/esalsa/tools/LoadBalancing.java @@ -17,283 +17,362 @@ package nl.esciencecenter.esalsa.tools; import nl.esciencecenter.esalsa.loadbalancer.LoadBalancer; +import nl.esciencecenter.esalsa.util.Block; import nl.esciencecenter.esalsa.util.Distribution; import nl.esciencecenter.esalsa.util.Grid; import nl.esciencecenter.esalsa.util.Layers; import nl.esciencecenter.esalsa.util.Neighbours; +import nl.esciencecenter.esalsa.util.OptimizeTopography; import nl.esciencecenter.esalsa.util.Topography; /** - * LoadBalancing is an application that generates a block distribution for POP. + * LoadBalancing is an application that generates a block distribution for POP. * - * The block distribution is generated based on an ocean bottom topography, the desired block size, and the - * desired number of clusters, nodes per cluster, and cores per node, and a desired split heuristic. + * The block distribution is generated based on an ocean bottom topography, the desired block size, and the desired number of + * clusters, nodes per cluster, and cores per node, and a desired split heuristic. * * @author Jason Maassen * @version 1.0 * @since 1.0 - * + * */ public class LoadBalancing { - /** The topographyWidth as set by user */ - private static int topographyWidth = -1; - - /** The topographyHeight as set by user */ - private static int topographyHeight = -1; - - /** The blockWidth as set by user */ - private static int blockWidth = -1; - - /** The blockHeight as set by user */ - private static int blockHeight = -1; - - /** The number of clusters as set by user */ - private static int clusters = 1; - - /** The number of nodes per cluster as set by user */ - private static int nodesPerCluster = -1; - - /** The number of cores per node as set by user */ - private static int coresPerNode = -1; - - /** Should we show the GUI ? */ - private static boolean showGUI = false; - - /** Statistics to print */ - private static String statistics = null; - - /** File name of topography input file to load */ - private static String topographyFile = null; - - /** File name of output distribution file to write */ - private static String outputDistribution = null; - - /** File name of output image file to write */ - private static String outputImage = null; - - /** Name of the split methods to use */ - private static String splitMethod = "roughlyrect"; - - /** - * Print the usage on the console. - */ - private static void usage() { - System.out.println( - "Usage: LoadBalancing topography_file [OPTION]*\n" + - "\n" + - "Reads a POP topography file and determines a block distribution suitable for a number nodes each" + - " containing a number of cores (and optionally divided into a number of clusters).\n" + - "\n" + - "Mandatory arguments:\n" + - " topography_file the topography file that contains the index of the deepest ocean levels.\n" + - " --grid WIDTH HEIGHT dimensions of the topography file grid (WIDTHxHEIGHT).\n" + - " --blocksize WIDTH HEIGHT dimensions of the blocks to use (WIDTHxHEIGHT).\n" + - " --nodes NODES number of nodes for which the distribution must be calculated.\n" + - " --cores CORES number of cores in each node.\n" + - "\n" + - "Optional arguments:\n" + - " --clusters CLUSTERS number of clusters to calculate ditribution for (default is 1).\n" + - " --output FILE store the resulting distribution in FILE.\n" + - " --image FILE store an image of the resulting distribution in FILE.\n" + - " --statistics LAYER print statistics on the resulting distribution on layer LAYER. Valid" + - " value for LAYER are CORES, NODES, CLUSTERS, ALL.\n" + - " --method METHOD method used to distribute the blocks. Valid values for METHOD are" + - " SIMPLE, ROUGHLYRECT, and SEARCH. Default is ROUGHLYRECT.\n" + - " --showgui show a graphical interface that allows the user to explore the distribution.\n" + - " --help show this help."); - - System.exit(0); - } - - /** - * Create a LoadBalancer for the given topography and core configuration, run it, and save the desired output. - */ - private static void run() { - - try { - Topography topography = new Topography(topographyWidth, topographyHeight, topographyFile); - Grid grid = new Grid(topography, blockWidth, blockHeight); - - Neighbours neighbours = new Neighbours(grid, blockWidth, blockHeight, Neighbours.CYCLIC, Neighbours.TRIPOLE); - - Layers layers = new Layers(); - - LoadBalancer lb = new LoadBalancer(layers, neighbours, topography, grid, blockWidth, blockHeight, - clusters, nodesPerCluster, coresPerNode, splitMethod); - - Distribution distribution = lb.split(); - - if (showGUI || outputImage != null) { - - DistributionViewer sv = new DistributionViewer(distribution, topography, grid, neighbours, showGUI); - sv.drawBlocks(); - - if (clusters > 1) { - sv.drawClusters(); - } - - if (nodesPerCluster > 1) { - sv.drawNodes(); - } - - if (coresPerNode > 1) { - sv.drawCores(); - } - - if (outputImage != null) { - try { - sv.save(outputImage); - } catch (Exception e) { - System.err.println("Failed to save distribution to file " + outputDistribution + ": " - + e.getLocalizedMessage()); - } - } - } - - if (statistics != null) { - try { - lb.printStatistics(statistics); - } catch (Exception e) { - System.err.println("Failed to print statistics " + statistics + ": " + e.getLocalizedMessage()); - } - } - - if (outputDistribution != null) { - try { - distribution.write(outputDistribution); - } catch (Exception e) { - System.err.println("Failed to save distribution to file " + outputDistribution + ": " + e); - e.printStackTrace(); - } - } - - } catch (Exception e) { - Utils.fatal(e); - } - } - - /** - * Main entry point into application. - * - * @param args the command line arguments provided by the user. - */ - public static void main(String [] args) { - - boolean topographySet = false; - boolean blockSet = false; - boolean nodesSet = false; - boolean coresSet = false; - - if (args.length < 1) { - usage(); - } - - topographyFile = args[0]; - - int index = 1; - - while (index < args.length) { - - if (args[index].equals("--grid")) { - Utils.checkOptions("--grid", 2, index, args.length); - topographyWidth = Utils.parseInt("--grid", args[index+1], 1); - topographyHeight = Utils.parseInt("--grid", args[index+2], 1); - index += 3; - topographySet = true; - - } else if (args[index].equals("--blocksize")) { - Utils.checkOptions("--blocksize", 2, index, args.length); - blockWidth = Utils.parseInt("--blocksize", args[index+1], 1); - blockHeight = Utils.parseInt("--blocksize", args[index+2], 1); - index += 3; - blockSet = true; - - } else if (args[index].equals("--clusters")) { - Utils.checkOptions("--clusters", 1, index, args.length); - clusters = Utils.parseInt("--clusters", args[index+1], 1); - index += 2; - - } else if (args[index].equals("--nodes")) { - Utils.checkOptions("--nodes", 1, index, args.length); - nodesPerCluster = Utils.parseInt("--nodes", args[index+1], 1); - index += 2; - nodesSet = true; - - } else if (args[index].equals("--cores")) { - Utils.checkOptions("--cores", 1, index, args.length); - coresPerNode = Utils.parseInt("--cores", args[index+1], 1); - index += 2; - coresSet = true; - - } else if (args[index].equals("--output")) { - Utils.checkOptions("--output", 1, index, args.length); - outputDistribution = args[index+1]; - index += 2; - - } else if (args[index].equals("--image")) { - Utils.checkOptions("--image", 1, index, args.length); - outputImage = args[index+1]; - index += 2; - - } else if (args[index].equals("--showGUI")) { - showGUI = true; - index++; - - } else if (args[index].equals("--help")) { - usage(); - - } else if (args[index].equals("--statistics")) { - Utils.checkOptions("--statistics", 1, index, args.length); - statistics = args[index+1]; - index += 2; - - } else if (args[index].equals("--method")) { - Utils.checkOptions("--method", 1, index, args.length); - splitMethod = args[index+1]; - index += 2; - - } else { - Utils.fatal("Unknown option: " + args[index]); - } - } - - if (!topographySet) { - Utils.fatal("Please specify topography dimension using \"--grid WIDTH HEIGHT\""); - } - - if (!blockSet) { - Utils.fatal("Please specify block dimension using \"--blocksize WIDTH HEIGHT\""); - } - - if (!nodesSet) { - Utils.fatal("Please specify node count using \"--nodes NODES\""); - } - - if (!coresSet) { - Utils.fatal("Please specify core per node using \"--cores CORES\""); - } - - if (blockWidth > topographyWidth) { - Utils.fatal("Block width cannot be larger that grid width"); - } - - if (topographyWidth % blockWidth != 0) { - Utils.fatal("Block width must divide grid width equally!"); - } - - if (blockHeight > topographyHeight) { - Utils.fatal("Block height cannot be larger that grid height"); - } - - if (topographyHeight % blockHeight != 0) { - Utils.fatal("Block height must divide grid height equally!"); - } - - if (!showGUI && outputDistribution == null && outputImage == null && statistics == null) { - System.out.println("WARNING: This application will not produce any output, since none of the outputs is selected!"); - } - - run(); - } + /** The topographyWidth as set by user */ + private static int topographyWidth = -1; + + /** The topographyHeight as set by user */ + private static int topographyHeight = -1; + + /** The blockWidth as set by user */ + private static int blockWidth = -1; + + /** The blockHeight as set by user */ + private static int blockHeight = -1; + + /** The number of clusters as set by user */ + private static int clusters = 1; + + /** The number of nodes per cluster as set by user */ + private static int nodesPerCluster = -1; + + /** The number of cores per node as set by user */ + private static int coresPerNode = -1; + + /** The boundary wrapping method in the X direction (CYCLIC or CLOSED). */ + private static int boundaryWrapX = Neighbours.CYCLIC; + + /** The boundary wrapping method in the Y direction (TRIPOLE, CYCLIC or CLOSED).*/ + private static int boundaryWrapY = Neighbours.TRIPOLE; + + /** Should we show the GUI ? */ + private static boolean showGUI = false; + + /** Should we optimize the topology before creating a distribution ? */ + private static boolean optimizeTopolography = false; + + /** Statistics to print */ + private static String statistics = null; + + /** File name of topography input file to load */ + private static String topographyFile = null; + + /** File name of output distribution file to write */ + private static String outputDistribution = null; + + /** File name of output image file to write */ + private static String outputImage = null; + + /** Name of the split methods to use */ + private static String splitMethod = "search"; + + /** + * Print the usage on the console. + */ + private static void usage() { + System.out.println("Usage: LoadBalancing topography_file [OPTION]*\n" + "\n" + + "Reads a POP topography file and determines a block distribution suitable for a number nodes each" + + " containing a number of cores (and optionally divided into a number of clusters).\n" + "\n" + + "Mandatory arguments:\n" + + " topography_file the topography file that contains the index of the deepest ocean levels.\n" + + " --grid WIDTH HEIGHT dimensions of the topography file grid (WIDTHxHEIGHT).\n" + + " --blocksize WIDTH HEIGHT dimensions of the blocks to use (WIDTHxHEIGHT).\n" + + " --nodes NODES number of nodes for which the distribution must be calculated.\n" + + " --cores CORES number of cores in each node.\n" + + "\nOptional arguments:\n" + + " --clusters CLUSTERS number of clusters to calculate ditribution for (default is 1).\n" + + " --output FILE store the resulting distribution in FILE.\n" + + " --image FILE store an image of the resulting distribution in FILE.\n" + + " --statistics LAYER print statistics on the resulting distribution on layer LAYER. Valid" + + " value for LAYER are CORES, NODES, CLUSTERS, ALL.\n" + + " --method METHOD method used to distribute the blocks. Valid values for METHOD are" + + " SEARCH (default), SIMPLE and ROUGHLYRECT.\n" + + " --optimizeTopography optimize topography before determining distribution.\n" + + " --boundaryWrapX wrapping method used in X direction. Valid values are CLOSED (default) and " + + "CYCLIC.\n" + + " --boundaryWrapY wrapping method used in Y direction. Valid values are TRIPOLE (default), CLOSED" + + " and CYCLIC.\n" + + " --showgui show a graphical interface that allows the user to explore the distribution.\n" + + " --help show this help."); + System.exit(0); + } + + /** + * Create a LoadBalancer for the given topography and core configuration, run it, and save the desired output. + */ + private static void run() { + + try { + int gridWidth = topographyWidth / blockWidth; + int gridHeigth = topographyHeight / blockHeight; + + Topography topography = new Topography(topographyWidth, topographyHeight, topographyFile); + + Neighbours neighbours = new Neighbours(topography, gridWidth, gridHeigth, blockWidth, blockHeight, + boundaryWrapX, boundaryWrapY); + + Grid grid = new Grid(gridWidth, gridHeigth, blockWidth, blockHeight, neighbours); + + Distribution distribution = null; + LoadBalancer lb = null; + + if (optimizeTopolography) { + + OptimizeTopography top = new OptimizeTopography(topography, grid, neighbours); + top.optimize(); + + Topography optT = top.getOptimizedTopography(); + Grid optG = top.getOptimizedGrid(); + + Layers l = new Layers(); + lb = new LoadBalancer(l, optT.width, optT.height, optG, + blockWidth, blockHeight, + clusters, nodesPerCluster, coresPerNode, splitMethod); + + lb.split(); + + int [] result = new int[gridWidth * gridHeigth]; + + for (Block b : optG) { + + if (b.ocean) { + result[b.blockID-1] = b.getMark()+1; + } else { + result[b.blockID-1] = 0; + } + } + + Distribution tmpD = lb.getDistribution(); + + distribution = new Distribution(topographyWidth, topographyHeight, + blockWidth, blockHeight, clusters, nodesPerCluster, coresPerNode, + tmpD.minBlocksPerCore, tmpD.maxBlocksPerCore, + gridWidth * gridHeigth, result); + + } else { + + Layers layers = new Layers(); + + lb = new LoadBalancer(layers, topography.width, topography.height, grid, blockWidth, blockHeight, + clusters, nodesPerCluster, coresPerNode, splitMethod); + + lb.split(); + distribution = lb.getDistribution(); + } + + if (showGUI || outputImage != null) { + + DistributionViewer sv = new DistributionViewer(distribution, topography, grid, showGUI); + sv.drawBlocks(); + + if (clusters > 1) { + sv.drawClusters(); + } + + if (nodesPerCluster > 1) { + sv.drawNodes(); + } + + if (coresPerNode > 1) { + sv.drawCores(); + } + + if (outputImage != null) { + try { + sv.save(outputImage); + } catch (Exception e) { + System.err.println("Failed to save distribution to file " + outputDistribution + ": " + + e.getLocalizedMessage()); + } + } + } + + if (statistics != null) { + try { + lb.printStatistics(statistics); + } catch (Exception e) { + System.err.println("Failed to print statistics " + statistics + ": " + e.getLocalizedMessage()); + } + } + + if (outputDistribution != null) { + try { + distribution.write(outputDistribution); + } catch (Exception e) { + System.err.println("Failed to save distribution to file " + outputDistribution + ": " + e); + e.printStackTrace(); + } + } + + } catch (Exception e) { + Utils.fatal(e); + } + } + + /** + * Main entry point into application. + * + * @param args + * the command line arguments provided by the user. + */ + public static void main(String[] args) { + + boolean topographySet = false; + boolean blockSet = false; + boolean nodesSet = false; + boolean coresSet = false; + + if (args.length < 1) { + usage(); + } + + topographyFile = args[0]; + + int index = 1; + + while (index < args.length) { + + if (args[index].equals("--grid")) { + Utils.checkOptions("--grid", 2, index, args.length); + topographyWidth = Utils.parseInt("--grid", args[index + 1], 1); + topographyHeight = Utils.parseInt("--grid", args[index + 2], 1); + index += 3; + topographySet = true; + + } else if (args[index].equals("--blocksize")) { + Utils.checkOptions("--blocksize", 2, index, args.length); + blockWidth = Utils.parseInt("--blocksize", args[index + 1], 1); + blockHeight = Utils.parseInt("--blocksize", args[index + 2], 1); + index += 3; + blockSet = true; + + } else if (args[index].equals("--clusters")) { + Utils.checkOptions("--clusters", 1, index, args.length); + clusters = Utils.parseInt("--clusters", args[index + 1], 1); + index += 2; + + } else if (args[index].equals("--nodes")) { + Utils.checkOptions("--nodes", 1, index, args.length); + nodesPerCluster = Utils.parseInt("--nodes", args[index + 1], 1); + index += 2; + nodesSet = true; + + } else if (args[index].equals("--cores")) { + Utils.checkOptions("--cores", 1, index, args.length); + coresPerNode = Utils.parseInt("--cores", args[index + 1], 1); + index += 2; + coresSet = true; + + } else if (args[index].equals("--output")) { + Utils.checkOptions("--output", 1, index, args.length); + outputDistribution = args[index + 1]; + index += 2; + + } else if (args[index].equals("--image")) { + Utils.checkOptions("--image", 1, index, args.length); + outputImage = args[index + 1]; + index += 2; + + } else if (args[index].equals("--optimizeTopography")) { + optimizeTopolography = true; + index++; + + } else if (args[index].equals("--showGUI")) { + showGUI = true; + index++; + + } else if (args[index].equals("--help")) { + usage(); + + } else if (args[index].equals("--statistics")) { + Utils.checkOptions("--statistics", 1, index, args.length); + statistics = args[index + 1]; + index += 2; + + } else if (args[index].equals("--method")) { + Utils.checkOptions("--method", 1, index, args.length); + splitMethod = args[index + 1]; + index += 2; + + } else if (args[index].equals("--boundaryWrapX")) { + Utils.checkOptions("--boundaryWrapX", 1, index, args.length); + boundaryWrapX = Neighbours.parseBoundaryX(args[index + 1]); + index += 2; + + } else if (args[index].equals("--boundaryWrapY")) { + Utils.checkOptions("--boundaryWrapY", 1, index, args.length); + boundaryWrapY = Neighbours.parseBoundaryY(args[index + 1]); + index += 2; + + } else { + Utils.fatal("Unknown option: " + args[index]); + } + } + + if (!topographySet) { + Utils.fatal("Please specify topography dimension using \"--grid WIDTH HEIGHT\""); + } + + if (!blockSet) { + Utils.fatal("Please specify block dimension using \"--blocksize WIDTH HEIGHT\""); + } + + if (!nodesSet) { + Utils.fatal("Please specify node count using \"--nodes NODES\""); + } + + if (!coresSet) { + Utils.fatal("Please specify core per node using \"--cores CORES\""); + } + + if (boundaryWrapX == -1) { + Utils.fatal("Invalid value for boundaryWrapX"); + } + + if (boundaryWrapY == -1) { + Utils.fatal("Invalid value for boundaryWrapY"); + } + + if (blockWidth > topographyWidth) { + Utils.fatal("Block width cannot be larger that grid width"); + } + + if (topographyWidth % blockWidth != 0) { + Utils.fatal("Block width must divide grid width equally!"); + } + + if (blockHeight > topographyHeight) { + Utils.fatal("Block height cannot be larger that grid height"); + } + + if (topographyHeight % blockHeight != 0) { + Utils.fatal("Block height must divide grid height equally!"); + } + + if (!showGUI && outputDistribution == null && outputImage == null && statistics == null) { + System.out.println("WARNING: This application will not produce any output, since none of the outputs is selected!"); + } + + run(); + } } \ No newline at end of file diff --git a/src/nl/esciencecenter/esalsa/tools/PrintStatistics.java b/src/nl/esciencecenter/esalsa/tools/PrintStatistics.java index 2ada952..dfa88a1 100644 --- a/src/nl/esciencecenter/esalsa/tools/PrintStatistics.java +++ b/src/nl/esciencecenter/esalsa/tools/PrintStatistics.java @@ -24,45 +24,50 @@ /** * PrintStatistics is an application that prints information about a given POP distribution. - * + * * @author Jason Maassen * @version 1.0 * @since 1.0 */ public class PrintStatistics { - - /** - * Main entry point into the application. - * - * @param args the command line arguments. - */ - public static void main(String [] args) { - if (args.length < 3) { - System.out.println("Usage: PrintStatistics topography_file distribution_file statistics_name\n" + - "\n" + - "Read a topography file and work distribution file and print statistics on the work distribution and " + - "communication per cluster, node or core.\n" + - "\n" + - " topography_file a topography file that contains the index of the deepest ocean level at " + - "each gridpoint.\n" + - " distribution_file a work distribution file.\n" + - " statistics_name name of the statistics to print. Valid values are CLUSTER, NODE, CORE, ALL."); - - System.exit(1); - } - - try { - Distribution d = new Distribution(args[1]); - Topography t = new Topography(d.topographyWidth, d.topographyHeight, args[0]); - Grid g = new Grid(t, d.blockWidth, d.blockHeight); - Neighbours n = new Neighbours(g, d.blockWidth, d.blockHeight, Neighbours.CYCLIC, Neighbours.TRIPOLE); - - Statistics s = new Statistics(d.toLayers(), n); - s.printStatistics(args[2], System.out); - - } catch (Exception e) { - Utils.fatal("Failed to print statistics!", e); - } - } + /** + * Main entry point into the application. + * + * @param args + * the command line arguments. + */ + public static void main(String[] args) { + + if (args.length < 3) { + System.out.println("Usage: PrintStatistics topography_file distribution_file statistics_name\n" + "\n" + + "Read a topography file and work distribution file and print statistics on the work distribution and " + + "communication per cluster, node or core.\n" + "\n" + + " topography_file a topography file that contains the index of the deepest ocean level at " + + "each gridpoint.\n" + " distribution_file a work distribution file.\n" + + " statistics_name name of the statistics to print. Valid values are CLUSTER, NODE, CORE, ALL."); + + System.exit(1); + } + + try { + Distribution d = new Distribution(args[1]); + Topography t = new Topography(d.topographyWidth, d.topographyHeight, args[0]); + + int gridWidth = t.width / d.blockWidth; + int gridHeight = t.height / d.blockHeight; + + // FIXME!!!! + Neighbours n = new Neighbours(t, gridWidth, gridHeight, d.blockWidth, d.blockHeight, + Neighbours.CYCLIC, Neighbours.TRIPOLE); + + Grid g = new Grid(gridWidth, gridHeight, d.blockWidth, d.blockHeight, n); + + Statistics s = new Statistics(d.toLayers(g)); + s.printStatistics(args[2], System.out); + + } catch (Exception e) { + Utils.fatal("Failed to print statistics!", e); + } + } } diff --git a/src/nl/esciencecenter/esalsa/tools/TextToDistribution.java b/src/nl/esciencecenter/esalsa/tools/TextToDistribution.java index e4448ed..60ce9e8 100644 --- a/src/nl/esciencecenter/esalsa/tools/TextToDistribution.java +++ b/src/nl/esciencecenter/esalsa/tools/TextToDistribution.java @@ -24,72 +24,74 @@ import nl.esciencecenter.esalsa.util.Distribution; /** - * TextToDistribution is an application that converts a POP distribution represented in ASCI text to the binary format used in - * POP itself. - * + * TextToDistribution is an application that converts a POP distribution represented in ASCI text to the binary format used in POP + * itself. + * * @author Jason Maassen * @version 1.0 * @since 1.0 */ public class TextToDistribution { - /** - * Main entry point into application. - * - * @param args the command line arguments provided by the user. - */ - public static void main(String [] args) { - - if (args.length != 2) { - System.err.println("Usage: TextToDistribution [text-file] [distribution file]"); - System.exit(1); - } - - BufferedReader r = null; - - try { - r = new BufferedReader(new FileReader(new File(args[0]))); - } catch (IOException e) { - Utils.fatal("Failed to open input file: " + args[0] + "\n", e); - } - - Distribution d = null; - - try { - int topographyWidth = Integer.parseInt(r.readLine().trim()); - int topographyHeight = Integer.parseInt(r.readLine().trim()); - - int blockWidth = Integer.parseInt(r.readLine().trim()); - int blockHeight = Integer.parseInt(r.readLine().trim()); - - int clusters = Integer.parseInt(r.readLine().trim()); - int nodesPerCluster = Integer.parseInt(r.readLine().trim()); - int coresPerNode = Integer.parseInt(r.readLine().trim()); - - int minBlocksPerCore = Integer.parseInt(r.readLine().trim()); - int maxBlocksPerCore = Integer.parseInt(r.readLine().trim()); - - int totalBlocks = Integer.parseInt(r.readLine().trim()); - - int [] distribution = new int[totalBlocks]; - - for (int i=0;i * @version 1.0 * @since 1.0 */ public class TopographyViewer { - /** - * Main entry point into application. - * - * @param args the command line arguments provided by the user. - */ - public static void main(String [] args) { - - if (args.length < 3) { - System.out.println("Usage: TopographyViewer topography_file topography_width topography_height " + - "block_width block_height [--showGUI] [--image image.png]\n" + - "\n" + - "Read a topography file of topography_width x topography_height, and divide it into blocks of size " + - "block_width x block_height. Optionally, the result can be shown in a graphical interface or saved in an " + - "image.\n" + - "\n" + - " topography_file a topography file that contains the index of the deepest ocean level at " + - "each gridpoint.\n" + - " topography_width the width of the topography.\n" + - " topography_height the heigth of the topography.\n" + - " [--blocks width height] divide the topology into blocks of width c height.\n" + - " [--showWork] color blocks according to work.\n" + - " [--image image.png] store the result in a png image instead of showing it in a GUI.\n"); - - System.exit(1); - } - - String topographyFile = args[0]; - int width = Utils.parseInt("topography_width", args[1], 1); - int height = Utils.parseInt("topography_height", args[2], 1); - - int blockWidth = width; - int blockHeight = height; - - boolean showWork = false; - - String output = null; - - int i=3; - - while (i= args.length) { - Utils.fatal("Option \"--blocks\" requires width and height parameters!"); - } - - blockWidth = Utils.parseInt("block_width", args[i+1], 1); - blockHeight = Utils.parseInt("block_height", args[i+2], 1); - i += 3; - - } else if (args[i].equals("--showWork")) { - showWork = true; - i++; - - } else if (args[i].equals("--image")) { - - if ((i+1) >= args.length) { - Utils.fatal("Option \"--image\" requires parameter!"); - } - - output = args[i+1]; - i += 2; - - } else { - Utils.fatal("Unknown option " + args[i]); - } - } - - try { - Topography t = new Topography(width, height, topographyFile); - Grid g = new Grid(t, blockWidth, blockHeight); - - Color c = new Color(128, 128, 128, 128); - - TopographyCanvas tc = new TopographyCanvas(t, g); - - tc.addLayer("BLOCKS"); - tc.addLayer("LINES"); - - tc.draw("LINES", new Line(new Coordinate(0, 0), new Coordinate(0, g.height)), c, 7.0f); - tc.draw("LINES", new Line(new Coordinate(0, 0), new Coordinate(g.width, 0)), c, 7.0f); - - tc.draw("LINES", new Line(new Coordinate(0, g.height), new Coordinate(g.width, g.height)), c, 7.0f); - tc.draw("LINES", new Line(new Coordinate(g.width, 0), new Coordinate(g.width, g.height)), c, 7.0f); - - for (int w=1;w= args.length) { + Utils.fatal("Option \"--blocks\" requires width and height parameters!"); + } + + blockWidth = Utils.parseInt("block_width", args[i + 1], 1); + blockHeight = Utils.parseInt("block_height", args[i + 2], 1); + i += 3; + + } else if (args[i].equals("--showWork")) { + showWork = true; + i++; + + } else if (args[i].equals("--image")) { + + if ((i + 1) >= args.length) { + Utils.fatal("Option \"--image\" requires parameter!"); + } + + output = args[i + 1]; + i += 2; + + } else { + Utils.fatal("Unknown option " + args[i]); + } + } + + try { + Topography t = new Topography(width, height, topographyFile); + + int gridWidth = t.width / blockWidth; + int gridHeight = t.height / blockHeight; + + Neighbours n = new Neighbours(t, gridWidth, gridHeight, blockWidth, blockHeight, + Neighbours.CYCLIC, Neighbours.TRIPOLE); + + Grid g = new Grid(gridWidth, gridHeight, blockWidth, blockHeight, n); + + //Color c = new Color(128, 128, 128, 128); + + Color c = Color.DARK_GRAY; + + TopographyCanvas tc = new TopographyCanvas(t, g); + + tc.addLayer("BLOCKS"); + tc.addLayer("LINES"); + + tc.draw("LINES", new Line(new Coordinate(0, 0), new Coordinate(0, g.height)), c, 7.0f); + tc.draw("LINES", new Line(new Coordinate(0, 0), new Coordinate(g.width, 0)), c, 7.0f); + + tc.draw("LINES", new Line(new Coordinate(0, g.height), new Coordinate(g.width, g.height)), c, 7.0f); + tc.draw("LINES", new Line(new Coordinate(g.width, 0), new Coordinate(g.width, g.height)), c, 7.0f); + + for (int w = 1; w < g.width; w++) { + tc.draw("LINES", new Line(new Coordinate(w, 0), new Coordinate(w, g.height)), c, 7.0f); + } + + for (int h = 1; h < g.height; h++) { + tc.draw("LINES", new Line(new Coordinate(0, h), new Coordinate(g.width, h)), c, 7.0f); + } + + if (showWork) { + for (int y = 0; y < g.height; y++) { + for (int x = 0; x < g.width; x++) { + + Block b = g.get(x, y); + + if (!b.ocean) { + tc.fillBlock("BLOCKS", x, y, Color.GRAY); + } else { + tc.fillBlock("BLOCKS", x, y, Color.WHITE); + } + } + } + } + + if (output == null) { + JFrame frame = new JFrame("Topograpy"); + frame.setSize(1000, 667); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().add(tc); + frame.setVisible(true); + tc.repaint(); + } else { + tc.save(output); + } + + } catch (Exception e) { + Utils.fatal("Failed to run TopographyViewer ", e); + } + } } diff --git a/src/nl/esciencecenter/esalsa/tools/Utils.java b/src/nl/esciencecenter/esalsa/tools/Utils.java index 2dbfac8..a5a79a2 100644 --- a/src/nl/esciencecenter/esalsa/tools/Utils.java +++ b/src/nl/esciencecenter/esalsa/tools/Utils.java @@ -16,108 +16,120 @@ package nl.esciencecenter.esalsa.tools; /** - * Utils is a container class for various static methods used in the applications in this package. - * + * Utils is a container class for various static methods used in the applications in this package. + * * @author Jason Maassen * @version 1.0 * @since 1.0 */ public class Utils { - - /** - * Check if enough parameters are available for the current option. - * If not, an error is printed and the application is terminated. - * - * @param option the current command line option. - * @param parameters the number of parameters required. - * @param index the current index in the command line parameter list. - * @param length the length of the command line parameter list. - */ - public static void checkOptions(String option, int parameters, int index, int length) { - if (index+parameters >= length) { - fatal("Missing arguments for option " + option + " (required parameters " + parameters + ")"); - } - } - - /** - * Print an error and terminate the application. - * - * @param error the error to print. - */ - public static void fatal(String error) { - System.err.println(error); - System.exit(1); - } - - /** - * Print an error and terminate the application. - * - * @param e Exception that describes the fatal fault. - */ - public static void fatal(Exception e) { - System.err.println(e.getLocalizedMessage()); - e.printStackTrace(System.err); - System.exit(1); - } - /** - * Print an error and terminate the application. - * - * @param e Exception that describes the fatal fault. - */ - public static void fatal(String message, Exception e) { - System.err.println(message + " " + e.getLocalizedMessage()); - e.printStackTrace(System.err); - System.exit(1); - } - - /** - * Parse a string containing an integer and ensure its value is at least {@code minValue}. - * If the string cannot be parsed, or the integer is smaller than minValue, an error is printed - * and the application is terminated. - * - * @param option the current command line option. - * @param toParse the string to parse - * @param minValue the required minimal value for the integer. - * @return the int value in the option string. - */ - public static int parseInt(String option, String toParse, int minValue) { - - int result = -1; - - try { - result = Integer.parseInt(toParse); - } catch (Exception e) { - fatal("Failed to read argument for option " + option + " (parameters " + toParse + " is not a number)"); - } - - if (result < minValue) { - fatal("Argument for option " + option + " must have a value of at least " + minValue + " (got " + result + ")"); - } - - return result; - } - - /** - * Parse a string containing an integer and ensure its value is at least minValue and at most - * maxValue. - * If the string cannot be parsed, or the integer is smaller than minValue or larger than maxValue, an error is printed - * and the application is terminated. - * - * @param option the current command line option. - * @param toParse the string to parse - * @param minValue the minimum value for the integer. - * @param maxValue the maximum value for the integer. - * @return the int value in the option string. - */ - public static int parseInt(String option, String toParse, int minValue, int maxValue) { - - int result = parseInt(option, toParse, minValue); - - if (result > maxValue) { - fatal("Argument for option " + option + " must have a value of at most " + maxValue + " (got " + result + ")"); - } - - return result; - } + /** + * Check if enough parameters are available for the current option. If not, an error is printed and the application is + * terminated. + * + * @param option + * the current command line option. + * @param parameters + * the number of parameters required. + * @param index + * the current index in the command line parameter list. + * @param length + * the length of the command line parameter list. + */ + public static void checkOptions(String option, int parameters, int index, int length) { + if (index + parameters >= length) { + fatal("Missing arguments for option " + option + " (required parameters " + parameters + ")"); + } + } + + /** + * Print an error and terminate the application. + * + * @param error + * the error to print. + */ + public static void fatal(String error) { + System.err.println(error); + System.exit(1); + } + + /** + * Print an error and terminate the application. + * + * @param e + * Exception that describes the fatal fault. + */ + public static void fatal(Exception e) { + System.err.println(e.getLocalizedMessage()); + e.printStackTrace(System.err); + System.exit(1); + } + + /** + * Print an error and terminate the application. + * + * @param e + * Exception that describes the fatal fault. + */ + public static void fatal(String message, Exception e) { + System.err.println(message + " " + e.getLocalizedMessage()); + e.printStackTrace(System.err); + System.exit(1); + } + + /** + * Parse a string containing an integer and ensure its value is at least {@code minValue}. If the string cannot be parsed, or + * the integer is smaller than minValue, an error is printed and the application is terminated. + * + * @param option + * the current command line option. + * @param toParse + * the string to parse + * @param minValue + * the required minimal value for the integer. + * @return the int value in the option string. + */ + public static int parseInt(String option, String toParse, int minValue) { + + int result = -1; + + try { + result = Integer.parseInt(toParse); + } catch (Exception e) { + fatal("Failed to read argument for option " + option + " (parameters " + toParse + " is not a number)"); + } + + if (result < minValue) { + fatal("Argument for option " + option + " must have a value of at least " + minValue + " (got " + result + ")"); + } + + return result; + } + + /** + * Parse a string containing an integer and ensure its value is at least minValue and at most + * maxValue. If the string cannot be parsed, or the integer is smaller than minValue or larger than maxValue, an + * error is printed and the application is terminated. + * + * @param option + * the current command line option. + * @param toParse + * the string to parse + * @param minValue + * the minimum value for the integer. + * @param maxValue + * the maximum value for the integer. + * @return the int value in the option string. + */ + public static int parseInt(String option, String toParse, int minValue, int maxValue) { + + int result = parseInt(option, toParse, minValue); + + if (result > maxValue) { + fatal("Argument for option " + option + " must have a value of at most " + maxValue + " (got " + result + ")"); + } + + return result; + } } diff --git a/src/nl/esciencecenter/esalsa/util/Block.java b/src/nl/esciencecenter/esalsa/util/Block.java index 073b5bd..00f4751 100644 --- a/src/nl/esciencecenter/esalsa/util/Block.java +++ b/src/nl/esciencecenter/esalsa/util/Block.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package nl.esciencecenter.esalsa.util; /** - * Block represents a block in a POP work distribution. + * Block represents a block in a POP work distribution. * - * It consists of an immutable Coordinate, and an int mark that can be used to label the block with an - * arbitrary integer value. + * It consists of an immutable Coordinate, and an int mark that can be used to label the block with an + * arbitrary integer value. * * @author Jason Maassen * @version 1.0 @@ -28,68 +27,313 @@ * @see Coordinate */ public class Block { + + public final int blockID; + + /** The Coordinate of the block. */ + public final Coordinate coordinate; + + /** Is this an ocean block ? */ + public final boolean ocean; + + /** All possible neighbours */ + public final int neighbourNorth; + public final int neighbourNorthWest; + public final int neighbourWest; + public final int neighbourSouthWest; + public final int neighbourSouth; + public final int neighbourSouthEast; + public final int neighbourEast; + public final int neighbourNorthEast; + + public final int messageSizeNorth; + public final int messageSizeNorthWest; + public final int messageSizeWest; + public final int messageSizeSouthWest; + public final int messageSizeSouth; + public final int messageSizeSouthEast; + public final int messageSizeEast; + public final int messageSizeNorthEast; + + /** Mark use to label the block */ + private int mark = 0; + + /** + * Constructor to create a new Block. + * + * @param coordinate + * coordinate of the block. + */ + public Block(int blockID, Coordinate coordinate, Neighbours n) { + this.blockID = blockID; + this.coordinate = coordinate; + this.ocean = n.isOcean(coordinate); + + this.neighbourNorth = n.getNeighbourNorth(coordinate); + this.neighbourNorthWest = n.getNeighbourNorthWest(coordinate); + this.neighbourWest = n.getNeighbourWest(coordinate); + this.neighbourSouthWest = n.getNeighbourSouthWest(coordinate); + this.neighbourSouth = n.getNeighbourSouth(coordinate); + this.neighbourSouthEast = n.getNeighbourSouthEast(coordinate); + this.neighbourEast = n.getNeighbourEast(coordinate); + this.neighbourNorthEast = n.getNeighbourNorthEast(coordinate); + + if (ocean) { + this.messageSizeNorth = n.getMessageSizeNorth(coordinate); + this.messageSizeNorthWest = n.getMessageSizeNorthWest(coordinate); + this.messageSizeWest = n.getMessageSizeWest(coordinate); + this.messageSizeSouthWest = n.getMessageSizeSouthWest(coordinate); + this.messageSizeSouth = n.getMessageSizeSouth(coordinate); + this.messageSizeSouthEast = n.getMessageSizeSouthEast(coordinate); + this.messageSizeEast = n.getMessageSizeEast(coordinate); + this.messageSizeNorthEast = n.getMessageSizeNorthEast(coordinate); + } else { + this.messageSizeNorth = 0; + this.messageSizeNorthWest = 0; + this.messageSizeWest = 0; + this.messageSizeSouthWest = 0; + this.messageSizeSouth = 0; + this.messageSizeSouthEast = 0; + this.messageSizeEast = 0; + this.messageSizeNorthEast = 0; + } + } + + public Block(Block original, Coordinate newCoordinate) { + this.blockID = original.blockID; + this.coordinate = newCoordinate; + + this.ocean = original.ocean; + this.mark = original.mark; + + this.neighbourNorth = original.neighbourNorth; + this.neighbourNorthWest = original.neighbourNorthWest; + this.neighbourWest = original.neighbourWest; + this.neighbourSouthWest = original.neighbourSouthWest; + this.neighbourSouth = original.neighbourSouth; + this.neighbourSouthEast = original.neighbourSouthEast; + this.neighbourEast = original.neighbourEast; + this.neighbourNorthEast = original.neighbourNorthEast; + + this.messageSizeNorth = original.messageSizeNorth; + this.messageSizeNorthWest = original.messageSizeNorthWest; + this.messageSizeWest = original.messageSizeWest; + this.messageSizeSouthWest = original.messageSizeSouthWest; + this.messageSizeSouth = original.messageSizeSouth; + this.messageSizeSouthEast = original.messageSizeSouthEast; + this.messageSizeEast = original.messageSizeEast; + this.messageSizeNorthEast = original.messageSizeNorthEast; + } + + public Block(Coordinate coordinate) { + + this.blockID = -1; + this.coordinate = coordinate; + this.ocean = false; + + this.neighbourNorth = 0; + this.neighbourNorthWest = 0; + this.neighbourWest = 0; + this.neighbourSouthWest = 0; + this.neighbourSouth = 0; + this.neighbourSouthEast = 0; + this.neighbourEast = 0; + this.neighbourNorthEast = 0; + + this.messageSizeNorth = 0; + this.messageSizeNorthWest = 0; + this.messageSizeWest = 0; + this.messageSizeSouthWest = 0; + this.messageSizeSouth = 0; + this.messageSizeSouthEast = 0; + this.messageSizeEast = 0; + this.messageSizeNorthEast = 0; + } + /** + * Constructor to move a block. + * + * @param coordinate + * coordinate of the block. + */ +// public void move(Coordinate coordinate) { +// +// if (this.coordinate.equals(coordinate)) { +// // Move to current position doe nothing. +// return; +// } +// +// if (!moved) { +// // First move saves original postion. +// originalCoordinate = this.coordinate; +// this.coordinate = coordinate; +// moved = true; +// } else { +// if (originalCoordinate.equals(coordinate)) { +// // Move back to original position clears move. +// originalCoordinate = null; +// this.coordinate= coordinate; +// moved = false; +// } else { +// // Additional move loses intermediate position +// this.coordinate= coordinate; +// } +// } +// } +// +// public boolean hasMoved() { +// return moved; +// } +// +// public Coordinate getCoordinate() { +// return coordinate; +// } +// +// public Coordinate getOriginalCoordinate() { +// +// if (moved) { +// return originalCoordinate; +// } +// +// return coordinate; +// } + + /** + * Mark the block with the given value. + * + * @param value + * the value to mark the block with. + */ + public void setMark(int value) { + mark = value; + } + + /** + * Add the given value to the current mark. + * + * @param value + * value to add to the current mark. + */ + public void addToMark(int value) { + mark += value; + } + + /** + * Retrieve the value of the current mark. + * + * @return the current value of the mark. + */ + public int getMark() { + return mark; + } + + public int[][] getNeighbours() { + + int[][] result = new int[3][3]; + + result[0][0] = neighbourNorthWest; + result[0][1] = neighbourNorth; + result[0][2] = neighbourNorthEast; + + result[1][0] = neighbourWest; + result[1][1] = 0; + result[1][2] = neighbourEast; + + result[2][0] = neighbourSouthWest; + result[2][1] = neighbourSouth; + result[2][2] = neighbourSouthEast; + + return result; + } + + public int[][] getCommunication() { + + int[][] result = new int[3][3]; + + result[0][0] = messageSizeNorthWest; + result[0][1] = messageSizeNorth; + result[0][2] = messageSizeNorthEast; + + result[1][0] = messageSizeWest; + result[1][1] = 0; + result[1][2] = messageSizeEast; + + result[2][0] = messageSizeSouthWest; + result[2][1] = messageSizeSouth; + result[2][2] = messageSizeSouthEast; + + return result; + } + +// +// public int getCommunication(int dx, int dy) { +// +// switch (dx) { +// case -1: { +// switch (dy) { +// case -1: +// return messageSizeSouthWest; +// case 0: +// return messageSizeWest; +// case 1: +// return messageSizeNorthWest; +// default: +// return 0; +// } +// } +// +// case 0: { +// switch (dy) { +// case -1: +// return messageSizeSouth; +// case 0: +// return 0; +// case 1: +// return messageSizeNorth; +// default: +// return 0; +// } +// } +// +// case 1: { +// switch (dy) { +// case -1: +// return messageSizeSouthEast; +// case 0: +// return messageSizeEast; +// case 1: +// return messageSizeNorthEast; +// default: +// return 0; +// } +// } +// +// default: +// return 0; +// } +// } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + coordinate.x; + result = prime * result + coordinate.y; + return result; + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) + return true; + + if (obj == null || getClass() != obj.getClass()) + return false; - /** The Coordinate of the block. */ - public final Coordinate coordinate; - - /** Mark use to label the block */ - private int mark = 0; - - /** - * Constructor to create a new Block. - * - * @param coordinate coordinate of the block. - */ - public Block(Coordinate coordinate) { - this.coordinate = coordinate; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + coordinate.x; - result = prime * result + coordinate.y; - return result; - } - - @Override - public boolean equals(Object obj) { - - if (this == obj) - return true; - - if (obj == null || getClass() != obj.getClass()) - return false; - - Block other = (Block) obj; - return coordinate.equals(other.coordinate); - } - - /** - * Mark the block with the given value. - * - * @param value the value to mark the block with. - */ - public void setMark(int value) { - mark = value; - } - - /** - * Add the given value to the current mark. - * - * @param value value to add to the current mark. - */ - public void addToMark(int value) { - mark += value; - } - - /** - * Retrieve the value of the current mark. - * - * @return the current value of the mark. - */ - public int getMark() { - return mark; - } + return blockID == ((Block) obj).blockID; + } + + public String toString() { + return "Block(" + coordinate + ", " + ocean + ")"; + } } diff --git a/src/nl/esciencecenter/esalsa/util/BlockIterator.java b/src/nl/esciencecenter/esalsa/util/BlockIterator.java index 3322ee8..fa58401 100644 --- a/src/nl/esciencecenter/esalsa/util/BlockIterator.java +++ b/src/nl/esciencecenter/esalsa/util/BlockIterator.java @@ -20,7 +20,7 @@ import java.util.NoSuchElementException; /** - * An iterator for Blocks. + * An iterator for Blocks. * * @author Jason Maassen * @version 1.0 @@ -30,52 +30,53 @@ */ public class BlockIterator implements Iterator { - /** The blocks to iterate over. */ - private final Block [] blocks; - - /** The index of the next block to return. */ - private int index; - - /** - * Creates a BlockIterator that will iterate over the {@link Block}s in provided array. - * - * @param blocks an array of blocks to iterate over. - */ - public BlockIterator(Block [] blocks) { - this.blocks = blocks; - this.index = 0; - } - - @Override - public boolean hasNext() { - - if (blocks == null || index >= blocks.length) { - return false; - } - - while (blocks[index] == null) { - index++; + /** The blocks to iterate over. */ + private final Block[] blocks; - if (index >= blocks.length) { - return false; - } - } - - return true; - } + /** The index of the next block to return. */ + private int index; - @Override - public Block next() { - - if (hasNext()) { - return blocks[index++]; - } - - throw new NoSuchElementException("ElementIterator ran out of elements!"); - } + /** + * Creates a BlockIterator that will iterate over the {@link Block}s in provided array. + * + * @param blocks + * an array of blocks to iterate over. + */ + public BlockIterator(Block[] blocks) { + this.blocks = blocks; + this.index = 0; + } - @Override - public void remove() { - throw new UnsupportedOperationException("Remove not supported!"); - } + @Override + public boolean hasNext() { + + if (blocks == null || index >= blocks.length) { + return false; + } + + while (blocks[index] == null) { + index++; + + if (index >= blocks.length) { + return false; + } + } + + return true; + } + + @Override + public Block next() { + + if (hasNext()) { + return blocks[index++]; + } + + throw new NoSuchElementException("ElementIterator ran out of elements!"); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported!"); + } } \ No newline at end of file diff --git a/src/nl/esciencecenter/esalsa/util/Coordinate.java b/src/nl/esciencecenter/esalsa/util/Coordinate.java index 1249a83..e23abc4 100644 --- a/src/nl/esciencecenter/esalsa/util/Coordinate.java +++ b/src/nl/esciencecenter/esalsa/util/Coordinate.java @@ -18,7 +18,7 @@ /** * Coordinate represents a location in (x,y) coordinate space, specified in integer precision. - * + * * @author Jason Maassen * @version 1.0 * @since 1.0 @@ -26,55 +26,59 @@ */ public class Coordinate { - /** The x coordinate. */ - public final int x; - - /** The y coordinate. */ - public final int y; - - /** - * Create a new Coordinate representing the specified (x,y) location. - * - * @param x the x coordinate. - * @param y the y coordinate. - */ - public Coordinate(int x, int y) { - this.x = x; - this.y = y; - } + /** The x coordinate. */ + public final int x; + + /** The y coordinate. */ + public final int y; + + /** + * Create a new Coordinate representing the specified (x,y) location. + * + * @param x + * the x coordinate. + * @param y + * the y coordinate. + */ + public Coordinate(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Create a new Coordinate by adding an offset to the current coordinate. + * + * @param dx + * the x offset to add. + * @param dy + * the y offset to add. + * @return a new Coordinate containing the current location plus the offsets. + */ + public Coordinate offset(int dx, int dy) { + return new Coordinate(x + dx, y + dy); + } + + @Override + public int hashCode() { + return x + y * 31; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } - /** - * Create a new Coordinate by adding an offset to the current coordinate. - * - * @param dx the x offset to add. - * @param dy the y offset to add. - * @return a new Coordinate containing the current location plus the offsets. - */ - public Coordinate offset(int dx, int dy) { - return new Coordinate(x+dx, y+dy); - } - - @Override - public int hashCode() { - return x + y * 31; - } + if (obj == null || getClass() != obj.getClass()) { + return false; + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - Coordinate other = (Coordinate) obj; - return x == other.x && y == other.y; - } + Coordinate other = (Coordinate) obj; + return x == other.x && y == other.y; + } - @Override - public String toString() { - return "(" + x + ", " + y + ")"; - } + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } } diff --git a/src/nl/esciencecenter/esalsa/util/CoordinateIterator.java b/src/nl/esciencecenter/esalsa/util/CoordinateIterator.java index e739ad1..25a8786 100644 --- a/src/nl/esciencecenter/esalsa/util/CoordinateIterator.java +++ b/src/nl/esciencecenter/esalsa/util/CoordinateIterator.java @@ -20,7 +20,7 @@ import java.util.NoSuchElementException; /** - * An iterator for Coordinates. + * An iterator for Coordinates. * * @author Jason Maassen * @version 1.0 @@ -30,39 +30,40 @@ */ public class CoordinateIterator implements Iterator { - /** The Coordinates to iterate over. */ - private final Coordinate [] coordinates; - - /** The index of the next Coordinate to return. */ - private int index; - - /** - * Creates a CoordinateIterator that will iterate over the {@link Coordinate}s in the provided array. - * - * @param coordinates an array of Coordinates to iterate over. - */ - public CoordinateIterator(Coordinate [] coordinates) { - this.coordinates = coordinates; - this.index = 0; - } - - @Override - public boolean hasNext() { - return (index < coordinates.length); - } + /** The Coordinates to iterate over. */ + private final Coordinate[] coordinates; - @Override - public Coordinate next() { - - if (index < coordinates.length) { - return coordinates[index++]; - } + /** The index of the next Coordinate to return. */ + private int index; - throw new NoSuchElementException("Iterator ran out of elements!"); - } + /** + * Creates a CoordinateIterator that will iterate over the {@link Coordinate}s in the provided array. + * + * @param coordinates + * an array of Coordinates to iterate over. + */ + public CoordinateIterator(Coordinate[] coordinates) { + this.coordinates = coordinates; + this.index = 0; + } - @Override - public void remove() { - throw new UnsupportedOperationException("Remove not supported!"); - } + @Override + public boolean hasNext() { + return (index < coordinates.length); + } + + @Override + public Coordinate next() { + + if (index < coordinates.length) { + return coordinates[index++]; + } + + throw new NoSuchElementException("Iterator ran out of elements!"); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported!"); + } } \ No newline at end of file diff --git a/src/nl/esciencecenter/esalsa/util/Distribution.java b/src/nl/esciencecenter/esalsa/util/Distribution.java index f2a6601..9c9ebe7 100644 --- a/src/nl/esciencecenter/esalsa/util/Distribution.java +++ b/src/nl/esciencecenter/esalsa/util/Distribution.java @@ -26,345 +26,373 @@ import java.util.ArrayList; import java.util.Collection; - /** - * Distribution represents a POP block distribution. + * Distribution represents a POP block distribution. * * @author Jason Maassen - * @version 1.0 + * @version 1.0 * @since 1.0 * */ public class Distribution { - /** The width of the topography for which this distribution was generated. */ - public final int topographyWidth; - - /** The height of the topography for which this distribution was generated. */ - public final int topographyHeight; - - /** The width of the blocks for which this distribution was generated. */ - public final int blockWidth; - - /** The height of the topography for which this distribution was generated. */ - public final int blockHeight; - - /** The number of clusters for which this distribution was generated. */ - public final int clusters; - - /** The number of nodes per cluster for which this distribution was generated. */ - public final int nodesPerCluster; - - /** The number of cores per node for which this distribution was generated. */ - public final int coresPerNode; - - /** The minimal number of blocks per core in this distribution */ - public final int minBlocksPerCore; - - /** The maximal number of blocks per core in this distribution */ - public final int maxBlocksPerCore; - - /** The total number of block in this distribution */ - public final int totalBlocks; - - /** The distribution itself. Position i contains the core on which the block should be placed */ - private final int [] distribution; - - /** - * Create a new distribution. - * - * @param topographyWidth the width of the topography used for the distribution. - * @param topographyHeight the height of the topography used for the distribution. - * @param blockWidth the width of the blocks used for the distribution. - * @param blockHeight the height of the blocks use for the distribution. - * @param clusters the number of clusters used for the distribution. - * @param nodesPerCluster the number of nodes per clusters used for the distribution. - * @param coresPerNode the number of cores per node used for the distribution. - * @param minBlocksPerCore the minimal number of blocks per core in the distribution. - * @param maxBlocksPerCore the maximal number of blocks per core in the distribution. - * @param totalBlocks the total number of blocks in the distribution. - * @param distribution the distribution to store. - */ - public Distribution(int topographyWidth, int topographyHeight, - int blockWidth, int blockHeight, - int clusters, int nodesPerCluster, int coresPerNode, - int minBlocksPerCore, int maxBlocksPerCore, - int totalBlocks, int[] distribution) { - - this.topographyWidth = topographyWidth; - this.topographyHeight = topographyHeight; - this.blockWidth = blockWidth; - this.blockHeight = blockHeight; - this.clusters = clusters; - this.nodesPerCluster = nodesPerCluster; - this.coresPerNode = coresPerNode; - this.minBlocksPerCore = minBlocksPerCore; - this.maxBlocksPerCore = maxBlocksPerCore; - this.totalBlocks = totalBlocks; - this.distribution = distribution; - } - - /** - * Create a new Distribution by reading it contents for a file. - * - * @param filename the file to read. - * @throws IOException if the file could not be read. - * @throws IOException if the file could not be read. - */ - @SuppressWarnings("resource") - public Distribution(String filename) throws Exception { - - DataInputStream in = null; - - try { - in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename))); - - topographyWidth = in.readInt(); - topographyHeight = in.readInt(); - - if (topographyWidth <= 0 || topographyHeight <= 0) { - throw new Exception("Illegal topography dimensions " + topographyWidth + "x" + topographyHeight); - } - - blockWidth = in.readInt(); - blockHeight = in.readInt(); - - if (blockWidth <= 0 || blockHeight <= 0) { - throw new Exception("Illegal block dimensions " + blockWidth + "x" + blockHeight); - } - - if (topographyWidth % blockWidth != 0 || topographyHeight % blockHeight != 0) { - throw new Exception("Blocks do not perfectly fit topography (" + topographyWidth + "x" + topographyHeight + " " - + blockWidth + "x" + blockHeight +")"); - } - - clusters = in.readInt(); - - if (clusters <= 0) { - throw new Exception("Illegal cluster count " + clusters); - } - - nodesPerCluster = in.readInt(); - - if (nodesPerCluster <= 0) { - throw new Exception("Illegal nodesPerCluster count " + nodesPerCluster); - } - - coresPerNode = in.readInt(); - - if (coresPerNode <= 0) { - throw new Exception("Illegal coresPerNode count " + nodesPerCluster); - } - - minBlocksPerCore = in.readInt(); - maxBlocksPerCore = in.readInt(); - - if (minBlocksPerCore < 0) { - throw new Exception("Illegal minBlocksPerCore count " + minBlocksPerCore); - } - - if (maxBlocksPerCore < 0) { - throw new Exception("Illegal maxBlocksPerCore count " + maxBlocksPerCore); - } - - if (maxBlocksPerCore < minBlocksPerCore) { - throw new Exception("maxBlocksPerCore is smaller than minBlockPerCore " + maxBlocksPerCore + " < " - + minBlocksPerCore); - } - - totalBlocks = in.readInt(); - - int expectedBlocks = (topographyWidth / blockWidth) * (topographyHeight / blockHeight); - - if (totalBlocks != expectedBlocks) { - throw new Exception("totalblock is inconsistent with topography and blocksize! " + totalBlocks + " != " - + expectedBlocks); - } - - distribution = new int[totalBlocks]; - - for (int i=0;i (clusters * nodesPerCluster * coresPerNode)) { - throw new Exception("Inconsistent block number at position " + i + ": " + distribution[i]); - } - } - } finally { - try { - in.close(); - } catch (Exception e) { - // ignored - } - } - } - - /** - * Returns the number of the core that owns the block at the given index. - * - * @param index the index of the block. - * @return the owner of the block. - */ - public int getOwner(int index) { - return distribution[index]; - } - - /** - * Writes a block distribution to disk. - * - * @param filename the filename of the file to write the distribution to. - * @throws IOException if an error occurred while writing to the file. - */ - public void write(String filename) throws IOException { - - DataOutputStream out = null; - - try { - out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename))); - - out.writeInt(topographyWidth); - out.writeInt(topographyHeight); - - out.writeInt(blockWidth); - out.writeInt(blockHeight); - - out.writeInt(clusters); - out.writeInt(nodesPerCluster); - out.writeInt(coresPerNode); - - out.writeInt(minBlocksPerCore); - out.writeInt(maxBlocksPerCore); - - out.writeInt(totalBlocks); - - for (int i=0;i - * The following layers are created (top-to-bottom): - *

- *

    - *
  • "CLUSTERS" containing one set for each cluster. - *
  • "NODES" containing one set for each node. - *
  • "CORES" containing one set for each core. - *
  • "BLOCKS" containing one set for each block. - *
  • "ALL" containing a single set of all blocks. - *
- *

- * Each "CORES", "NODES", "CLUSTERS" set contains the blocks as assigned in the - * distribution. In addition, the subset relations are also returned. For example, a "NODES" set will contain - * {@link #coresPerNode} "CORES" subsets. - * - * @return the block distribution represented as five layers of sets. - * @see Layer - * @see Set - * @see Block - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Layers toLayers() { - - // Start by computing some constants. - int blocksPerRow = topographyWidth / blockWidth; - int totalNodes = clusters * nodesPerCluster; - int totalCores = totalNodes * coresPerNode; - - // Next, create all layers. - Layers layers = new Layers(); - - Layer clusterLayer = new Layer("CLUSTERS"); - Layer nodesLayer = new Layer("NODES"); - Layer coresLayer = new Layer("CORES"); - Layer blockLayer = new Layer("BLOCKS"); - Layer combinedLayer = new Layer("ALL"); - - layers.add(combinedLayer); - layers.add(blockLayer); - layers.add(coresLayer); - layers.add(nodesLayer); - layers.add(clusterLayer); - - // Then create all non-zero blocks in the distribution. - Collection [] tmp = new Collection[totalCores]; - ArrayList allBlocks = new ArrayList(); - - // Traverse the distribution array. - for (int i=0;i= 0) { - ArrayList current = (ArrayList) tmp[coreNumber]; - - if (current == null) { - current = new ArrayList(); - tmp[coreNumber] = current; - } - - // Create the block, and add to the block list of the core, the blockLayer, and the allBlocks list. - Block b = new Block(new Coordinate(i % blocksPerRow, i / blocksPerRow)); - - current.add(b); - blockLayer.add(new Set(b, i)); - allBlocks.add(b); - } - } - - // Create a layer containing one set with all blocks. - combinedLayer.add(new Set(allBlocks, 0)); - - // Create a layers containing one set per core. - for (int i=0;i coresOfNode = new ArrayList(); - ArrayList blocksOfNode = new ArrayList(); - - for (int j=0;j nodesOfCluster = new ArrayList(); - ArrayList blocksOfCluster = new ArrayList(); - - for (int j=0;ji contains the core on which the block should be placed */ + private final int[] distribution; + + /** + * Create a new distribution. + * + * @param topographyWidth + * the width of the topography used for the distribution. + * @param topographyHeight + * the height of the topography used for the distribution. + * @param blockWidth + * the width of the blocks used for the distribution. + * @param blockHeight + * the height of the blocks use for the distribution. + * @param clusters + * the number of clusters used for the distribution. + * @param nodesPerCluster + * the number of nodes per clusters used for the distribution. + * @param coresPerNode + * the number of cores per node used for the distribution. + * @param minBlocksPerCore + * the minimal number of blocks per core in the distribution. + * @param maxBlocksPerCore + * the maximal number of blocks per core in the distribution. + * @param totalBlocks + * the total number of blocks in the distribution. + * @param distribution + * the distribution to store. + */ + public Distribution(int topographyWidth, int topographyHeight, int blockWidth, int blockHeight, int clusters, + int nodesPerCluster, int coresPerNode, int minBlocksPerCore, int maxBlocksPerCore, int totalBlocks, + int[] distribution) { + + this.topographyWidth = topographyWidth; + this.topographyHeight = topographyHeight; + this.blockWidth = blockWidth; + this.blockHeight = blockHeight; + this.clusters = clusters; + this.nodesPerCluster = nodesPerCluster; + this.coresPerNode = coresPerNode; + this.minBlocksPerCore = minBlocksPerCore; + this.maxBlocksPerCore = maxBlocksPerCore; + this.totalBlocks = totalBlocks; + this.distribution = distribution; + + for (int i=0;i (clusters*nodesPerCluster*coresPerNode)) { + throw new IllegalArgumentException("Inconsistent distribution! " + clusters + "*" + nodesPerCluster + "*" + + coresPerNode + " != " + tmp); + } + } + + } + + /** + * Create a new Distribution by reading it contents for a file. + * + * @param filename + * the file to read. + * @throws IOException + * if the file could not be read. + * @throws IOException + * if the file could not be read. + */ + @SuppressWarnings("resource") + public Distribution(String filename) throws Exception { + + DataInputStream in = null; + + try { + in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename))); + + topographyWidth = in.readInt(); + topographyHeight = in.readInt(); + + if (topographyWidth <= 0 || topographyHeight <= 0) { + throw new Exception("Illegal topography dimensions " + topographyWidth + "x" + topographyHeight); + } + + blockWidth = in.readInt(); + blockHeight = in.readInt(); + + if (blockWidth <= 0 || blockHeight <= 0) { + throw new Exception("Illegal block dimensions " + blockWidth + "x" + blockHeight); + } + + if (topographyWidth % blockWidth != 0 || topographyHeight % blockHeight != 0) { + throw new Exception("Blocks do not perfectly fit topography (" + topographyWidth + "x" + topographyHeight + " " + + blockWidth + "x" + blockHeight + ")"); + } + + clusters = in.readInt(); + + if (clusters <= 0) { + throw new Exception("Illegal cluster count " + clusters); + } + + nodesPerCluster = in.readInt(); + + if (nodesPerCluster <= 0) { + throw new Exception("Illegal nodesPerCluster count " + nodesPerCluster); + } + + coresPerNode = in.readInt(); + + if (coresPerNode <= 0) { + throw new Exception("Illegal coresPerNode count " + nodesPerCluster); + } + + minBlocksPerCore = in.readInt(); + maxBlocksPerCore = in.readInt(); + + if (minBlocksPerCore < 0) { + throw new Exception("Illegal minBlocksPerCore count " + minBlocksPerCore); + } + + if (maxBlocksPerCore < 0) { + throw new Exception("Illegal maxBlocksPerCore count " + maxBlocksPerCore); + } + + if (maxBlocksPerCore < minBlocksPerCore) { + throw new Exception("maxBlocksPerCore is smaller than minBlockPerCore " + maxBlocksPerCore + " < " + + minBlocksPerCore); + } + + totalBlocks = in.readInt(); + + int expectedBlocks = (topographyWidth / blockWidth) * (topographyHeight / blockHeight); + + if (totalBlocks != expectedBlocks) { + throw new Exception("totalblock is inconsistent with topography and blocksize! " + totalBlocks + " != " + + expectedBlocks); + } + + distribution = new int[totalBlocks]; + + for (int i = 0; i < totalBlocks; i++) { + distribution[i] = in.readInt(); + + if (distribution[i] < 0 || distribution[i] > (clusters * nodesPerCluster * coresPerNode)) { + throw new Exception("Inconsistent block number at position " + i + ": " + distribution[i]); + } + } + } finally { + try { + in.close(); + } catch (Exception e) { + // ignored + } + } + } + + /** + * Returns the number of the core that owns the block at the given index. + * + * @param index + * the index of the block. + * @return the owner of the block. + */ + public int getOwner(int index) { + return distribution[index]; + } + + /** + * Writes a block distribution to disk. + * + * @param filename + * the filename of the file to write the distribution to. + * @throws IOException + * if an error occurred while writing to the file. + */ + public void write(String filename) throws IOException { + + DataOutputStream out = null; + + try { + out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename))); + + out.writeInt(topographyWidth); + out.writeInt(topographyHeight); + + out.writeInt(blockWidth); + out.writeInt(blockHeight); + + out.writeInt(clusters); + out.writeInt(nodesPerCluster); + out.writeInt(coresPerNode); + + out.writeInt(minBlocksPerCore); + out.writeInt(maxBlocksPerCore); + + out.writeInt(totalBlocks); + + for (int i = 0; i < totalBlocks; i++) { + out.writeInt(distribution[i]); + } + } finally { + try { + out.close(); + } catch (Exception e) { + // ignored + } + } + } + + /** + * Convert the block distribution into a representation using five layers of sets. + *

+ * The following layers are created (top-to-bottom): + *

+ *

    + *
  • "CLUSTERS" containing one set for each cluster. + *
  • "NODES" containing one set for each node. + *
  • "CORES" containing one set for each core. + *
  • "BLOCKS" containing one set for each block. + *
  • "ALL" containing a single set of all blocks. + *
+ *

+ * Each "CORES", "NODES", "CLUSTERS" set contains the blocks as assigned in the + * distribution. In addition, the subset relations are also returned. For example, a "NODES" set will contain + * {@link #coresPerNode} "CORES" subsets. + * + * @return the block distribution represented as five layers of sets. + * @see Layer + * @see Set + * @see Block + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Layers toLayers(Grid grid) { + + // Start by computing some constants. + int blocksPerRow = topographyWidth / blockWidth; + int totalNodes = clusters * nodesPerCluster; + int totalCores = totalNodes * coresPerNode; + + System.out.println("blocksPerRow " + blocksPerRow); + System.out.println("totalNodes " + totalNodes); + System.out.println("totalCores " + totalCores); + + // Next, create all layers. + Layers layers = new Layers(); + + Layer clusterLayer = new Layer("CLUSTERS"); + Layer nodesLayer = new Layer("NODES"); + Layer coresLayer = new Layer("CORES"); + Layer blockLayer = new Layer("BLOCKS"); + Layer combinedLayer = new Layer("ALL"); + + layers.add(combinedLayer); + layers.add(blockLayer); + layers.add(coresLayer); + layers.add(nodesLayer); + layers.add(clusterLayer); + + // Then create all non-zero blocks in the distribution. + Collection[] tmp = new Collection[totalCores]; + ArrayList allBlocks = new ArrayList(); + + // Traverse the distribution array. + for (int i = 0; i < totalBlocks; i++) { + // Note: we subtract one here, since the distribution uses a Fortran friendly 1-based notation (0=unused, 1...N=used). + int coreNumber = distribution[i] - 1; + + // If a coreNumber is zero or higher, it represents a valid block + if (coreNumber >= 0) { + ArrayList current = (ArrayList) tmp[coreNumber]; + + if (current == null) { + current = new ArrayList(); + tmp[coreNumber] = current; + } + + // Create the block, and add to the block list of the core, the blockLayer, and the allBlocks list. + Block b = grid.get(i % blocksPerRow, i / blocksPerRow); + current.add(b); + blockLayer.add(new Set(b, i)); + allBlocks.add(b); + } + } + + // Create a layer containing one set with all blocks. + combinedLayer.add(new Set(allBlocks, 0)); + + // Create a layers containing one set per core. + for (int i = 0; i < totalCores; i++) { + coresLayer.add(new Set(tmp[i], i)); + } + + // Create a layers containing one set per node. + for (int i = 0; i < totalNodes; i++) { + + ArrayList coresOfNode = new ArrayList(); + ArrayList blocksOfNode = new ArrayList(); + + for (int j = 0; j < coresPerNode; j++) { + Set core = coresLayer.get(i * coresPerNode + j); + + coresOfNode.add(core); + core.getAll(blocksOfNode); + } + + Set node = new Set(blocksOfNode, i); + node.addSubSets(coresOfNode); + nodesLayer.add(node); + } + + // Create a layers containing one set per cluster. + for (int i = 0; i < clusters; i++) { + + ArrayList nodesOfCluster = new ArrayList(); + ArrayList blocksOfCluster = new ArrayList(); + + for (int j = 0; j < nodesPerCluster; j++) { + Set node = nodesLayer.get(i * nodesPerCluster + j); + + nodesOfCluster.add(node); + node.getAll(blocksOfCluster); + } + + Set cluster = new Set(blocksOfCluster, i); + cluster.addSubSets(nodesOfCluster); + clusterLayer.add(cluster); + } + + return layers; + } } diff --git a/src/nl/esciencecenter/esalsa/util/Grid.java b/src/nl/esciencecenter/esalsa/util/Grid.java index 4b1cc85..89a74ea 100644 --- a/src/nl/esciencecenter/esalsa/util/Grid.java +++ b/src/nl/esciencecenter/esalsa/util/Grid.java @@ -17,259 +17,428 @@ package nl.esciencecenter.esalsa.util; import java.util.ArrayList; -import java.util.Collection; import java.util.Iterator; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Grid represents an rectangular grid of Blocks. - * + * * @author Jason Maassen * @version 1.0 * @since 1.0 * @see Block - * + * */ public class Grid implements Iterable { - - /** A Logger used for debugging. */ - private static final Logger logger = LoggerFactory.getLogger(Grid.class); - - /** The width of the grid. */ - public final int width; - - /** The height of the grid. */ - public final int height; - - /** The width of a block in topography points. */ - public final int blockWidth; - - /** The height of a block in topography points. */ - public final int blockHeight; - - /** An array of length width*height to store blocks in this grid. */ - private final Block [] blocks; - - /** The number of active blocks in this grid. */ - private int count = 0; - - /** - * Create an empty grid of size width x height. - * - * @param width the width of the grid to create. - * @param height the height of the grid to create. - */ - public Grid(int width, int height, int blockWidth, int blockHeight) { - - this.width = width; - this.height = height; - this.blockWidth = blockWidth; - this.blockHeight = blockHeight; - - blocks = new Block[width*height]; - - if (logger.isDebugEnabled()) { - logger.debug("Created Grid " + width + "x" + height); - } - } - - /** - * Create grid by subdividing a Topography into blocks of size blockWidth x blockHeight points. - * - * Only Blocks containing at least one ocean point will be stored. As a result, after creation, - * some locations in the grid may not contain a block. - * - * @param topo the Topography to divide. - * @param blockWidth the width of a block in topography points. - * @param blockHeight the height of a block in topography points. - * @throws Exception if the block size does not divide the topography equally. - * @see Topography - * @see Block - */ - public Grid(Topography topo, int blockWidth, int blockHeight) throws Exception { - - if (topo.width % blockWidth != 0) { - throw new Exception("Cannot subdivide topography: block width " + blockWidth + - " is not a divider of width " + topo.width); - } - - if (topo.height % blockHeight != 0) { - throw new Exception("Cannot subdivide topography: block height " + blockHeight + - " is not a divider of height " + topo.height); - } - - this.width = topo.width / blockWidth; - this.height = topo.height / blockHeight; - - this.blockWidth = blockWidth; - this.blockHeight = blockHeight; - - if (logger.isDebugEnabled()) { - logger.debug("Creating new grid from topography " + width + "x" + height); - } - - blocks = new Block[width*height]; - - for (int y=0;y 0) { - put(new Block(new Coordinate(x, y))); - } - } - } - - if (logger.isDebugEnabled()) { - logger.debug("Created new grid from topography with " + getCount() + " active elements."); - } - } - - /** - * Checks if the given coordinate falls within this grid. - * - * @param x the x coordinate to check. - * @param y the y coordinate to check. - * @return if the given coordinate falls within this grid. - */ - private boolean inRange(int x, int y) { - return !(x < 0 || x >= width || y < 0 || y >= height); - } - - /** - * Checks if the given coordinate fall within this grid. - * - * @param c the Coordinate to check. - * @return if the given coordinate falls within this grid. - */ - private boolean inRange(Coordinate c) { - return !(c.x < 0 || c.x >= width || c.y < 0 || c.y >= height); - } - - /** - * Returns the number of non-empty blocks in this grid. - * - * @return the number of non-empty blocks in this grid. - * @see Block - */ - public int getCount() { - return count; - } - - /** - * Stores a Block in the grid. - * - * @param b the Block to store. - * @see Block - */ - public void put(Block b) { - - if (!inRange(b.coordinate)) { - throw new IllegalArgumentException("Coordinate out of bounds! " + b.coordinate); - } - - if (blocks[b.coordinate.y*width + b.coordinate.x] == null) { - count++; - } - - blocks[b.coordinate.y*width + b.coordinate.x] = b; - } - - /** - * Stores all Blocks in a Collection in this grid. - * - * @param elts the blocks to store. - * @see Block - * @see Collection - */ - public void putAll(Collection elts) { - for (Block b : elts) { - put(b); - } - } - - /** - * Retrieves all Blocks in this grid and store them in a Collection. - * - * @param out the collection to which all blocks in this grid will be added. - * @see Block - * @see Collection - */ - public void getAll(Collection out) { - for (Block b : blocks) { - if (b != null) { - out.add(b); - } - } - } - - /** - * Retrieves a Block at a given location. - * - * @param c the location of the block to retrieve. - * @return the Block at the given location, or null if the location is empty. - * @see Block - */ - public Block get(Coordinate c) { - return get(c.x, c.y); - } - - /** - * Retrieves a Block at a given location. - * - * @param x the x coordinate of the location to retrieve. - * @param y the y coordinate of the location to retrieve. - * @return the Block at the given location, or null if the location is empty. - * @see Block - */ - public Block get(int x, int y) { - - if (!inRange(x, y)) { - throw new IllegalArgumentException("Coordiate out of bounds! " + x + "x" + y); - } - - return blocks[y*width + x]; - } - - @Override - public Iterator iterator() { - return new BlockIterator(blocks); - } - - /** - * Retrieve all Blocks in a given rectangular subsection of this grid. - * - * The subsection is an area in a coordinate space that is enclosed by the locations (x,y) (inclusive) and - * (x+width,y+height) (exclusive) in the coordinate space. - * - * @param x the x coordinate of the subsection. - * @param y the y coordinate of the subsection. - * @param width the width of the subsection. - * @param height the height of the subsection. - * @return All blocks enclosed by the specified subsection. - * @see Block - */ - public Block [] getRectangle(int x, int y, int width, int height) { - - ArrayList result = new ArrayList(); - - for (int j=y;j= width || y < 0 || y >= height); + } + + /** + * Checks if the given coordinate fall within this grid. + * + * @param c + * the Coordinate to check. + * @return if the given coordinate falls within this grid. + */ + private boolean inRange(Coordinate c) { + return !(c.x < 0 || c.x >= width || c.y < 0 || c.y >= height); + } + + /** + * Returns the number of non-empty blocks in this grid. + * + * @return the number of non-empty blocks in this grid. + * @see Block + */ + public int getCount() { + return count; + } + + /** + * Stores a Block in the grid. + * + * @param b + * the Block to store. + * @see Block + */ +// private void put(Block b) { +// +// if (!inRange(b.coordinate)) { +// throw new IllegalArgumentException("Coordinate out of bounds! " + b.coordinate); +// } +// +// Coordinate c = b.coordinate; +// +// if (blocks[c.y * width + c.x] == null) { +// count++; +// } +// +// blocks[c.y * width + c.x] = b; +// } + + /** + * Stores all Blocks in a Collection in this grid. + * + * @param elts + * the blocks to store. + * @see Block + * @see Collection + */ +// public void putAll(Collection elts) { +// for (Block b : elts) { +// put(b); +// } +// } + + /** + * Retrieves all Blocks in this grid and store them in a Collection. + * + * @param out + * the collection to which all blocks in this grid will be added. + * @see Block + * @see Collection + */ +// public void getAll(Collection out) { +// for (Block b : blocks) { +// if (b != null) { +// out.add(b); +// } +// } +// } + + /** + * Retrieves a Block at a given location. + * + * @param c + * the location of the block to retrieve, or null. + * @return the Block at the given location, or null if the location is empty. + * @see Block + */ +// private Block get(Coordinate c) { +// +// if (c == null) { +// return null; +// } +// +// return get(c.x, c.y); +// } + + /** + * Retrieves a Block at a given location. + * + * @param x + * the x coordinate of the location to retrieve. + * @param y + * the y coordinate of the location to retrieve. + * @return the Block at the given location, or null if the location is empty. + * @see Block + */ + public Block get(int x, int y) { + + if (!inRange(x, y)) { + throw new IllegalArgumentException("Coordiate out of bounds! " + x + "x" + y); + } + + return blocksByLocation[y][x]; + } + + /** + * Retrieves a Block with a given blockID. + * + * @param blockID + * the ID of the block to retrieve. + + * @return the Block at the given location, or null if the location does not exist. + * @see Block + */ + public Block get(int blockID) { + + if (blockID < 0 || blockID >= blocksByNumber.length) { + throw new IllegalArgumentException("BlockID out of bounds! " + blockID); + } + + return blocksByNumber[blockID]; + } + + + @Override + public Iterator iterator() { + return new BlockIterator(blocksByNumber); + } + + /** + * Retrieve all Blocks in a given rectangular subsection of this grid. + * + * The subsection is an area in a coordinate space that is enclosed by the locations (x,y) (inclusive) and (x+width,y+height) + * (exclusive) in the coordinate space. + * + * @param x + * the x coordinate of the subsection. + * @param y + * the y coordinate of the subsection. + * @param width + * the width of the subsection. + * @param height + * the height of the subsection. + * @return All blocks enclosed by the specified subsection. + * @see Block + */ + public Block[] getRectangle(int x, int y, int width, int height) { + + ArrayList result = new ArrayList(); + + for (int j = y; j < y + height; j++) { + for (int i = x; i < x + width; i++) { + if (inRange(x, y)) { + + Block b = blocksByLocation[j][i]; + + if (b != null) { + result.add(b); + } + } + } + } + + return result.toArray(new Block[result.size()]); + } } diff --git a/src/nl/esciencecenter/esalsa/util/Layer.java b/src/nl/esciencecenter/esalsa/util/Layer.java index 1562779..8dbc30c 100644 --- a/src/nl/esciencecenter/esalsa/util/Layer.java +++ b/src/nl/esciencecenter/esalsa/util/Layer.java @@ -21,7 +21,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; -/** +/** * Layer represents a subdivision of a grid into one or more sets of blocks. * * @author Jason Maassen @@ -30,107 +30,113 @@ * @see Grid * @see Set * @see Block - * + * */ public class Layer implements Iterable { - /** The name of this layer */ - public final String name; - - // The sets in this layer. - private final ArrayList sets = new ArrayList(); - - /** - * Creates an empty layer. - * - * @param name the name for the layer. - */ - public Layer(String name) { - this.name = name; - } - - /** - * Add all Sets in the Collection to this layer. - * - * @param collection the collection of sets to add. - * @see Set - * @see Collection - */ - public void addAll(Collection collection) { - - if (collection == null || collection.size() == 0) { - throw new NullPointerException("Set may not be null!"); - } - - for (Set s : collection) { - add(s); - } - } - - /** - * Add a Set to this layer. - * - * @param set the set to add. - * @see Set - */ - public void add(Set set) { - - if (set == null) { - throw new NullPointerException("Set may not be null!"); - } - - sets.add(set); - } - - /** - * Retrieves the Set at a given index. - * - * @param index the index of the set to retrieve. Must be between 0 (inclusive) and {@link #size()} (exclusive). - * @return the set at the given index. - * @see Set - */ - public Set get(int index) { - - if (index < 0 || index >= sets.size()) { - throw new NoSuchElementException("Invalid index: " + index); - } - - return sets.get(index); - } - - /** - * Retrieves a Set in this layer that contains the Block with Coordinate (x,y). - * - * @param x the x location of the block. - * @param y the y location of the block. - * @return the set that contains the specified block, or null if no set contained the block. - * @see Set - * @see Block - * @see Coordinate - */ - public Set locate(int x, int y) { - - for (Set s : sets) { - if (s.contains(x, y)) { - return s; - } - } - - return null; - } - - /** - * Returns the number of Sets in this layer. - * - * @return the number of sets in this layer. - * @see Set - */ - public int size() { - return sets.size(); - } - - @Override - public Iterator iterator() { - return sets.iterator(); - } + /** The name of this layer */ + public final String name; + + // The sets in this layer. + private final ArrayList sets = new ArrayList(); + + /** + * Creates an empty layer. + * + * @param name + * the name for the layer. + */ + public Layer(String name) { + this.name = name; + } + + /** + * Add all Sets in the Collection to this layer. + * + * @param collection + * the collection of sets to add. + * @see Set + * @see Collection + */ + public void addAll(Collection collection) { + + if (collection == null || collection.size() == 0) { + throw new NullPointerException("Set may not be null!"); + } + + for (Set s : collection) { + add(s); + } + } + + /** + * Add a Set to this layer. + * + * @param set + * the set to add. + * @see Set + */ + public void add(Set set) { + + if (set == null) { + throw new NullPointerException("Set may not be null!"); + } + + sets.add(set); + } + + /** + * Retrieves the Set at a given index. + * + * @param index + * the index of the set to retrieve. Must be between 0 (inclusive) and {@link #size()} (exclusive). + * @return the set at the given index. + * @see Set + */ + public Set get(int index) { + + if (index < 0 || index >= sets.size()) { + throw new NoSuchElementException("Invalid index: " + index); + } + + return sets.get(index); + } + + /** + * Retrieves a Set in this layer that contains the Block with Coordinate (x,y). + * + * @param x + * the x location of the block. + * @param y + * the y location of the block. + * @return the set that contains the specified block, or null if no set contained the block. + * @see Set + * @see Block + * @see Coordinate + */ + public Set locate(int x, int y) { + + for (Set s : sets) { + if (s.contains(x, y)) { + return s; + } + } + + return null; + } + + /** + * Returns the number of Sets in this layer. + * + * @return the number of sets in this layer. + * @see Set + */ + public int size() { + return sets.size(); + } + + @Override + public Iterator iterator() { + return sets.iterator(); + } } diff --git a/src/nl/esciencecenter/esalsa/util/Layers.java b/src/nl/esciencecenter/esalsa/util/Layers.java index 91d336b..6decdc9 100644 --- a/src/nl/esciencecenter/esalsa/util/Layers.java +++ b/src/nl/esciencecenter/esalsa/util/Layers.java @@ -21,9 +21,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Layers provides a simple storage class for a collection of layers. A layer can be retrieved using its name. - * +/** + * Layers provides a simple storage class for a collection of layers. A layer can be retrieved using its name. + * * @author Jason Maassen * @version 1.0 * @since 1.0 @@ -32,78 +32,80 @@ */ public class Layers { - private static final Logger logger = LoggerFactory.getLogger(Layers.class); - - private final HashMap layers = new HashMap(); - - /** - * Add a Layer. - * - * @param layer the Layer to add. - */ - public void add(Layer layer) { - - if (logger.isDebugEnabled()) { - logger.debug("Add layer " + layer.name + " with " + layer.size() + " elements at layer: " + layers.size()); - } - - layers.put(layer.name, layer); - } - - /** - * Retrieve a Layer with a certain name. - * - * @param name the name of the Layer to retrieve. - * @return the Layer requested, or null if the Layer does not exist. - */ - public Layer get(String name) { - - if (logger.isDebugEnabled()) { - logger.debug("Get layer " + name); - } - - if (name == null) { - throw new IllegalArgumentException("A layer name must be provided!"); - } - - return layers.get(name); - } - - /** - * Check if a layer with a certain name exists. - * - * @param name the name of the layer to check. - * @return true if a layer with the provided name exists, false otherwise. - */ - public boolean contains(String name) { - - if (layers.size() == 0) { - return false; - } - - if (name == null) { - throw new IllegalArgumentException("A layer name must be provided!"); - } - - return layers.containsKey(name); - } - - /** - * Returns the number of available layer. - * - * @return the number of available layers. - */ - public int size() { - return layers.size(); - } - - /** - * Returns the names of all layers currently stored. - * - * @return an array containing the names of all layers. - * The name of layer N will be stored at position N in the array. - */ - public String [] listLayers() { - return layers.keySet().toArray(new String[layers.size()]); - } + private static final Logger logger = LoggerFactory.getLogger(Layers.class); + + private final HashMap layers = new HashMap(); + + /** + * Add a Layer. + * + * @param layer + * the Layer to add. + */ + public void add(Layer layer) { + + if (logger.isDebugEnabled()) { + logger.debug("Add layer " + layer.name + " with " + layer.size() + " elements at layer: " + layers.size()); + } + + layers.put(layer.name, layer); + } + + /** + * Retrieve a Layer with a certain name. + * + * @param name + * the name of the Layer to retrieve. + * @return the Layer requested, or null if the Layer does not exist. + */ + public Layer get(String name) { + + if (logger.isDebugEnabled()) { + logger.debug("Get layer " + name); + } + + if (name == null) { + throw new IllegalArgumentException("A layer name must be provided!"); + } + + return layers.get(name); + } + + /** + * Check if a layer with a certain name exists. + * + * @param name + * the name of the layer to check. + * @return true if a layer with the provided name exists, false otherwise. + */ + public boolean contains(String name) { + + if (layers.size() == 0) { + return false; + } + + if (name == null) { + throw new IllegalArgumentException("A layer name must be provided!"); + } + + return layers.containsKey(name); + } + + /** + * Returns the number of available layer. + * + * @return the number of available layers. + */ + public int size() { + return layers.size(); + } + + /** + * Returns the names of all layers currently stored. + * + * @return an array containing the names of all layers. The name of layer N will be stored at position N in the array. + */ + public String[] listLayers() { + return layers.keySet().toArray(new String[layers.size()]); + } } diff --git a/src/nl/esciencecenter/esalsa/util/Line.java b/src/nl/esciencecenter/esalsa/util/Line.java index 6640223..98787ac 100644 --- a/src/nl/esciencecenter/esalsa/util/Line.java +++ b/src/nl/esciencecenter/esalsa/util/Line.java @@ -17,55 +17,57 @@ package nl.esciencecenter.esalsa.util; /** - * Line represents a line between two coordinates. + * Line represents a line between two coordinates. * * @author Jason Maassen * @version 1.0 * @since 1.0 - * @see Coordinate - * + * @see Coordinate + * */ public class Line { - - /** The start of the line. */ - public final Coordinate start; - - /** The end of the line. */ - public final Coordinate end; - - /** - * Creates a new Line between the two specified coordinates. - * - * @param start the start of the line. - * @param end the end of the line. - */ - public Line(Coordinate start, Coordinate end) { - - if (start == null || end == null) { - throw new IllegalArgumentException("Coordinates in a line cannot be null!"); - } - - this.start = start; - this.end = end; - } - @Override - public int hashCode() { - return start.hashCode() + end.hashCode(); - } + /** The start of the line. */ + public final Coordinate start; + + /** The end of the line. */ + public final Coordinate end; + + /** + * Creates a new Line between the two specified coordinates. + * + * @param start + * the start of the line. + * @param end + * the end of the line. + */ + public Line(Coordinate start, Coordinate end) { + + if (start == null || end == null) { + throw new IllegalArgumentException("Coordinates in a line cannot be null!"); + } + + this.start = start; + this.end = end; + } + + @Override + public int hashCode() { + return start.hashCode() + end.hashCode(); + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } - @Override - public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - Line other = (Line) obj; - return (start.equals(other.start) && end.equals(other.end)) || (start.equals(other.end) && end.equals(other.start)); - } + Line other = (Line) obj; + return (start.equals(other.start) && end.equals(other.end)) || (start.equals(other.end) && end.equals(other.start)); + } } diff --git a/src/nl/esciencecenter/esalsa/util/Neighbours.java b/src/nl/esciencecenter/esalsa/util/Neighbours.java index a9a1940..b5c7747 100644 --- a/src/nl/esciencecenter/esalsa/util/Neighbours.java +++ b/src/nl/esciencecenter/esalsa/util/Neighbours.java @@ -17,805 +17,988 @@ package nl.esciencecenter.esalsa.util; /** - * Neighbours is a Java port of the neighborhood functions used in the Fortran version of POP. + * Neighbours is a Java port of the neighborhood functions used in the Fortran version of POP. * * It can be used to determine the neighbors for a given block and the communication costs for these neighbors. Like the original - * Fortran versions, the neighbor and communication functions support CLOSED, CYCLIC and TRIPOLE wrapping of coordinates. + * Fortran versions, the neighbor and communication functions support CLOSED, CYCLIC and TRIPOLE wrapping of coordinates. * * @author Jason Maassen * @version 1.0 * @since 1.0 - * + * */ public class Neighbours { - /** The width of the HALO used in communication */ - public static final int HALOWIDTH = 2; - - /** Constant used to indicate TRIPOLE wrapping of the y coordinate. */ - public static final int TRIPOLE = 0; - - /** Constant used to indicate CYCLIC wrapping of the x or y coordinate. */ - public static final int CYCLIC = 1; - - /** Constant used to indicate CLOSED wrapping (i.e., no wrapping) of the x or y coordinate. */ - public static final int CLOSED = 2; - - /** The selected wrapping for the y coordinate (TRIPOLE, CYCLIC or CLOSED). */ - public final int boundaryV; - - /** The selected wrapping for the x coordinate (CYCLIC or CLOSED). */ - public final int boundaryW; - - /** The message size for messages sends to east or west neighbors. */ - public final int messageSizeEastWest; - - /** The message size for messages sends to north or south neighbors. */ - public final int messageSizeNorthSouth; - - /** The message size for messages sends to north east, north west, south east, south west neighbors. */ - public final int messageSizeCorner; - - /** The message size for messages sends to tripole neighbors. */ - public final int messageSizeTripole; - - /** The grid containing the blocks to use. */ - private final Grid grid; - - public Neighbours(Grid grid, int blockWidth, int blockHeight, int boundaryW, int boundaryV) { - - // Ensure the boundary wrapping settings are correct. - if (!(boundaryW == CYCLIC || boundaryW == CLOSED)) { - throw new IllegalArgumentException("Illegal boundaryW " + boundaryW); - } - - if (boundaryV < 0 || boundaryV > CLOSED) { - throw new IllegalArgumentException("Illegal boundaryV " + boundaryV); - } - - this.grid = grid; - this.boundaryW = boundaryW; - this.boundaryV = boundaryV; - - // Compute the various message sizes. - this.messageSizeEastWest = blockHeight * HALOWIDTH; - this.messageSizeNorthSouth = blockWidth * HALOWIDTH; - this.messageSizeCorner = HALOWIDTH*HALOWIDTH; - this.messageSizeTripole = blockWidth * (HALOWIDTH+1); - } - - /** - * Retrieve the north neighbor of the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryV} is set to CLOSED and the source coordinate is at the top of the grid (y=0). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the north neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourNorth(Coordinate c, boolean includeLand) { - - int x = c.x; - int y = c.y + 1; - - if (y >= grid.height) { - switch (boundaryV) { - case CLOSED: - return null; - case CYCLIC: - y = 0; - break; - case TRIPOLE: - // POP_numBlocksX - iBlock + 1 - x = (grid.width - (c.x+1) + 1) - 1; - y = c.y; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve the south neighbor for the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryV} is set to CLOSED or TRIPOLE and source coordinate is at the bottom of the grid (y=grid.height-1). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the south neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourSouth(Coordinate c, boolean includeLand) { - - int x = c.x; - int y = c.y - 1; - - if (y < 0) { - switch (boundaryV) { - case CLOSED: - case TRIPOLE: - return null; - case CYCLIC: - y = grid.height-1; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve the east neighbor for the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and source coordinate is at the right edge of the grid (x=grid.width-1). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the east neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourEast(Coordinate c, boolean includeLand) { - - int x = c.x + 1; - int y = c.y; - - if (x >= grid.width) { - switch (boundaryW) { - case CLOSED: - return null; - case CYCLIC: - x = 0; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve the west neighbor for the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the west neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourWest(Coordinate c, boolean includeLand) { - - int x = c.x - 1; - int y = c.y; - - if (x < 0) { - switch (boundaryW) { - case CLOSED: - return null; - case CYCLIC: - x = grid.width-1; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve the north east neighbor for the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of the grid (x=grid.width-1). - * - {@code boundaryV} is set to CLOSED and the source coordinate is at the top of the grid (y=0). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the north east neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourNorthEast(Coordinate c, boolean includeLand) { - - int x = c.x + 1; - int y = c.y + 1; - - if (x >= grid.width) { - switch (boundaryW) { - case CLOSED: - return null; - case CYCLIC: - x = 0; - break; - } - } - - if (y >= grid.height) { - switch (boundaryV) { - case CLOSED: - return null; - case CYCLIC: - y = 0; - break; - case TRIPOLE: - // inbr = POP_numBlocksX - iBlock - // if (inbr == 0) inbr = POP_numBlocksX - // jnbr = -jBlock - x = (grid.width - (c.x+1)) - 1; - - if (x < 0) { - x = grid.width-1; - } - - y = c.y; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve the north west neighbor for the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0). - * - {@code boundaryV} is set to CLOSED and the source coordinate is at the top of the grid (y=grid-height-1). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the north west neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourNorthWest(Coordinate c, boolean includeLand) { - - int x = c.x - 1; - int y = c.y + 1; - - if (x < 0) { - switch (boundaryW) { - case CLOSED: - return null; - case CYCLIC: - x = grid.width-1; - break; - } - } - - if (y >= grid.height) { - switch (boundaryV) { - case CLOSED: - return null; - case CYCLIC: - y = 0; - break; - case TRIPOLE: - //inbr = POP_numBlocksX - iBlock + 2 - //if (inbr > POP_numBlocksX) inbr = 1 - //jnbr = -jBlock - x = (grid.width - (c.x+1) + 2) - 1; - - if (x >= grid.width) { - x = 0; - } - - y = c.y; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve the south east neighbor for the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of the grid (x=grid.width-1). - * - {@code boundaryV} is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the south east neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourSouthEast(Coordinate c, boolean includeLand) { - - int x = c.x + 1; - int y = c.y - 1; - - if (x >= grid.width) { - switch (boundaryW) { - case CLOSED: - return null; - case CYCLIC: - x = 0; - break; - } - } - - if (y < 0) { - switch (boundaryV) { - case CLOSED: - case TRIPOLE: - return null; - case CYCLIC: - y = grid.height-1; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve the south west neighbor for the given source coordinate. - * - * The result will be null if: - * - * - {@code includeLand} is set to false and the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0). - * - {@code boundaryV} is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0). - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return the coordinate of the south west neighbor of the source, or null if no valid neighbor exists. - */ - public Coordinate getNeighbourSouthWest(Coordinate c, boolean includeLand) { - - int x = c.x - 1; - int y = c.y - 1; - - if (x < 0) { - switch (boundaryW) { - case CLOSED: - return null; - case CYCLIC: - x = grid.width-1; - break; - } - } - - if (y < 0) { - switch (boundaryV) { - case CLOSED: - case TRIPOLE: - return null; - case CYCLIC: - y = grid.height-1; - break; - } - } - - if (!includeLand && grid.get(x, y) == null) { - return null; - } - - return new Coordinate(x, y); - } - - /** - * Retrieve a 3x3 matrix containing all neighbors for the given source coordinate. - * - * The neighbors are stored in the matrix as follows: - * - * 0 1 2 - * 0 [NW, N, NE] - * 1 [ W, -, E ] - * 2 [SW, S, SE] - * - * The middle entry (i.e., result[1][1]) is always null as it represents the source coordinate. - * - * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0), - * the NW, W, and SW entries will contain null. - * - * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of the grid (x=grid.width-1), - * the NE, E, and SE entries will contain null. - * - * If the {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of the grid (y=grid.height-1), - * the NE, N, and NW entries will contain null. - * - * If the {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0), - * the SE, S, and SW entries will contain null. - * - * If {@code includeLand} is set to false, entries may be null if the neighbor represents a land-only block. - * - * @param c the source coordinate. - * @param includeLand should land only coordinates be returned ? - * @return a 3x3 matrix containing all valid neighbors for the given source coordinate. - */ - public Coordinate [][] getNeighbours(Coordinate c, boolean includeLand) { - - Coordinate [][] result = new Coordinate[3][3]; - - result[0][0] = getNeighbourNorthWest(c, includeLand); - result[0][1] = getNeighbourNorth(c, includeLand); - result[0][2] = getNeighbourNorthEast(c, includeLand); - - result[1][0] = getNeighbourWest(c, includeLand); - result[1][1] = null; - result[1][2] = getNeighbourEast(c, includeLand); - - result[2][0] = getNeighbourSouthWest(c, includeLand); - result[2][1] = getNeighbourSouth(c, includeLand); - result[2][2] = getNeighbourSouthEast(c, includeLand); - - return result; - } - - /** - * Retrieve the amount of communication needed to the north neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryV} is set to CLOSED and the source coordinate is at the top of the grid (y=0). - * - * @param c the source coordinate. - * @return the amount of communication needed with the north neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeNorth(Coordinate c) { - - int x = c.x; - int y = c.y + 1; - - int messageSize = messageSizeNorthSouth; - - if (y >= grid.height) { - switch (boundaryV) { - case CLOSED: - return 0; - case CYCLIC: - y = 0; - messageSize = messageSizeNorthSouth; - break; - case TRIPOLE: - x = (grid.width - (c.x+1) + 1) - 1; - y = c.y; - messageSize = messageSizeTripole; - break; - } - } - - if (grid.get(x, y) == null) { - return 0; - } - - return messageSize; - } - - /** - * Retrieve the amount of communication needed to the south neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryV} is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=grid.height-1). - * - * @param c the source coordinate. - * @return the amount of communication needed with the south neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeSouth(Coordinate c) { - return (getNeighbourSouth(c, false) == null ? 0 : messageSizeNorthSouth); - } - - /** - * Retrieve the amount of communication needed to the east neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of the grid (x=grid.width-1). - * - * @param c the source coordinate. - * @return the amount of communication needed with the east neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeEast(Coordinate c) { - return (getNeighbourEast(c, false) == null ? 0 : messageSizeEastWest); - } - - /** - * Retrieve the amount of communication needed to the west neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0). - * - * @param c the source coordinate. - * @return the amount of communication needed with the west neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeWest(Coordinate c) { - return (getNeighbourWest(c, false) == null ? 0 : messageSizeEastWest); - } - - /** - * Retrieve the amount of communication needed to the north east neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryW} setting is set to CLOSED and the source coordinate is at the right edge of the grid (x=grid.width-1). - * - {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of the grid (y=0). - * - * @param c the source coordinate. - * @return the amount of communication needed with the north east neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeNorthEast(Coordinate c) { - - int x = c.x + 1; - int y = c.y + 1; - - int messageSize = messageSizeCorner; - - if (x >= grid.width) { - switch (boundaryW) { - case CLOSED: - return 0; - case CYCLIC: - x = 0; - break; - } - } - - if (y >= grid.height) { - switch (boundaryV) { - case CLOSED: - return 0; - case CYCLIC: - y = 0; - break; - case TRIPOLE: - // inbr = POP_numBlocksX - iBlock - // if (inbr == 0) inbr = POP_numBlocksX - // jnbr = -jBlock - x = (grid.width - (c.x+1)) - 1; - - if (x < 0) { - x = grid.width-1; - } - - y = c.y; - - messageSize = messageSizeTripole; - break; - } - } - - if (grid.get(x, y) == null) { - return 0; - } - - return messageSize; - } - - /** - * Retrieve the amount of communication needed to the north west neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0). - * - {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of the grid (y=0). - * - * @param c the source coordinate. - * @return the amount of communication needed with the north west neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeNorthWest(Coordinate c) { - - int x = c.x - 1; - int y = c.y + 1; - - int messageSize = messageSizeCorner; - - if (x <= 0) { - switch (boundaryW) { - case CLOSED: - return 0; - case CYCLIC: - x = grid.width-1; - break; - } - } - - if (y >= grid.height) { - switch (boundaryV) { - case CLOSED: - return 0; - case CYCLIC: - y = 0; - break; - case TRIPOLE: - //inbr = POP_numBlocksX - iBlock + 2 - //if (inbr > POP_numBlocksX) inbr = 1 - //jnbr = -jBlock - x = (grid.width - (c.x+1) + 2) - 1; - - if (x >= grid.width) { - x = 0; - } - - y = c.y; - messageSize = messageSizeTripole; - break; - } - } - - if (grid.get(x, y) == null) { - return 0; - } - - return messageSize; - } - - /** - * Retrieve the amount of communication needed to the south east neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryW} setting is set to CLOSED and the source coordinate is at the right edge of the grid (x=grid.width-1). - * - {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0). - * - * @param c the source coordinate. - * @return the amount of communication needed with the south east neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeSouthEast(Coordinate c) { - return (getNeighbourSouthEast(c, false) == null ? 0 : messageSizeCorner); - } - - /** - * Retrieve the amount of communication needed to the south west neighbor for the given source coordinate. - * - * The result is 0 if: - * - * - the neighbor is a land only block. - * - {@code boundaryW} setting is set to CLOSED and the source coordinate is at the left edge of the grid (x=0). - * - {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0). - * - * @param c the source coordinate. - * @return the amount of communication needed with the south west neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getMessageSizeSouthWest(Coordinate c) { - return (getNeighbourSouthWest(c, false) == null ? 0 : messageSizeCorner); - } - - /** - * Retrieve the amount of communication needed to a specified neighbor of the given source coordinate. - * - * The neighbor can be selected by specifying the x and y coordinate offsets (dx, dy). - * - * The result is 0 if: - * - * - the selected neighbor is a land only block. - * - any of the offsets is larger than 1 or smaller than -1. - * - {@code boundaryW} setting is set to CLOSED and the selected neighbor is not on the grid. - * - {@code boundaryV} setting is set to CLOSED and the selected neighbor is not on the grid. - * - {@code boundaryV} setting is set to TRIPOLE and the selected neighbor is below the lower edge of the grid. - * - * @param c the source coordinate. - * @param dx the x offset of the neighbor coordinate. - * @param dy the y offset of the neighbor coordinate. - * - * @return the amount of communication needed with the specified neighbor of the source, or 0 if no valid neighbor exists. - */ - public int getCommunication(Coordinate c, int dx, int dy) { - - switch (dx) { - case -1: { - switch (dy) { - case -1: return getMessageSizeNorthWest(c); - case 0: return getMessageSizeWest(c); - case 1: return getMessageSizeNorthEast(c); - default: return 0; - } - } - - case 0: { - switch (dy) { - case -1: return getMessageSizeWest(c); - case 0: return 0; - case 1: return getMessageSizeEast(c); - default: return 0; - } - } - - case 1: { - switch (dy) { - case -1: return getMessageSizeSouthWest(c); - case 0: return getMessageSizeSouth(c); - case 1: return getMessageSizeSouthEast(c); - default: return 0; - } - } - - default: return 0; - } - } - - /** - * Retrieve a 3x3 matrix containing the amount of communication needed for all neighbors for the given source coordinate. - * - * The communication is stored in the matrix as follows: - * - * 0 1 2 - * 0 [NW, N, NE] - * 1 [ W, -, E ] - * 2 [SW, S, SE] - * - * The middle entry (i.e., result[1][1]) is always 0 as it represents the source coordinate. - * - * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0), - * the NW, W, and SW entries will contain 0. - * - * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of the grid (x=grid.width-1), - * the NE, E, and SE entries will contain 0. - * - * If the {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of the grid (y=grid.height-1), - * the NE, N, and NW entries will contain 0. - * - * If the {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0), - * the SE, S, and SW entries will contain 0. - * - * In addition, entries may be 0 if the neighbor represents a land-only block. - * - * @param c the source coordinate. - * @return a 3x3 matrix containing the amount of communication needed for each neighbor of the given source coordinate. - */ - public int [][] getCommunication(Coordinate c) { - - int [][] result = new int[3][3]; - - result[0][0] = getMessageSizeNorthWest(c); - result[0][1] = getMessageSizeNorth(c); - result[0][2] = getMessageSizeNorthEast(c); - - result[1][0] = getMessageSizeWest(c); - result[1][1] = 0; - result[1][2] = getMessageSizeEast(c); - - result[2][0] = getMessageSizeSouthWest(c); - result[2][1] = getMessageSizeSouth(c); - result[2][2] = getMessageSizeSouthEast(c); - - return result; - } + /** The width of the HALO used in communication */ + public static final int HALOWIDTH = 2; + + /** Constant used to indicate TRIPOLE wrapping of the y coordinate. */ + public static final int TRIPOLE = 0; + + /** Constant used to indicate CYCLIC wrapping of the x or y coordinate. */ + public static final int CYCLIC = 1; + + /** Constant used to indicate CLOSED wrapping (i.e., no wrapping) of the x or y coordinate. */ + public static final int CLOSED = 2; + + /** The selected wrapping for the y coordinate (TRIPOLE, CYCLIC or CLOSED). */ + public final int boundaryV; + + /** The selected wrapping for the x coordinate (CYCLIC or CLOSED). */ + public final int boundaryW; + + /** The message size for messages sends to east or west neighbors. */ + public final int messageSizeEastWest; + + /** The message size for messages sends to north or south neighbors. */ + public final int messageSizeNorthSouth; + + /** The message size for messages sends to north east, north west, south east, south west neighbors. */ + public final int messageSizeCorner; + + /** The message size for messages sends to tripole neighbors. */ + public final int messageSizeTripole; + + /** The grid containing the blocks to use. */ + // private final Grid grid; + private final int gridWidth; + private final int gridHeight; + + + private final int blockWidth; + private final int blockHeight; + + private final Topography topo; + + public Neighbours(Topography topo, int gridWidth, int gridHeight, int blockWidth, int blockHeight, int boundaryW, int boundaryV) { + + // Ensure the boundary wrapping settings are correct. + if (!(boundaryW == CYCLIC || boundaryW == CLOSED)) { + throw new IllegalArgumentException("Illegal boundaryW " + boundaryW); + } + + if (boundaryV < 0 || boundaryV > CLOSED) { + throw new IllegalArgumentException("Illegal boundaryV " + boundaryV); + } + + this.topo = topo; + + this.gridWidth = gridWidth; + this.gridHeight = gridHeight; + + this.blockWidth = blockWidth; + this.blockHeight = blockHeight; + + this.boundaryW = boundaryW; + this.boundaryV = boundaryV; + + // Compute the various message sizes. + this.messageSizeEastWest = blockHeight * HALOWIDTH; + this.messageSizeNorthSouth = blockWidth * HALOWIDTH; + this.messageSizeCorner = HALOWIDTH * HALOWIDTH; + this.messageSizeTripole = blockWidth * (HALOWIDTH + 1); + } + + public static int parseBoundaryX(String value) { + + if (value != null) { + + if (value.equalsIgnoreCase("CLOSED")) { + return CLOSED; + } + + if (value.equalsIgnoreCase("CYCLIC")) { + return CYCLIC; + } + } + + return -1; + } + + public static int parseBoundaryY(String value) { + + if (value != null) { + + if (value.equalsIgnoreCase("CLOSED")) { + return CLOSED; + } + + if (value.equalsIgnoreCase("CYCLIC")) { + return CYCLIC; + } + + if (value.equalsIgnoreCase("TRIPOLE")) { + return TRIPOLE; + } + } + + return -1; + } + + public static String [] getValidBoundaryX() { + return new String [] { "CLOSED", "CYCLIC" }; + } + + public static String [] getValidBoundaryY() { + return new String [] { "CLOSED", "CYCLIC", "TRIPOLE" }; + } + + public boolean isOcean(int x, int y) { + return topo.getRectangleWork(x * blockWidth, y * blockHeight, blockWidth, blockHeight) > 0; + } + + public boolean isOcean(Coordinate c) { + return isOcean(c.x, c.y); + } + + /** + * Retrieve the north neighbor of the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryV} is set to CLOSED and the + * source coordinate is at the top of the grid (y=0). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the north neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourNorth(Coordinate c) { + + int x = c.x; + int y = c.y + 1; + + if (y >= gridHeight) { + switch (boundaryV) { + case CLOSED: + return 0; + case CYCLIC: + y = 0; + break; + case TRIPOLE: + // POP_numBlocksX - iBlock + 1 + x = (gridWidth - (c.x + 1) + 1) - 1; + y = c.y; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve the south neighbor for the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryV} is set to CLOSED or + * TRIPOLE and source coordinate is at the bottom of the grid (y=gridHeight-1). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the south neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourSouth(Coordinate c) { + + int x = c.x; + int y = c.y - 1; + + if (y < 0) { + switch (boundaryV) { + case CLOSED: + case TRIPOLE: + return 0; + case CYCLIC: + y = gridHeight - 1; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve the east neighbor for the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and + * source coordinate is at the right edge of the grid (x=gridWidth-1). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the east neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourEast(Coordinate c) { + + int x = c.x + 1; + int y = c.y; + + if (x >= gridWidth) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = 0; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve the west neighbor for the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the + * source coordinate is at the left edge of the grid (x=0). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the west neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourWest(Coordinate c) { + + int x = c.x - 1; + int y = c.y; + + if (x < 0) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = gridWidth - 1; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve the north east neighbor for the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the + * source coordinate is at the right edge of the grid (x=gridWidth-1). - {@code boundaryV} is set to CLOSED and the source + * coordinate is at the top of the grid (y=0). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the north east neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourNorthEast(Coordinate c) { + + int x = c.x + 1; + int y = c.y + 1; + + if (x >= gridWidth) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = 0; + break; + } + } + + if (y >= gridHeight) { + switch (boundaryV) { + case CLOSED: + return 0; + case CYCLIC: + y = 0; + break; + case TRIPOLE: + // inbr = POP_numBlocksX - iBlock + // if (inbr == 0) inbr = POP_numBlocksX + // jnbr = -jBlock + x = (gridWidth - (c.x + 1)) - 1; + + if (x < 0) { + x = gridWidth - 1; + } + + y = c.y; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve the north west neighbor for the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the + * source coordinate is at the left edge of the grid (x=0). - {@code boundaryV} is set to CLOSED and the source coordinate is + * at the top of the grid (y=grid-height-1). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the north west neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourNorthWest(Coordinate c) { + + int x = c.x - 1; + int y = c.y + 1; + + if (x < 0) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = gridWidth - 1; + break; + } + } + + if (y >= gridHeight) { + switch (boundaryV) { + case CLOSED: + return 0; + case CYCLIC: + y = 0; + break; + case TRIPOLE: + //inbr = POP_numBlocksX - iBlock + 2 + //if (inbr > POP_numBlocksX) inbr = 1 + //jnbr = -jBlock + x = (gridWidth - (c.x + 1) + 2) - 1; + + if (x >= gridWidth) { + x = 0; + } + + y = c.y; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve the south east neighbor for the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the + * source coordinate is at the right edge of the grid (x=gridWidth-1). - {@code boundaryV} is set to CLOSED or TRIPOLE and the + * source coordinate is at the bottom of the grid (y=0). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the south east neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourSouthEast(Coordinate c) { + + int x = c.x + 1; + int y = c.y - 1; + + if (x >= gridWidth) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = 0; + break; + } + } + + if (y < 0) { + switch (boundaryV) { + case CLOSED: + case TRIPOLE: + return 0; + case CYCLIC: + y = gridHeight - 1; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve the south west neighbor for the given source coordinate. + * + * The result will be null if: + * + * - {@code includeLand} is set to false and the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the + * source coordinate is at the left edge of the grid (x=0). - {@code boundaryV} is set to CLOSED or TRIPOLE and the source + * coordinate is at the bottom of the grid (y=0). + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return the coordinate of the south west neighbor of the source, or null if no valid neighbor exists. + */ + public int getNeighbourSouthWest(Coordinate c) { + + int x = c.x - 1; + int y = c.y - 1; + + if (x < 0) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = gridWidth - 1; + break; + } + } + + if (y < 0) { + switch (boundaryV) { + case CLOSED: + case TRIPOLE: + return 0; + case CYCLIC: + y = gridHeight - 1; + break; + } + } + + return y*gridWidth + x + 1; + } + + /** + * Retrieve a 3x3 matrix containing all neighbors for the given source coordinate. + * + * The neighbors are stored in the matrix as follows: + * + * 0 1 2 0 [NW, N, NE] 1 [ W, -, E ] 2 [SW, S, SE] + * + * The middle entry (i.e., result[1][1]) is always null as it represents the source coordinate. + * + * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0), the NW, W, and + * SW entries will contain null. + * + * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of the grid (x=gridWidth-1), the + * NE, E, and SE entries will contain null. + * + * If the {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of the grid (y=gridHeight-1), the + * NE, N, and NW entries will contain null. + * + * If the {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0), + * the SE, S, and SW entries will contain null. + * + * If {@code includeLand} is set to false, entries may be null if the neighbor represents a land-only block. + * + * @param c + * the source coordinate. + * @param includeLand + * should land only coordinates be returned ? + * @return a 3x3 matrix containing all valid neighbors for the given source coordinate. + */ + public int[][] getNeighbours(Coordinate c) { + + int[][] result = new int[3][3]; + + result[0][0] = getNeighbourNorthWest(c); + result[0][1] = getNeighbourNorth(c); + result[0][2] = getNeighbourNorthEast(c); + + result[1][0] = getNeighbourWest(c); + result[1][1] = 0; + result[1][2] = getNeighbourEast(c); + + result[2][0] = getNeighbourSouthWest(c); + result[2][1] = getNeighbourSouth(c); + result[2][2] = getNeighbourSouthEast(c); + + return result; + } + + /** + * Retrieve the amount of communication needed to the north neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryV} is set to CLOSED and the source coordinate is at the top of the + * grid (y=0). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the north neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeNorth(Coordinate c) { + + int x = c.x; + int y = c.y + 1; + + int messageSize = messageSizeNorthSouth; + + if (y >= gridHeight) { + switch (boundaryV) { + case CLOSED: + return 0; + case CYCLIC: + //y = 0; + messageSize = messageSizeNorthSouth; + break; + case TRIPOLE: + //x = (gridWidth - (c.x+1) + 1) - 1; + //y = c.y; + messageSize = messageSizeTripole; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSize; + } + + /** + * Retrieve the amount of communication needed to the south neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryV} is set to CLOSED or TRIPOLE and the source coordinate is at the + * bottom of the grid (y=gridHeight-1). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the south neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeSouth(Coordinate c) { + + int x = c.x; + int y = c.y - 1; + + if (y < 0) { + switch (boundaryV) { + case CLOSED: + case TRIPOLE: + return 0; + case CYCLIC: + y = gridHeight - 1; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSizeNorthSouth; + } + + /** + * Retrieve the amount of communication needed to the east neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of + * the grid (x=gridWidth-1). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the east neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeEast(Coordinate c) { + + int x = c.x + 1; + int y = c.y; + + if (x >= gridWidth) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = 0; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSizeEastWest; + } + + /** + * Retrieve the amount of communication needed to the west neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of + * the grid (x=0). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the west neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeWest(Coordinate c) { + + int x = c.x - 1; + int y = c.y; + + if (x < 0) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = gridWidth - 1; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSizeEastWest; + } + + /** + * Retrieve the amount of communication needed to the north east neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryW} setting is set to CLOSED and the source coordinate is at the right + * edge of the grid (x=gridWidth-1). - {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of + * the grid (y=0). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the north east neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeNorthEast(Coordinate c) { + + int x = c.x + 1; + int y = c.y + 1; + + int messageSize = messageSizeCorner; + + if (x >= gridWidth) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = 0; + break; + } + } + + if (y >= gridHeight) { + switch (boundaryV) { + case CLOSED: + return 0; + case CYCLIC: + y = 0; + break; + case TRIPOLE: + // inbr = POP_numBlocksX - iBlock + // if (inbr == 0) inbr = POP_numBlocksX + // jnbr = -jBlock + x = (gridWidth - (c.x + 1)) - 1; + + if (x < 0) { + x = gridWidth - 1; + } + + y = c.y; + + messageSize = messageSizeTripole; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSize; + } + + /** + * Retrieve the amount of communication needed to the north west neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of + * the grid (x=0). - {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of the grid (y=0). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the north west neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeNorthWest(Coordinate c) { + + int x = c.x - 1; + int y = c.y + 1; + + int messageSize = messageSizeCorner; + + if (x <= 0) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = gridWidth - 1; + break; + } + } + + if (y >= gridHeight) { + switch (boundaryV) { + case CLOSED: + return 0; + case CYCLIC: + y = 0; + break; + case TRIPOLE: + //inbr = POP_numBlocksX - iBlock + 2 + //if (inbr > POP_numBlocksX) inbr = 1 + //jnbr = -jBlock + x = (gridWidth - (c.x + 1) + 2) - 1; + + if (x >= gridWidth) { + x = 0; + } + + y = c.y; + messageSize = messageSizeTripole; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSize; + } + + /** + * Retrieve the amount of communication needed to the south east neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryW} setting is set to CLOSED and the source coordinate is at the right + * edge of the grid (x=gridWidth-1). - {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at + * the bottom of the grid (y=0). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the south east neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeSouthEast(Coordinate c) { + + int x = c.x + 1; + int y = c.y - 1; + + if (x >= gridWidth) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = 0; + break; + } + } + + if (y < 0) { + switch (boundaryV) { + case CLOSED: + case TRIPOLE: + return 0; + case CYCLIC: + y = gridHeight - 1; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSizeCorner; + } + + /** + * Retrieve the amount of communication needed to the south west neighbor for the given source coordinate. + * + * The result is 0 if: + * + * - the neighbor is a land only block. - {@code boundaryW} setting is set to CLOSED and the source coordinate is at the left + * edge of the grid (x=0). - {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at the bottom + * of the grid (y=0). + * + * @param c + * the source coordinate. + * @return the amount of communication needed with the south west neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getMessageSizeSouthWest(Coordinate c) { + + int x = c.x - 1; + int y = c.y - 1; + + if (x < 0) { + switch (boundaryW) { + case CLOSED: + return 0; + case CYCLIC: + x = gridWidth - 1; + break; + } + } + + if (y < 0) { + switch (boundaryV) { + case CLOSED: + case TRIPOLE: + return 0; + case CYCLIC: + y = gridHeight - 1; + break; + } + } + + if (!isOcean(x, y)) { + return 0; + } + + return messageSizeCorner; + } + + /** + * Retrieve the amount of communication needed to a specified neighbor of the given source coordinate. + * + * The neighbor can be selected by specifying the x and y coordinate offsets (dx, dy). + * + * The result is 0 if: + * + * - the selected neighbor is a land only block. - any of the offsets is larger than 1 or smaller than -1. - {@code boundaryW} + * setting is set to CLOSED and the selected neighbor is not on the grid. - {@code boundaryV} setting is set to CLOSED and the + * selected neighbor is not on the grid. - {@code boundaryV} setting is set to TRIPOLE and the selected neighbor is below the + * lower edge of the grid. + * + * @param c + * the source coordinate. + * @param dx + * the x offset of the neighbor coordinate. + * @param dy + * the y offset of the neighbor coordinate. + * + * @return the amount of communication needed with the specified neighbor of the source, or 0 if no valid neighbor exists. + */ + public int getCommunication(Coordinate c, int dx, int dy) { + + switch (dx) { + case -1: { + switch (dy) { + case -1: + return getMessageSizeNorthWest(c); + case 0: + return getMessageSizeWest(c); + case 1: + return getMessageSizeNorthEast(c); + default: + return 0; + } + } + + case 0: { + switch (dy) { + case -1: + return getMessageSizeWest(c); + case 0: + return 0; + case 1: + return getMessageSizeEast(c); + default: + return 0; + } + } + + case 1: { + switch (dy) { + case -1: + return getMessageSizeSouthWest(c); + case 0: + return getMessageSizeSouth(c); + case 1: + return getMessageSizeSouthEast(c); + default: + return 0; + } + } + + default: + return 0; + } + } + + /** + * Retrieve a 3x3 matrix containing the amount of communication needed for all neighbors for the given source coordinate. + * + * The communication is stored in the matrix as follows: + * + * 0 1 2 0 [NW, N, NE] 1 [ W, -, E ] 2 [SW, S, SE] + * + * The middle entry (i.e., result[1][1]) is always 0 as it represents the source coordinate. + * + * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the left edge of the grid (x=0), the NW, W, and + * SW entries will contain 0. + * + * If the {@code boundaryW} is set to CLOSED and the source coordinate is at the right edge of the grid (x=gridWidth-1), the + * NE, E, and SE entries will contain 0. + * + * If the {@code boundaryV} setting is set to CLOSED and the source coordinate is at the top of the grid (y=gridHeight-1), the + * NE, N, and NW entries will contain 0. + * + * If the {@code boundaryV} setting is set to CLOSED or TRIPOLE and the source coordinate is at the bottom of the grid (y=0), + * the SE, S, and SW entries will contain 0. + * + * In addition, entries may be 0 if the neighbor represents a land-only block. + * + * @param c + * the source coordinate. + * @return a 3x3 matrix containing the amount of communication needed for each neighbor of the given source coordinate. + */ + public int[][] getCommunication(Coordinate c) { + + int[][] result = new int[3][3]; + + result[0][0] = getMessageSizeNorthWest(c); + result[0][1] = getMessageSizeNorth(c); + result[0][2] = getMessageSizeNorthEast(c); + + result[1][0] = getMessageSizeWest(c); + result[1][1] = 0; + result[1][2] = getMessageSizeEast(c); + + result[2][0] = getMessageSizeSouthWest(c); + result[2][1] = getMessageSizeSouth(c); + result[2][2] = getMessageSizeSouthEast(c); + + return result; + } } diff --git a/src/nl/esciencecenter/esalsa/util/OptimizeTopography.java b/src/nl/esciencecenter/esalsa/util/OptimizeTopography.java new file mode 100644 index 0000000..8203856 --- /dev/null +++ b/src/nl/esciencecenter/esalsa/util/OptimizeTopography.java @@ -0,0 +1,158 @@ +/* + * Copyright 2013 Netherlands eScience Center + * + * 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 nl.esciencecenter.esalsa.util; + +/** + * OptimizeTopography can optimize a Topography by relocating blocks such that is becomes easier to find an optimal load balance. + * + * Currently OptimizeTopography is only capable of optimizing when a tripole grid is used. When using a tripole grid, additional + * communication is required for blocks located at the top of the topography (i.e., the north pole). OptimizeTopography optimizes + * by relocating all ocean points in the area roughly delimited by north-east Russia, Alaska and Canada to the area delimited by + * Canada, Greenland, and Europe. + * + * @author Jason Maassen + * @version 1.1 + * @since 1.1 + * @see Topography + * @see Grid + * @see Neighbours + * + */ +public class OptimizeTopography { + + private final Topography topology; + private final Grid grid; + private final Neighbours neighbours; + + private Topography optTop; + private Grid optGrid; + + public OptimizeTopography(Topography topology, Grid grid, Neighbours neighbours) { + this.topology = topology; + this.grid = grid; + this.neighbours = neighbours; + } + + public Topography getOptimizedTopography() { + return optTop; + } + + public Grid getOptimizedGrid() { + return optGrid; + } + + private Topography createTopography(int index) throws Exception { + + int topoHeight = (topology.height + (topology.height - index * grid.blockHeight)); + + System.out.println("New Topology dimensions " + topology.width + "x" + topoHeight); + + int xOffset = (grid.width / 2) * grid.blockWidth; + + int[][] data = new int[topology.width][topoHeight]; + + for (int x = 0; x < topology.width; x++) { + for (int y = 0; y < topoHeight; y++) { + data[x][y] = 0; + } + } + + for (int y = 0; y < index * grid.blockHeight; y++) { + for (int x = 0; x < topology.width; x++) { + data[x][y] = topology.get(x, y); + } + } + + for (int y = index * grid.blockHeight; y < topology.height; y++) { + for (int x = 0; x < xOffset; x++) { + data[x][y] = topology.get(x, y); + } + } + + for (int y = 1; y < (grid.height - index) * grid.blockHeight; y++) { + for (int x = 0; x < xOffset; x++) { + data[xOffset - x - 1][topology.height + y - 1] = topology.get(xOffset + x, topology.height - y); + } + } + + return new Topography(data); + } + + private Grid createGrid(int index) { + + System.out.println("Original Grid dimensions " + grid.width + "x" + grid.height); + System.out.println("New Grid dimensions " + grid.width + "x" + (grid.height + (grid.height - index))); + + Grid g = new Grid(grid, grid.height - index); + + for (int y = index; y < grid.height; y++) { + for (int x = grid.width / 2; x < grid.width; x++) { + + int newX = grid.width / 2 - (x - grid.width / 2) - 1; + int newY = grid.height + (grid.height - y - 1); + + g.relocate(x, y, newX, newY); + } + } + + g.insertLand(); + + return g; + } + + public void optimize() throws Exception { + + // This optimization only makes sense if a tripolo grid is used. + if (neighbours.boundaryV == Neighbours.TRIPOLE) { + // Optimize tripole blocks up top. + + int communication = 0; + + for (int i = 0; i < grid.width; i++) { + int[][] tmp = neighbours.getCommunication(new Coordinate(i, grid.height - 1)); + communication += tmp[0][0] + tmp[1][0] + tmp[2][0]; + } + + System.out.println("Communication TRIPOLE " + communication); + + // Find the narrowest point at the Bering Straight. + int[] width = new int[grid.height]; + int best = Integer.MAX_VALUE; + int index = -1; + + for (int i = 0; i < grid.height; i++) { + for (int j = grid.width / 2; j < grid.width; j++) { + if (grid.get(j, i).ocean) { + width[i]++; + } + } + + System.out.println("Width[" + i + "] = " + width[i]); + + if (width[i] < best) { + best = width[i]; + index = i; + } + } + + System.out.println("Best " + index + " = " + best); + + optTop = createTopography(index); + optGrid = createGrid(index); + } + } +} diff --git a/src/nl/esciencecenter/esalsa/util/PrimeFactorization.java b/src/nl/esciencecenter/esalsa/util/PrimeFactorization.java index 61b00bb..c1d7df2 100644 --- a/src/nl/esciencecenter/esalsa/util/PrimeFactorization.java +++ b/src/nl/esciencecenter/esalsa/util/PrimeFactorization.java @@ -20,95 +20,90 @@ /** * PrimeFactorization is a utility class for prime factorization. - * - * Using factor, a value can be split into its prime factors using a simple trial division algorithm. * - * This implementation assumes the largest possible prime factor used is 997. + * Using factor, a value can be split into its prime factors using a simple trial division algorithm. + * + * This implementation assumes the largest possible prime factor used is 997. * * @author Jason Maassen * @version 1.0 * @since 1.0 * @see Block - * + * */ public class PrimeFactorization { - /** All primes smaller than 1000. */ - private static final int [] PRIMES = new int [] - { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, - 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, - 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, - 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, - 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, - 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, - 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, - 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, - 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, - 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, - 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, - 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, - 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, - 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, - 941, 947, 953, 967, 971, 977, 983, 991, 997 }; - - /** - * Find the smallest prime that divides value cleanly. - * - * @param value the value to split. - * @return the smallest prime that divides value cleanly. - * @throws Exception if no divider could be found. - */ - private static int findDivider(int value) throws Exception { - - for (int i=0;i value) { - break; - } - - if (value % d == 0) { - return d; - } - } - - throw new Exception("No divider not found!"); - } - - /** - * Split value into its prime factors using a simple trial division algorithm. - * - * This implementation assumes the largest possible prime factor used is 997. - * - * @param value the value to split (must be 2 or greater). - * @return an array containing the prime factors of value, sorted by size (largest first). - * @throws Exception if value could not be split because it is smaller than two or contains - * a prime factor larger than 997. - */ - public static int [] factor(int value) throws Exception { - - if (value <= 1) { - throw new Exception("Value must be >= 2!"); - } - - ArrayList dividers = new ArrayList(); - - while (value > 1) { - - int div = findDivider(value); - - dividers.add(div); - - value = value/div; - } - - int [] result = new int[dividers.size()]; - - for (int i=0;ivalue cleanly. + * + * @param value + * the value to split. + * @return the smallest prime that divides value cleanly. + * @throws Exception + * if no divider could be found. + */ + private static int findDivider(int value) throws Exception { + + for (int i = 0; i < PRIMES.length; i++) { + + int d = PRIMES[i]; + + if (d > value) { + break; + } + + if (value % d == 0) { + return d; + } + } + + throw new Exception("No divider not found!"); + } + + /** + * Split value into its prime factors using a simple trial division algorithm. + * + * This implementation assumes the largest possible prime factor used is 997. + * + * @param value + * the value to split (must be 2 or greater). + * @return an array containing the prime factors of value, sorted by size (largest first). + * @throws Exception + * if value could not be split because it is smaller than two or contains a prime factor larger than 997. + */ + public static int[] factor(int value) throws Exception { + + if (value <= 1) { + throw new Exception("Value must be >= 2!"); + } + + ArrayList dividers = new ArrayList(); + + while (value > 1) { + + int div = findDivider(value); + + dividers.add(div); + + value = value / div; + } + + int[] result = new int[dividers.size()]; + + for (int i = 0; i < dividers.size(); i++) { + result[i] = dividers.get(dividers.size() - i - 1); + } + + return result; + } } diff --git a/src/nl/esciencecenter/esalsa/util/Set.java b/src/nl/esciencecenter/esalsa/util/Set.java index dcd12e2..97e84ba 100644 --- a/src/nl/esciencecenter/esalsa/util/Set.java +++ b/src/nl/esciencecenter/esalsa/util/Set.java @@ -24,442 +24,540 @@ import java.util.NoSuchElementException; /** - * Set represents a set of Blocks. + * Set represents a set of Blocks. * * @author Jason Maassen * @version 1.0 * @since 1.0 * @see Block - * + * */ public class Set implements Iterable { - /** The smallest x coordinate found in all blocks of the set. */ - public final int minX; - - /** The largest x coordinate found in all blocks of the set. */ - public final int maxX; - - /** The smallest y coordinate found in all blocks of the set. */ - public final int minY; - - /** The largest y coordinate found in all blocks of the set. */ - public final int maxY; - - /** The blocks in this set. */ - private final Block [] blocks; - - /** A list of subsets of this set. */ - private ArrayList subSets; - - /** A list of Coordinates of possible neighbor blocks of this set. */ - private Coordinate [] neighbours; - - /** The amount of external (out of set) communication required by the block in this set. */ - private int communication = -1; - - /** The index of this set on the layer it belogs to. */ - public final int index; - - /** A Comparator used to sort blocks on their coordinates (smallest first). */ - private class BlockComparator implements Comparator { - - @Override - public int compare(Block o1, Block o2) { - - Coordinate c1 = o1.coordinate; - Coordinate c2 = o2.coordinate; - - if (c1.y < c2.y) { - return -1; - } else if (c1.y == c2.y) { - if (c1.x < c2.x) { - return -1; - } else if (c1.x == c2.x) { - return 0; - } - } - - return 1; - } - } - - /** - * Create a Set containing a single Block. - * - * @param block the block to insert into this set. - */ - public Set(Block block, int index) { - this.index = index; - blocks = new Block[] { block }; - minX = maxX = block.coordinate.x; - minY = maxY = block.coordinate.y; - } - - /** - * Create a Set containing all Blocks in the Collection provided. - * - * @param collection the blocks to add to the set. - */ - public Set(Collection collection, int index) { - - this.index = index; - - if (collection == null || collection.size() == 0) { - //throw new IllegalArgumentException("Empty Set not allowed!"); - blocks = new Block[0]; - minX = maxX = minY = maxY = 0; - return; - } - - this.blocks = collection.toArray(new Block[collection.size()]); - Arrays.sort(this.blocks, new BlockComparator()); - - int tmpMinX = Integer.MAX_VALUE; - int tmpMinY = Integer.MAX_VALUE; - - int tmpMaxX = Integer.MIN_VALUE; - int tmpMaxY = Integer.MIN_VALUE; - - for (int i=0;i tmpMaxX) { - tmpMaxX = block.coordinate.x; - } - - if (block.coordinate.y < tmpMinY) { - tmpMinY = block.coordinate.y; - } - - if (block.coordinate.y > tmpMaxY) { - tmpMaxY = block.coordinate.y; - } - } - - minX = tmpMinX; - maxX = tmpMaxX; - minY = tmpMinY; - maxY = tmpMaxY; - } - - /** - * Create a Set containing all Blocks in the array provided. - * - * The array may not be null or empty. - * - * @param blocks the blocks to add to the set. - */ - public Set(Block [] blocks, int index) { - - this.index = index; - - if (blocks == null || blocks.length == 0) { - throw new IllegalArgumentException("Empty Set not allowed!"); - } - - this.blocks = blocks.clone(); - - Arrays.sort(this.blocks, new BlockComparator()); - - int tmpMinX = Integer.MAX_VALUE; - int tmpMinY = Integer.MAX_VALUE; - - int tmpMaxX = Integer.MIN_VALUE; - int tmpMaxY = Integer.MIN_VALUE; - - for (int i=0;i tmpMaxX) { - tmpMaxX = block.coordinate.x; - } - - if (block.coordinate.y < tmpMinY) { - tmpMinY = block.coordinate.y; - } - - if (block.coordinate.y > tmpMaxY) { - tmpMaxY = block.coordinate.y; - } - } - - minX = tmpMinX; - maxX = tmpMaxX; - minY = tmpMinY; - maxY = tmpMaxY; - } - - /** - * Creates a new containing the same blocks as the set provided. - * - * @param set the set to copy the blocks from. - */ - public Set(Set set, int index) { - - this.index = index; - - minX = set.minX; - maxX = set.maxX; - minY = set.minY; - maxY = set.maxY; - - blocks = set.blocks.clone(); - } - - /** - * Determines if a block may be on the edge of the set, that is, it has neighbors that are not part of the set. - * - * @param b the block to check - * @return if the block may be on the edge of the set. - */ - private boolean onEdge(Block b) { - - for (int i=-1;i<=1;i++) { - for (int j=-1;j<=1;j++) { - if (!(i == 0 && j == 0)) { - - int nx = b.coordinate.x+i; - int ny = b.coordinate.y+j; - - if (nx < 0 || ny < 0) { - return true; - } - - if (!contains(nx, ny)) { - return true; - } - } - } - } - - return false; - } - - /** - * Retrieve the coordinates of the neighbor Blocks of this set. - * - * @param neighbours the neighbor generator. - * @return the coordinates of the neighbor Blocks of this set. - */ - public Coordinate [] getNeighbours(Neighbours neighbours) { - - if (this.neighbours == null) { - - communication = 0; - - HashSet result = new HashSet(); - - for (Block b : blocks) { - - if (onEdge(b)) { - - Coordinate [][] tmp = neighbours.getNeighbours(b.coordinate, true); - - for (int i=0;i<3;i++) { - for (int j=0;j<3;j++) { - if (tmp[i][j] != null) { - - int nx = tmp[i][j].x; - int ny = tmp[i][j].y; - - if (!contains(nx, ny)) { - communication += neighbours.getCommunication(b.coordinate, i-1, j-1); - result.add(new Coordinate(nx, ny)); - } - } - } - } - } - } - - this.neighbours = result.toArray(new Coordinate[result.size()]); - } - - return this.neighbours; - } - - /** - * Return the total amount of external (out of set) communication required by - * the Blocks in this set. - * - * @param neighbours the neighbor generator. - * @return the total amount of external (out of set) communication required by this set. - */ - public int getCommunication(Neighbours neighbours) { - - if (communication == -1) { - getNeighbours(neighbours); - } - - return communication; - } - - /** - * Retrieve the Block at the given index. The index must be between 0 (inclusive) - * and {@link #size()} (exclusive). - * - * @param index the index of the block to retrieve. - * @return the block at the given index. - */ - public Block get(int index) { - - if (index < 0 || index >= blocks.length) { - throw new NoSuchElementException("Index out of bounds " + index); - } - - return blocks[index]; - } - - /** - * Retrieve all blocks of this Set. - * - * @return an array containing all blocks of this set. - */ - public Block [] getAll() { - return blocks.clone(); - } - - /** - * Add all blocks of this Set to the collection. - * - * @param output the collection to add the blocks to. - */ - public void getAll(Collection output) { - for (int i=0;i maxX || y < minY || y > maxY) { - return null; - } - - // FIXME: optimize! - for (int i=0;i(); - } - - subSets.add(subset); - } - - /** - * Add all sets in the provided Collection to list of subsets of this set. - * - * @param collection the sets to add as subsets of this set. - */ - public void addSubSets(Collection collection) { - - if (collection == null) { - throw new NullPointerException("Cannot add null as subsets"); - } - - for (Set s : collection) { - addSubSet(s); - } - } - - /** - * Returns the number of subsets of this set. - * - * @return the number of subsets of this set. - */ - public int countSubSets() { - if (subSets == null) { - return 0; - } - - return subSets.size(); - } - - /** - * Returns the list subsets of this set. - * - * @return the list subsets of this set. - */ - public ArrayList getSubSets() { - return subSets; - } - - @Override - public Iterator iterator() { - return new BlockIterator(blocks); - } + /** The smallest x coordinate found in all blocks of the set. */ + public final int minX; + + /** The largest x coordinate found in all blocks of the set. */ + public final int maxX; + + /** The smallest y coordinate found in all blocks of the set. */ + public final int minY; + + /** The largest y coordinate found in all blocks of the set. */ + public final int maxY; + + /** The blocks in this set. */ + private final Block[] blocks; + + /** A list of subsets of this set. */ + private ArrayList subSets; + + /** A list of blockIDs of possible neighbor blocks of this set. */ + private int[] neighbours; + + /** The amount of external (out of set) communication required by the blocks in this set. */ + private int communication = -1; + + /** The index of this set on the layer it belogs to. */ + public final int index; + + /** A Comparator used to sort blocks on their coordinates (smallest first). */ + private class BlockComparator implements Comparator { + + @Override + public int compare(Block o1, Block o2) { + + if (o1.blockID == o2.blockID) { + return 0; + } + + Coordinate c1 = o1.coordinate; + Coordinate c2 = o2.coordinate; + + if (c1.y < c2.y) { + return -1; + } else if (c1.y == c2.y) { + if (c1.x < c2.x) { + return -1; + } else if (c1.x == c2.x) { + // should not happen ? + return 0; + } + } + + return 1; + } + } + + /** + * Create a Set containing a single Block. + * + * @param block + * the block to insert into this set. + */ + public Set(Block block, int index) { + this.index = index; + blocks = new Block[] { block }; + + Coordinate c = block.coordinate; + + minX = maxX = c.x; + minY = maxY = c.y; + } + + /** + * Create a Set containing all Blocks in the Collection provided. + * + * @param collection + * the blocks to add to the set. + */ + public Set(Collection collection, int index) { + + this.index = index; + + if (collection == null || collection.size() == 0) { + //throw new IllegalArgumentException("Empty Set not allowed!"); + blocks = new Block[0]; + minX = maxX = minY = maxY = 0; + return; + } + + this.blocks = collection.toArray(new Block[collection.size()]); + Arrays.sort(this.blocks, new BlockComparator()); + + int tmpMinX = Integer.MAX_VALUE; + int tmpMinY = Integer.MAX_VALUE; + + int tmpMaxX = Integer.MIN_VALUE; + int tmpMaxY = Integer.MIN_VALUE; + + for (int i = 0; i < this.blocks.length; i++) { + + Block block = this.blocks[i]; + + Coordinate c = block.coordinate; + + if (c.x < tmpMinX) { + tmpMinX = c.x; + } + + if (c.x > tmpMaxX) { + tmpMaxX = c.x; + } + + if (c.y < tmpMinY) { + tmpMinY = c.y; + } + + if (c.y > tmpMaxY) { + tmpMaxY = c.y; + } + } + + minX = tmpMinX; + maxX = tmpMaxX; + minY = tmpMinY; + maxY = tmpMaxY; + } + + /** + * Create a Set containing all Blocks in the array provided. + * + * The array may not be null or empty. + * + * @param blocks + * the blocks to add to the set. + */ + public Set(Block[] blocks, int index) { + + this.index = index; + + if (blocks == null || blocks.length == 0) { + throw new IllegalArgumentException("Empty Set not allowed!"); + } + + this.blocks = blocks.clone(); + + Arrays.sort(this.blocks, new BlockComparator()); + + int tmpMinX = Integer.MAX_VALUE; + int tmpMinY = Integer.MAX_VALUE; + + int tmpMaxX = Integer.MIN_VALUE; + int tmpMaxY = Integer.MIN_VALUE; + + for (int i = 0; i < this.blocks.length; i++) { + + Block block = this.blocks[i]; + + Coordinate c = block.coordinate; + + if (c.x < tmpMinX) { + tmpMinX = c.x; + } + + if (c.x > tmpMaxX) { + tmpMaxX = c.x; + } + + if (c.y < tmpMinY) { + tmpMinY = c.y; + } + + if (c.y > tmpMaxY) { + tmpMaxY = c.y; + } + } + + minX = tmpMinX; + maxX = tmpMaxX; + minY = tmpMinY; + maxY = tmpMaxY; + } + + /** + * Creates a new containing the same blocks as the set provided. + * + * @param set + * the set to copy the blocks from. + */ + public Set(Set set, int index) { + + this.index = index; + + minX = set.minX; + maxX = set.maxX; + minY = set.minY; + maxY = set.maxY; + + blocks = set.blocks.clone(); + } + + + /** + * Retrieve the edges of this set, that is, an array of lines which mark edges between blocks which are part of this set and + * blocks that are not. + * + * @return an array of Lines that together form the border between this set and its neighbors. + */ + public Line [] getEdges() { + + ArrayList result = new ArrayList(); + + for (Block b : blocks) { + getEdges(b, result); + } + + return result.toArray(new Line[result.size()]); + } + + /** + * Determines if a block may be on the edge of the set, that is, it has neighbors that are not part of the set. If so, one or + * more Lines are added to the result array that mark the edges between the block and these neighbors. + * + * @param b + * the block to check + * @param result + * the output ArrayList in which to store the result. + * + * @return if the block may be on the edge of the set. + */ + private void getEdges(Block b, ArrayList result) { + + int [][] neighbours = b.getNeighbours(); + + if (neighbours[0][1] > 0 && !contains(neighbours[0][1])) { + result.add(new Line(b.coordinate.offset(0, 1), b.coordinate.offset(1, 1))); + } + + if (neighbours[1][0] > 0 && !contains(neighbours[1][0])) { + result.add(new Line(b.coordinate, b.coordinate.offset(0, 1))); + } + + if (neighbours[1][2] > 0 && !contains(neighbours[1][2])) { + result.add(new Line(b.coordinate.offset(1, 0), b.coordinate.offset(1, 1))); + } + + if (b.coordinate.y == 0 || (neighbours[2][1] > 0 && !contains(neighbours[2][1]))) { + result.add(new Line(b.coordinate, b.coordinate.offset(1, 0))); + } + } + + /** + * Determines if a block may be on the edge of the set, that is, it has neighbors that are not part of the set. + * + * @param b + * the block to check + * @return if the block may be on the edge of the set. + */ + private boolean onEdge(Block b) { + + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (!(i == 0 && j == 0)) { + + Coordinate c = b.coordinate; + + int nx = c.x + i; + int ny = c.y + j; + + if (nx < 0 || ny < 0) { + return true; + } + + if (!contains(nx, ny)) { + return true; + } + } + } + } + + return false; + } + + /** + * Retrieve the coordinates of the neighbor Blocks of this set. + * + * @return the coordinates of the neighbor Blocks of this set. + */ + public int[] getNeighbours() { + + if (neighbours == null) { + + this.communication = 0; + + HashSet result = new HashSet(); + + for (Block b : blocks) { + + if (onEdge(b)) { + + int[][] neighbours = b.getNeighbours(); + int[][] communication = b.getCommunication(); + + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + if (neighbours[y][x] > 0 && !contains(neighbours[y][x])) { + this.communication += communication[y][x]; + result.add(neighbours[y][x]); + } + } + } + } + } + + neighbours = new int[result.size()]; + + int index = 0; + + for (int tmp : result) { + neighbours[index++] = tmp; + } + } + + return neighbours; + } + + /** + * Return the total amount of external (out of set) communication required by the Blocks in this set. + * + * @return the total amount of external (out of set) communication required by this set. + */ + public int getCommunication() { + + if (communication == -1) { + getNeighbours(); + } + + return communication; + } + + /** + * Retrieve the Block at the given index. The index must be between 0 (inclusive) and {@link #size()} (exclusive). + * + * @param index + * the index of the block to retrieve. + * @return the block at the given index. + */ + public Block get(int index) { + + if (index < 0 || index >= blocks.length) { + throw new NoSuchElementException("Index out of bounds " + index); + } + + return blocks[index]; + } + + /** + * Retrieve all blocks of this Set. + * + * @return an array containing all blocks of this set. + */ + public Block[] getAll() { + return blocks.clone(); + } + + /** + * Add all blocks of this Set to the collection. + * + * @param output + * the collection to add the blocks to. + */ + public void getAll(Collection output) { + for (int i = 0; i < blocks.length; i++) { + output.add(blocks[i]); + } + } + + /** + * Test if this set contains a Block at location (x,y). + * + * @param x + * the x coordinate of the location to test. + * @param y + * the y coordinate of the location to test. + * @return if this set contains a block at the specified location. + */ + public boolean contains(int x, int y) { + // TODO: Can this go faster ? + return (get(x, y) != null); + } + + /** + * Test if this set contains a Block with a given blockID. + * + * @param blockID + * the ID of the block to find. + * + * @return if this set contains a block at the specified location. + */ + public boolean contains(int blockID) { + + for (int i=0;i maxX || y < minY || y > maxY) { + return null; + } + + // TODO: optimize? + for (int i = 0; i < blocks.length; i++) { + + Coordinate c = blocks[i].coordinate; + + if (c.x == x && c.y == y) { + return blocks[i]; + } + } + + return null; + } + + /** + * Returns the number of Blocks in this set. + * + * @return the number of blocks in this set. + */ + public int size() { + return blocks.length; + } + + /** + * Returns the width of the area defined by the blocks in this set. + * + * @return the width of this set. + */ + public int getWidth() { + return maxX - minX + 1; + } + + /** + * Returns the height of the area defined by the blocks in this set. + * + * @return the height of this set. + */ + public int getHeight() { + return maxY - minY + 1; + } + + /** + * Add a set to the list of subsets of this set. + * + * @param subset + * the set to add as a subset of this set. + */ + public void addSubSet(Set subset) { + + if (subset == null) { + throw new NullPointerException("Cannot add null as subset"); + } + + if (subSets == null) { + subSets = new ArrayList(); + } + + subSets.add(subset); + } + + /** + * Add all sets in the provided Collection to list of subsets of this set. + * + * @param collection + * the sets to add as subsets of this set. + */ + public void addSubSets(Collection collection) { + + if (collection == null) { + throw new NullPointerException("Cannot add null as subsets"); + } + + for (Set s : collection) { + addSubSet(s); + } + } + + /** + * Returns the number of subsets of this set. + * + * @return the number of subsets of this set. + */ + public int countSubSets() { + if (subSets == null) { + return 0; + } + + return subSets.size(); + } + + /** + * Returns the list subsets of this set. + * + * @return the list subsets of this set. + */ + public ArrayList getSubSets() { + return subSets; + } + + @Override + public Iterator iterator() { + return new BlockIterator(blocks); + } } diff --git a/src/nl/esciencecenter/esalsa/util/Statistics.java b/src/nl/esciencecenter/esalsa/util/Statistics.java index 23e839f..bc70f62 100644 --- a/src/nl/esciencecenter/esalsa/util/Statistics.java +++ b/src/nl/esciencecenter/esalsa/util/Statistics.java @@ -19,7 +19,6 @@ import nl.esciencecenter.esalsa.util.Layer; import nl.esciencecenter.esalsa.util.Layers; -import nl.esciencecenter.esalsa.util.Neighbours; import nl.esciencecenter.esalsa.util.Set; /** @@ -33,61 +32,62 @@ */ public class Statistics { - private final Layers layers; - private final Neighbours neighbours; - - /** - * Create a Statistics for a given set of layers. - * - * @param layers the layer for which the statistics must be printed. - * @param neighbours the neighbour function to use. - */ - public Statistics(Layers layers, Neighbours neighbours) { - this.layers = layers; - this.neighbours = neighbours; - } - - /** - * Print statistics on work distribution and communication in layer to console. - * - * @param layer the layer to print statistics for. - */ - private void printStatistics(Layer layer, PrintStream output) { - - if (layer == null) { - return; - } + private final Layers layers; - output.println("Statistics for layer: " + layer.name); - output.println(" Sets: " + layer.size()); - - for (int i=0;ilayer to the provided print stream. - * - * @param layer the name of the layer to print statistics for, or ALL to print statistics on all layers. - * @param output the stream to print the statistics to. - */ - public void printStatistics(String layer, PrintStream output) throws Exception { - - if (layer.equalsIgnoreCase("ALL")) { - printStatistics(layers.get("CLUSTERS"), output); - printStatistics(layers.get("NODES"), output); - printStatistics(layers.get("CORES"), output); - } else { - Layer l = layers.get(layer); + /** + * Print statistics on work distribution and communication in layer to console. + * + * @param layer + * the layer to print statistics for. + */ + private void printStatistics(Layer layer, PrintStream output) { - if (l == null) { - throw new Exception("Layer " + layer + " not found!"); - } - - printStatistics(l, output); - } - } + if (layer == null) { + return; + } + + output.println("Statistics for layer: " + layer.name); + output.println(" Sets: " + layer.size()); + + for (int i = 0; i < layer.size(); i++) { + Set tmp = layer.get(i); + // output.println(" " + i + " (" + tmp.minX + "," + tmp.minY + ") - (" + tmp.maxX + "," + tmp.maxY + ") " + + output.println(" " + i + " " + tmp.size() + " " + tmp.getCommunication()); + } + } + + /** + * Print statistics on work distribution and communication in layer to the provided print stream. + * + * @param layer + * the name of the layer to print statistics for, or ALL to print statistics on all layers. + * @param output + * the stream to print the statistics to. + */ + public void printStatistics(String layer, PrintStream output) throws Exception { + + if (layer.equalsIgnoreCase("ALL")) { + printStatistics(layers.get("CLUSTERS"), output); + printStatistics(layers.get("NODES"), output); + printStatistics(layers.get("CORES"), output); + } else { + Layer l = layers.get(layer); + + if (l == null) { + throw new Exception("Layer " + layer + " not found!"); + } + + printStatistics(l, output); + } + } } diff --git a/src/nl/esciencecenter/esalsa/util/Topography.java b/src/nl/esciencecenter/esalsa/util/Topography.java index a9d927d..0e320d7 100644 --- a/src/nl/esciencecenter/esalsa/util/Topography.java +++ b/src/nl/esciencecenter/esalsa/util/Topography.java @@ -25,10 +25,10 @@ import org.slf4j.LoggerFactory; /** - * Topography represents an ocean bottom topography definition used as in POP. + * Topography represents an ocean bottom topography definition used as in POP. * - * A bottom topography contains the index (> 0) of the deepest ocean level for each grid point. A value of 0 at any position - * indicates a land point. + * A bottom topography contains the index (> 0) of the deepest ocean level for each grid point. A value of 0 at any position + * indicates a land point. * * @author Jason Maassen * @version 1.0 @@ -37,309 +37,337 @@ */ public class Topography { - /** A logger used for debugging */ - private static final Logger logger = LoggerFactory.getLogger(Topography.class); - - /** The topography data as read from the input file */ - private final int [][] topography; - - /** The width of the topography */ - public final int width; - - /** The height of the topography */ - public final int height; - - /** The maximum value found in the topography */ - public final int max; - - /** The minimum value found in the topopgrapy */ - public final int min; - - /** - * Create a new topography by reading the data from the input file. - * - * @param width the width of the topography to create. - * @param height the height of the topography to create. - * @param inputfile the input file from which to read the topography data. - * @throws Exception if the topography could not be created. - */ - public Topography(int width, int height, String inputfile) throws Exception { - - int tmpMax = Integer.MIN_VALUE; - int tmpMin = Integer.MAX_VALUE; - - this.width = width; - this.height = height; - topography = new int[width][height]; - - int work = 0; - long sum = 0; - - DataInputStream in = null; - - try { - in = new DataInputStream(new BufferedInputStream(new FileInputStream(new File(inputfile)))); - - for (int y=0;y tmpMax) { - tmpMax = tmp; - } - - if (tmp < tmpMin) { - tmpMin = tmp; - } - - if (tmp > 0) { - work++; - } - - sum += tmp; - } - } - - in.close(); - } catch (Exception e) { - throw new Exception("Failed to read topography from file " + inputfile, e); - } finally { - try { - in.close(); - } catch (Exception e) { - // ignored - } - } - - max = tmpMax; - min = tmpMin; - - if (logger.isDebugEnabled()) { - logger.debug("Topography contains " + work + " nonzero fields (sum = " + sum + ")"); - } - } - - /** - * Create a new topography from existing data. - * - * @param data a square int matrix containing the data to store in the topography. - * @throws Exception if the topography could not be created. - */ - public Topography(int [][] data) throws Exception { - - int tmpMax = Integer.MIN_VALUE; - int tmpMin = Integer.MAX_VALUE; - - this.width = data.length; - this.height = data[0].length; - - int work = 0; - long sum = 0; - - topography = data.clone(); - - for (int y=0;y tmpMax) { - tmpMax = tmp; - } - - if (tmp < tmpMin) { - tmpMin = tmp; - } - - if (tmp > 0) { - work++; - } - - sum += tmp; - } - } - - max = tmpMax; - min = tmpMin; - - if (logger.isDebugEnabled()) { - logger.debug("Topography contains " + work + " nonzero fields (sum = " + sum + ")"); - } - } - - /** - * Create a new topography by down scaling an existing one. - * - * The down scaling will be performed block wise. The original topography will be divided in blocks of size - * (blockWidth x blockHeight). Each block will become a single point in the new topography. The value of this point will be the - * sum of all values in the block in the original topography. - * - * @param orig the original topography. - * @param blockWidth the block width to use for down scaling. - * @param blockHeight the block height to use for down scaling. - * @throws Exception if the topography could not be scaled down. - */ - public Topography(Topography orig, int blockWidth, int blockHeight) throws Exception { - - int tmpMax = Integer.MIN_VALUE; - int tmpMin = Integer.MAX_VALUE; - - if (orig.width % blockWidth != 0) { - throw new Exception("Illegal blockWidth"); - } - - if (orig.height % blockHeight != 0) { - throw new Exception("Illegal blockHeight"); - } - - this.width = orig.width / blockWidth; - this.height = orig.height / blockHeight; - topography = new int[width][height]; - - int work = 0; - long sum = 0; - - for (int y=0;y tmpMax) { - tmpMax = tmp; - } - - if (tmp < tmpMin) { - tmpMin = tmp; - } - - if (tmp > 0) { - work++; - } - - sum += tmp; - } - } - - max = tmpMax; - min = tmpMin; - - if (logger.isDebugEnabled()) { - logger.debug("Topography contains " + work + " nonzero fields (sum = " + sum + ")"); - } - } - - /** - * Returns the maximum value found in a rectangular area of the topography. - * - * @param x the x position of the rectangle. - * @param y the y position of the rectangle. - * @param w the width of the rectangle. - * @param h the height of the rectangle. - * @return the maximum value found in the specified rectangular area. - */ - public int getRectangleMax(int x, int y, int w, int h) { - - int max = Integer.MIN_VALUE; - - for (int i=x;i max) { - max = tmp; - } - } - } - - return max; - } - - /** - * Returns the average value found in a rectangular area of the topography. - * - * @param x the x position of the rectangle. - * @param y the y position of the rectangle. - * @param w the width of the rectangle. - * @param h the height of the rectangle. - * @return the average value found in the specified rectangular area. - */ - public int getRectangleAvg(int x, int y, int w, int h) { - - int sum = 0; - int count = 0; - - for (int i=x;i 0) { - return sum/count; - } - - return 0; - } - - /** - * Returns the sum of all values found in a rectangular area of the topography. - * - * @param x the x position of the rectangle. - * @param y the y position of the rectangle. - * @param w the width of the rectangle. - * @param h the height of the rectangle. - * @return the sum of all value in the specified rectangular area. - */ - public int getRectangleSum(int x, int y, int w, int h) { - - int sum = 0; - - for (int i=x;i 0) { - work++; - } - } - } - - return work; - } - - /** - * Retrieves the value of a specific location of the topography. - * - * @param x the x coordinate of the value to retrieve. - * @param y the y coordinate of the value to retrieve. - * @return the value found at the specified location in the topography. - */ - public int get(int x, int y) { - return topography[x][y]; - } + /** A logger used for debugging */ + private static final Logger logger = LoggerFactory.getLogger(Topography.class); + + /** The topography data as read from the input file */ + private final int[][] topography; + + /** The width of the topography */ + public final int width; + + /** The height of the topography */ + public final int height; + + /** The maximum value found in the topography */ + public final int max; + + /** The minimum value found in the topopgrapy */ + public final int min; + + /** + * Create a new topography by reading the data from the input file. + * + * @param width + * the width of the topography to create. + * @param height + * the height of the topography to create. + * @param inputfile + * the input file from which to read the topography data. + * @throws Exception + * if the topography could not be created. + */ + public Topography(int width, int height, String inputfile) throws Exception { + + int tmpMax = Integer.MIN_VALUE; + int tmpMin = Integer.MAX_VALUE; + + this.width = width; + this.height = height; + topography = new int[width][height]; + + int work = 0; + long sum = 0; + + DataInputStream in = null; + + try { + in = new DataInputStream(new BufferedInputStream(new FileInputStream(new File(inputfile)))); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int tmp = in.readInt(); + + //topography[x][height-y-1] = tmp; + topography[x][y] = tmp; + + if (tmp > tmpMax) { + tmpMax = tmp; + } + + if (tmp < tmpMin) { + tmpMin = tmp; + } + + if (tmp > 0) { + work++; + } + + sum += tmp; + } + } + + in.close(); + } catch (Exception e) { + throw new Exception("Failed to read topography from file " + inputfile, e); + } finally { + try { + in.close(); + } catch (Exception e) { + // ignored + } + } + + max = tmpMax; + min = tmpMin; + + if (logger.isDebugEnabled()) { + logger.debug("Topography contains " + work + " nonzero fields (sum = " + sum + ")"); + } + } + + /** + * Create a new topography from existing data. + * + * @param data + * a square int matrix containing the data to store in the topography. + * @throws Exception + * if the topography could not be created. + */ + public Topography(int[][] data) throws Exception { + + int tmpMax = Integer.MIN_VALUE; + int tmpMin = Integer.MAX_VALUE; + + this.width = data.length; + this.height = data[0].length; + + int work = 0; + long sum = 0; + + topography = data.clone(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + int tmp = topography[x][y]; + + if (tmp > tmpMax) { + tmpMax = tmp; + } + + if (tmp < tmpMin) { + tmpMin = tmp; + } + + if (tmp > 0) { + work++; + } + + sum += tmp; + } + } + + max = tmpMax; + min = tmpMin; + + if (logger.isDebugEnabled()) { + logger.debug("Topography contains " + work + " nonzero fields (sum = " + sum + ")"); + } + } + + /** + * Create a new topography by down scaling an existing one. + * + * The down scaling will be performed block wise. The original topography will be divided in blocks of size (blockWidth x + * blockHeight). Each block will become a single point in the new topography. The value of this point will be the sum of all + * values in the block in the original topography. + * + * @param orig + * the original topography. + * @param blockWidth + * the block width to use for down scaling. + * @param blockHeight + * the block height to use for down scaling. + * @throws Exception + * if the topography could not be scaled down. + */ + public Topography(Topography orig, int blockWidth, int blockHeight) throws Exception { + + int tmpMax = Integer.MIN_VALUE; + int tmpMin = Integer.MAX_VALUE; + + if (orig.width % blockWidth != 0) { + throw new Exception("Illegal blockWidth"); + } + + if (orig.height % blockHeight != 0) { + throw new Exception("Illegal blockHeight"); + } + + this.width = orig.width / blockWidth; + this.height = orig.height / blockHeight; + topography = new int[width][height]; + + int work = 0; + long sum = 0; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + int tmp = orig.getRectangleSum(x * blockWidth, y * blockHeight, blockWidth, blockHeight); + + topography[x][y] = tmp; + + if (tmp > tmpMax) { + tmpMax = tmp; + } + + if (tmp < tmpMin) { + tmpMin = tmp; + } + + if (tmp > 0) { + work++; + } + + sum += tmp; + } + } + + max = tmpMax; + min = tmpMin; + + if (logger.isDebugEnabled()) { + logger.debug("Topography contains " + work + " nonzero fields (sum = " + sum + ")"); + } + } + + /** + * Returns the maximum value found in a rectangular area of the topography. + * + * @param x + * the x position of the rectangle. + * @param y + * the y position of the rectangle. + * @param w + * the width of the rectangle. + * @param h + * the height of the rectangle. + * @return the maximum value found in the specified rectangular area. + */ + public int getRectangleMax(int x, int y, int w, int h) { + + int max = Integer.MIN_VALUE; + + for (int i = x; i < x + w && i < width; i++) { + for (int j = y; j < y + h && j < height; j++) { + int tmp = topography[i][j]; + if (tmp > max) { + max = tmp; + } + } + } + + return max; + } + + /** + * Returns the average value found in a rectangular area of the topography. + * + * @param x + * the x position of the rectangle. + * @param y + * the y position of the rectangle. + * @param w + * the width of the rectangle. + * @param h + * the height of the rectangle. + * @return the average value found in the specified rectangular area. + */ + public int getRectangleAvg(int x, int y, int w, int h) { + + int sum = 0; + int count = 0; + + for (int i = x; i < x + w && i < width; i++) { + for (int j = y; j < y + h && j < height; j++) { + sum += topography[i][j]; + count++; + } + } + + if (count > 0) { + return sum / count; + } + + return 0; + } + + /** + * Returns the sum of all values found in a rectangular area of the topography. + * + * @param x + * the x position of the rectangle. + * @param y + * the y position of the rectangle. + * @param w + * the width of the rectangle. + * @param h + * the height of the rectangle. + * @return the sum of all value in the specified rectangular area. + */ + public int getRectangleSum(int x, int y, int w, int h) { + + int sum = 0; + + for (int i = x; i < x + w && i < width; i++) { + for (int j = y; j < y + h && j < height; j++) { + sum += topography[i][j]; + } + } + + return sum; + } + + /** + * Returns the amount of work (that is, non-0 values) found in a rectangular area of the topography. + * + * @param x + * the x position of the rectangle. + * @param y + * the y position of the rectangle. + * @param w + * the width of the rectangle. + * @param h + * the height of the rectangle. + * @return the amount of work in the specified rectangular area. + */ + public int getRectangleWork(int x, int y, int w, int h) { + + int work = 0; + + for (int i = x; i < x + w && i < width; i++) { + for (int j = y; j < y + h && j < height; j++) { + if (topography[i][j] > 0) { + work++; + } + } + } + + return work; + } + + /** + * Retrieves the value of a specific location of the topography. + * + * @param x + * the x coordinate of the value to retrieve. + * @param y + * the y coordinate of the value to retrieve. + * @return the value found at the specified location in the topography. + */ + public int get(int x, int y) { + return topography[x][y]; + } } diff --git a/src/nl/esciencecenter/esalsa/util/TopographyCanvas.java b/src/nl/esciencecenter/esalsa/util/TopographyCanvas.java index 685cb75..049fc35 100644 --- a/src/nl/esciencecenter/esalsa/util/TopographyCanvas.java +++ b/src/nl/esciencecenter/esalsa/util/TopographyCanvas.java @@ -29,15 +29,14 @@ import javax.imageio.ImageIO; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * TopographyCanvas is an extension of {@link java.awt.Canvas} is capable of showing a {@link Topography}. + * TopographyCanvas is an extension of {@link java.awt.Canvas} is capable of showing a {@link Topography}. *

* In addition, one or more named layers can be created on top of the topography that can be used to draw lines, fill blocks, or - * display text on top of the topography. + * display text on top of the topography. * * @author Jason Maassen * @version 1.0 @@ -45,331 +44,363 @@ * */ public class TopographyCanvas extends Canvas { - - /** Generated */ - private static final long serialVersionUID = -8649482969047330657L; - - /** A logger used for debugging */ - private static final Logger logger = LoggerFactory.getLogger(TopographyCanvas.class); - - /** A BufferedImage containing a image showing the topography. */ - private final BufferedImage topo; - - /** A store for the various layers. */ - private final LinkedHashMap layers = new LinkedHashMap(); - - /** The topography we are displaying */ - public final Topography topography; - - /** The grid overlay on the topography. */ - public final Grid grid; - - /** - * Create a TopographyCanvas for a specified topography and grid. - * - * @param topography the Topography to use. - * @param grid the grid to use. - * @see Topography - * @see Grid - */ - public TopographyCanvas(Topography topography, Grid grid) { - - this.topography = topography; - this.grid = grid; - - int range = topography.max - topography.min + 1; - - if (logger.isDebugEnabled()) { - logger.debug("Color range: " + topography.min + " ... " + topography.max + " (" + range + ")"); - } - - int [] colors = new int[range]; - colors[0] = 0xFFFFFFFF; - - for (int i=1;i end.x ? start.x : end.x; - int highY = start.y > end.y ? start.y : end.y; - - // We should now fill the area between (lowX, lowY) (inclusive) and (highX, highY) (exclusive) - Graphics2D g = tmp.createGraphics(); - g.setColor(color); - g.fillRect(lowX, lowY, (highX-lowX), (highY-lowY)); - } - - /** - * Fill a block in a specified layer with a color. - * - * @param layer the layer at which to draw. - * @param x the x coordinate of the block. - * @param y the y coordinate of the block. - * @param color the color to fill the block with. - * @throws Exception if the specified layer does not exist. - */ - public void fillBlock(String layer, int x, int y, Color color) throws Exception { - int ny = grid.height-y-1; - fill(layer, new Coordinate(x*grid.blockWidth, ny*grid.blockHeight), - new Coordinate((x+1)*grid.blockWidth, (ny+1)*grid.blockHeight), color); - } - - /** - * Draw a line with a certain color and width in a specified layer. - * - * @param layer the layer at which to draw. - * @param line the line to draw. - * @param color the color of the line. - * @param lineWidth the width of the line. - * @throws Exception if the layer does not exist. - */ - public void draw(String layer, Line line, Color color, float lineWidth) throws Exception { - - // Calculate the scale of this line. - int sx = topography.width / grid.width; - int sy = topography.height / grid.height; - - Graphics2D g = (Graphics2D) getLayer(layer).getGraphics(); - - g.setColor(color); - g.setStroke(new BasicStroke(lineWidth)); - g.drawLine(line.start.x*sx, (topography.height-line.start.y*sy), line.end.x*sx, (topography.height-line.end.y*sy)); - } - - /** - * Draw a point with a certain color in a specified layer. - * - * @param layer the layer at which to draw. - * @param x x coordinate of point to draw. - * @param y y coordinate of point to draw. - * @param color the color of the point. - * @throws Exception if the layer does not exist. - */ - public void draw(String layer, int x, int y, Color color) throws Exception { - getLayer(layer).setRGB(x, topography.height-y-1, color.getRGB()); - } - - /** - * Retrieves the BufferedImage associated with a layer. - * - * @param layer the name of the layer. - * @return the BufferedImage associated with a layer. - * @throws Exception if the layer does not exist. - * @see BufferedImage - */ - private BufferedImage getLayer(String layer) throws Exception { - - BufferedImage tmp = layers.get(layer); - - if (tmp == null) { - throw new Exception("No such layer: " + layer); - } - - return tmp; - } - - /** - * Add a layer. - * - * @param layer the name of the layer to add. - * @throws Exception if a layer with the given name already exists. - */ - public void addLayer(String layer) throws Exception { - - if (layers.containsKey(layer)) { - throw new Exception("Layer " + layer + " + already exists!"); - } - - BufferedImage tmp = new BufferedImage(topography.width, topography.height, BufferedImage.TYPE_INT_ARGB); - - for (int x=0;x set = layers.keySet(); - return set.toArray(new String [set.size()]); - } - - /** - * Clear all layers. - */ - public void clearAll() { - - for (String name : layers.keySet()) { - - try { - clearLayer(name); - } catch (Exception e) { - throw new Error("Internal error while clearing layer " + name); - } - } - } - - @Override - public void paint(Graphics graphics) { - update(graphics); - } - - private BufferedImage getCurrentImage(int w, int h) { - - // Create buffered image of topography - BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = (Graphics2D) img.getGraphics(); - - g2.drawImage(topo, 0, 0, w, h, 0, 0, topography.width, topography.height, null); - - for (BufferedImage tmp : layers.values()) { - g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); - } - - return img; - } - - @Override - public void update(Graphics graphics) { - - int w = getWidth(); - int h = getHeight(); - - BufferedImage img = getCurrentImage(w, h); - -/* - // Create buffered image of topography - BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = (Graphics2D) img.getGraphics(); - - g2.drawImage(topo, 0, 0, w, h, 0, 0, topography.width, topography.height, null); - - /* - LinkedList reverse = new LinkedList(); - - for (BufferedImage tmp : layers.values()) { - reverse.addFirst(tmp); - } - - for (BufferedImage tmp : reverse) { - g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); - } - - for (BufferedImage tmp : layers.values()) { - g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); - } -*/ - Graphics2D g2 = (Graphics2D) graphics; - g2.drawImage(img, 0, 0, w, h, 0, 0, w, h, null); - } - - /** - * Save the current image as a png file. - * - * @param file the filename of the file to save. - * @throws IOException if the file could not be saved. - */ - public void save(String file) throws IOException { - -// int w = topography.width; - //int h = topography.height; - - // Create buffered image of topography - - // BufferedImage img = getCurrentImage(topography.width, topography.height); - - /* - BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = (Graphics2D) img.getGraphics(); - - g2.drawImage(topo, 0, 0, w, h, 0, 0, topography.width, topography.height, null); - */ - /* - LinkedList reverse = new LinkedList(); - - for (BufferedImage tmp : layers.values()) { - reverse.addFirst(tmp); - } - - BufferedImage bi = reverse.removeLast(); - reverse.addFirst(bi); - - for (BufferedImage tmp : reverse) { - g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); - } - */ - - ImageIO.write(getCurrentImage(topography.width, topography.height), "png", new File(file)); - - System.out.println("Done writing " + file); - } + + /** Generated */ + private static final long serialVersionUID = -8649482969047330657L; + + /** A logger used for debugging */ + private static final Logger logger = LoggerFactory.getLogger(TopographyCanvas.class); + + /** A BufferedImage containing a image showing the topography. */ + private final BufferedImage topo; + + /** A store for the various layers. */ + private final LinkedHashMap layers = new LinkedHashMap(); + + /** The topography we are displaying */ + public final Topography topography; + + /** The grid overlay on the topography. */ + public final Grid grid; + + /** + * Create a TopographyCanvas for a specified topography and grid. + * + * @param topography + * the Topography to use. + * @param grid + * the grid to use. + * @see Topography + * @see Grid + */ + public TopographyCanvas(Topography topography, Grid grid) { + + this.topography = topography; + this.grid = grid; + + int range = topography.max - topography.min + 1; + + if (logger.isDebugEnabled()) { + logger.debug("Color range: " + topography.min + " ... " + topography.max + " (" + range + ")"); + } + + int[] colors = new int[range]; + colors[0] = 0xFFFFFFFF; + + for (int i = 1; i < range; i++) { + int tmp = 255 - (int) ((255.0 / topography.max) * i); + colors[i] = (0xFF000000 | tmp); + } + + // Create the topography image + topo = new BufferedImage(topography.width, topography.height, BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < topography.width; x++) { + for (int y = 0; y < topography.height; y++) { + // Flip image, as the topography is stored upside down! + topo.setRGB(x, topography.height - y - 1, colors[topography.get(x, y)]); + } + } + } + + /** + * Fill a rectangle in a specified layer with a color. + * + * The rectangle is specified as the smallest rectangular area between to coordinates. + * + * @param layer + * the layer at which to draw. + * @param start + * the first coordinate that defines the rectangle. + * @param end + * the second coordinate that defines the rectangle. + * @param color + * the color to fill the rectangle with. + * @throws Exception + * if the specified layer does not exist. + */ + public void fill(String layer, Coordinate start, Coordinate end, Color color) throws Exception { + + BufferedImage tmp = getLayer(layer); + + if (start.x == end.x || start.y == end.y) { + // Nothing to fill! + return; + } + + int lowX = start.x < end.x ? start.x : end.x; + int lowY = start.y < end.y ? start.y : end.y; + + int highX = start.x > end.x ? start.x : end.x; + int highY = start.y > end.y ? start.y : end.y; + + // We should now fill the area between (lowX, lowY) (inclusive) and (highX, highY) (exclusive) + Graphics2D g = tmp.createGraphics(); + g.setColor(color); + g.fillRect(lowX, lowY, (highX - lowX), (highY - lowY)); + } + + /** + * Fill a block in a specified layer with a color. + * + * @param layer + * the layer at which to draw. + * @param x + * the x coordinate of the block. + * @param y + * the y coordinate of the block. + * @param color + * the color to fill the block with. + * @throws Exception + * if the specified layer does not exist. + */ + public void fillBlock(String layer, int x, int y, Color color) throws Exception { + int ny = grid.height - y - 1; + fill(layer, new Coordinate(x * grid.blockWidth, ny * grid.blockHeight), new Coordinate((x + 1) * grid.blockWidth, + (ny + 1) * grid.blockHeight), color); + } + + /** + * Draw a line with a certain color and width in a specified layer. + * + * @param layer + * the layer at which to draw. + * @param line + * the line to draw. + * @param color + * the color of the line. + * @param lineWidth + * the width of the line. + * @throws Exception + * if the layer does not exist. + */ + public void draw(String layer, Line line, Color color, float lineWidth) throws Exception { + + // Calculate the scale of this line. + int sx = topography.width / grid.width; + int sy = topography.height / grid.height; + + Graphics2D g = (Graphics2D) getLayer(layer).getGraphics(); + + g.setColor(color); + g.setStroke(new BasicStroke(lineWidth)); + g.drawLine(line.start.x * sx, (topography.height - line.start.y * sy), line.end.x * sx, (topography.height - line.end.y + * sy)); + } + + /** + * Draw a point with a certain color in a specified layer. + * + * @param layer + * the layer at which to draw. + * @param x + * x coordinate of point to draw. + * @param y + * y coordinate of point to draw. + * @param color + * the color of the point. + * @throws Exception + * if the layer does not exist. + */ + public void draw(String layer, int x, int y, Color color) throws Exception { + getLayer(layer).setRGB(x, topography.height - y - 1, color.getRGB()); + } + + /** + * Retrieves the BufferedImage associated with a layer. + * + * @param layer + * the name of the layer. + * @return the BufferedImage associated with a layer. + * @throws Exception + * if the layer does not exist. + * @see BufferedImage + */ + private BufferedImage getLayer(String layer) throws Exception { + + BufferedImage tmp = layers.get(layer); + + if (tmp == null) { + throw new Exception("No such layer: " + layer); + } + + return tmp; + } + + /** + * Add a layer. + * + * @param layer + * the name of the layer to add. + * @throws Exception + * if a layer with the given name already exists. + */ + public void addLayer(String layer) throws Exception { + + if (layers.containsKey(layer)) { + throw new Exception("Layer " + layer + " + already exists!"); + } + + BufferedImage tmp = new BufferedImage(topography.width, topography.height, BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < topography.width; x++) { + for (int y = 0; y < topography.height; y++) { + tmp.setRGB(x, topography.height - y - 1, 0x00000000); + } + } + + layers.put(layer, tmp); + } + + /** + * Delete a layer. + * + * @param layer + * the name of the layer to delete. + * @return if the layer existed. + */ + public boolean deleteLayer(String layer) { + return (layers.remove(layer) != null); + } + + /** + * Clears a layer by setting all its pixels to transparent. + * + * @param layer + * the name of the layer to clear. + * @throws Exception + * if the layer does not exist. + */ + public void clearLayer(String layer) throws Exception { + + BufferedImage tmp = getLayer(layer); + + for (int x = 0; x < topography.width; x++) { + for (int y = 0; y < topography.height; y++) { + tmp.setRGB(x, topography.height - y - 1, 0x00000000); + } + } + } + + /** + * Returns the names of the currently defined layers. + * + * @return an array containing the names of the currently defined layers. + */ + public String[] listLayers() { + Set set = layers.keySet(); + return set.toArray(new String[set.size()]); + } + + /** + * Clear all layers. + */ + public void clearAll() { + + for (String name : layers.keySet()) { + + try { + clearLayer(name); + } catch (Exception e) { + throw new Error("Internal error while clearing layer " + name); + } + } + } + + @Override + public void paint(Graphics graphics) { + update(graphics); + } + + private BufferedImage getCurrentImage(int w, int h) { + + // Create buffered image of topography + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = (Graphics2D) img.getGraphics(); + + g2.drawImage(topo, 0, 0, w, h, 0, 0, topography.width, topography.height, null); + + for (BufferedImage tmp : layers.values()) { + g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); + } + + return img; + } + + @Override + public void update(Graphics graphics) { + + int w = getWidth(); + int h = getHeight(); + + BufferedImage img = getCurrentImage(w, h); + + /* + // Create buffered image of topography + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = (Graphics2D) img.getGraphics(); + + g2.drawImage(topo, 0, 0, w, h, 0, 0, topography.width, topography.height, null); + + /* + LinkedList reverse = new LinkedList(); + + for (BufferedImage tmp : layers.values()) { + reverse.addFirst(tmp); + } + + for (BufferedImage tmp : reverse) { + g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); + } + + for (BufferedImage tmp : layers.values()) { + g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); + } + */ + Graphics2D g2 = (Graphics2D) graphics; + g2.drawImage(img, 0, 0, w, h, 0, 0, w, h, null); + } + + /** + * Save the current image as a png file. + * + * @param file + * the filename of the file to save. + * @throws IOException + * if the file could not be saved. + */ + public void save(String file) throws IOException { + + // int w = topography.width; + //int h = topography.height; + + // Create buffered image of topography + + // BufferedImage img = getCurrentImage(topography.width, topography.height); + + /* + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = (Graphics2D) img.getGraphics(); + + g2.drawImage(topo, 0, 0, w, h, 0, 0, topography.width, topography.height, null); + */ + /* + LinkedList reverse = new LinkedList(); + + for (BufferedImage tmp : layers.values()) { + reverse.addFirst(tmp); + } + + BufferedImage bi = reverse.removeLast(); + reverse.addFirst(bi); + + for (BufferedImage tmp : reverse) { + g2.drawImage(tmp, 0, 0, w, h, 0, 0, topography.width, topography.height, null); + } + */ + + ImageIO.write(getCurrentImage(topography.width, topography.height), "png", new File(file)); + + System.out.println("Done writing " + file); + } }