/** * Fairly straightforward: the task here is to reassemble the rows of the affinity matrix. The * tricky part is that any specific element in the list of elements which does NOT lay on the * diagonal will be so because it did not drop below the sensitivity threshold, hence it was not * "cut". * * <p>On the flip side, there will be many entries whose coordinate is now set to the diagonal, * indicating they were previously affinity entries whose sensitivities were below the threshold, * and hence were "cut" - set to 0 at their original coordinates, and had their values added to * the diagonal entry (hence the numerous entries with the coordinate of the diagonal). * * @throws Exception */ @Test public void testEigencutsAffinityCutsReducer() throws Exception { Configuration conf = new Configuration(); Path affinity = new Path("affinity"); Path sensitivity = new Path("sensitivity"); conf.set(EigencutsKeys.AFFINITY_PATH, affinity.getName()); conf.setInt(EigencutsKeys.AFFINITY_DIMENSIONS, this.affinity.length); // since we need the working paths to distinguish the vertex types, // we can't use the mapper (since we have no way of manually setting // the Context.workingPath() ) Map<Text, List<VertexWritable>> data = buildMapData(affinity, sensitivity, this.sensitivity); // now, set up the combiner EigencutsAffinityCutsCombiner combiner = new EigencutsAffinityCutsCombiner(); DummyRecordWriter<Text, VertexWritable> comWriter = new DummyRecordWriter<Text, VertexWritable>(); Reducer<Text, VertexWritable, Text, VertexWritable>.Context comContext = DummyRecordWriter.build(combiner, conf, comWriter, Text.class, VertexWritable.class); // perform the combining for (Map.Entry<Text, List<VertexWritable>> entry : data.entrySet()) { combiner.reduce(entry.getKey(), entry.getValue(), comContext); } // finally, set up the reduction writers EigencutsAffinityCutsReducer reducer = new EigencutsAffinityCutsReducer(); DummyRecordWriter<IntWritable, VectorWritable> redWriter = new DummyRecordWriter<IntWritable, VectorWritable>(); Reducer<Text, VertexWritable, IntWritable, VectorWritable>.Context redContext = DummyRecordWriter.build(reducer, conf, redWriter, Text.class, VertexWritable.class); // perform the reduction for (Text key : comWriter.getKeys()) { reducer.reduce(key, comWriter.getValue(key), redContext); } // now, check that the affinity matrix is correctly formed for (IntWritable row : redWriter.getKeys()) { List<VectorWritable> results = redWriter.getValue(row); // there should only be 1 vector assertEquals("Only one vector with a given row number", 1, results.size()); Vector therow = results.get(0).get(); for (Vector.Element e : therow.all()) { // check the diagonal if (row.get() == e.index()) { assertEquals( "Correct diagonal sum of cuts", sumOfRowCuts(row.get(), this.sensitivity), e.get(), EPSILON); } else { // not on the diagonal...if it was an element labeled to be cut, // it should have a value of 0. Otherwise, it should have kept its // previous value if (this.sensitivity[row.get()][e.index()] == 0.0) { // should be what it was originally assertEquals( "Preserved element", this.affinity[row.get()][e.index()], e.get(), EPSILON); } else { // should be 0 assertEquals("Cut element", 0.0, e.get(), EPSILON); } } } } }
/** * This is by far the trickiest step. However, an easy condition is if we have only two vertices - * indicating vertices on the diagonal of the two matrices - then we simply exit (since the * algorithm does not operate on the diagonal; it makes no sense to perform cuts by isolating data * points from themselves). * * <p>If there are four points, then first we must separate the two which belong to the affinity * matrix from the two that are sensitivities. In theory, each pair should have exactly the same * value (symmetry). If the sensitivity is below a certain threshold, then we set the two values * of the affinity matrix to 0 (but not before adding the affinity values to the diagonal, so as * to maintain the overall sum of the row of the affinity matrix). * * @throws Exception */ @Test public void testEigencutsAffinityCutsCombiner() throws Exception { Configuration conf = new Configuration(); Path affinity = new Path("affinity"); Path sensitivity = new Path("sensitivity"); conf.set(EigencutsKeys.AFFINITY_PATH, affinity.getName()); conf.setInt(EigencutsKeys.AFFINITY_DIMENSIONS, this.affinity.length); // since we need the working paths to distinguish the vertex types, // we can't use the mapper (since we have no way of manually setting // the Context.workingPath() ) Map<Text, List<VertexWritable>> data = buildMapData(affinity, sensitivity, this.sensitivity); // now, set up the combiner EigencutsAffinityCutsCombiner combiner = new EigencutsAffinityCutsCombiner(); DummyRecordWriter<Text, VertexWritable> redWriter = new DummyRecordWriter<Text, VertexWritable>(); Reducer<Text, VertexWritable, Text, VertexWritable>.Context redContext = DummyRecordWriter.build(combiner, conf, redWriter, Text.class, VertexWritable.class); // perform the combining for (Map.Entry<Text, List<VertexWritable>> entry : data.entrySet()) { combiner.reduce(entry.getKey(), entry.getValue(), redContext); } // test the number of cuts, there should be 2 assertEquals( "Number of cuts detected", 4, redContext.getCounter(EigencutsAffinityCutsJob.CUTSCOUNTER.NUM_CUTS).getValue()); // loop through all the results; let's see if they match up to our // affinity matrix (and all the cuts appear where they should Map<Text, List<VertexWritable>> results = redWriter.getData(); for (Map.Entry<Text, List<VertexWritable>> entry : results.entrySet()) { List<VertexWritable> row = entry.getValue(); IntWritable key = new IntWritable(Integer.parseInt(entry.getKey().toString())); double calcDiag = 0.0; double trueDiag = sumOfRowCuts(key.get(), this.sensitivity); for (VertexWritable e : row) { // should the value have been cut, e.g. set to 0? if (key.get() == e.getCol()) { // we have our diagonal calcDiag += e.getValue(); } else if (this.sensitivity[key.get()][e.getCol()] == 0.0) { // no, corresponding affinity should have same value as before assertEquals( "Preserved affinity value", this.affinity[key.get()][e.getCol()], e.getValue(), EPSILON); } else { // yes, corresponding affinity value should be 0 assertEquals("Cut affinity value", 0.0, e.getValue(), EPSILON); } } // check the diagonal has the correct sum assertEquals("Diagonal sum from cuts", trueDiag, calcDiag, EPSILON); } }