From d55692e5af8283a1432966b75d6f7e9c4c6cb69f Mon Sep 17 00:00:00 2001 From: Enrico Nasca Date: Thu, 16 Feb 2017 13:41:05 +0100 Subject: [PATCH] Add a filtering method to BasePreferenceForest and streamline the tests for the 'tree' package. --- .../tree/AbstractPreferenceForest.java | 25 ++-- .../ontocpnets/tree/BasePreferenceForest.java | 73 ++++------- .../ontocpnets/OntologicalCPNetTest.java | 6 +- .../ontocpnets/{Utils.java => TestUtils.java} | 22 +++- .../tree/IntPreferenceForestTest.java | 121 ++++++++++++++++++ .../ontocpnets/tree/PreferenceForestTest.java | 89 ------------- 6 files changed, 182 insertions(+), 154 deletions(-) rename src/test/java/it/poliba/enasca/ontocpnets/{Utils.java => TestUtils.java} (63%) create mode 100644 src/test/java/it/poliba/enasca/ontocpnets/tree/IntPreferenceForestTest.java delete mode 100644 src/test/java/it/poliba/enasca/ontocpnets/tree/PreferenceForestTest.java diff --git a/src/main/java/it/poliba/enasca/ontocpnets/tree/AbstractPreferenceForest.java b/src/main/java/it/poliba/enasca/ontocpnets/tree/AbstractPreferenceForest.java index 13a85dd..1257d2a 100644 --- a/src/main/java/it/poliba/enasca/ontocpnets/tree/AbstractPreferenceForest.java +++ b/src/main/java/it/poliba/enasca/ontocpnets/tree/AbstractPreferenceForest.java @@ -1,5 +1,7 @@ package it.poliba.enasca.ontocpnets.tree; +import com.google.common.collect.Lists; + import java.util.List; import java.util.Objects; import java.util.function.Predicate; @@ -15,11 +17,11 @@ public abstract class AbstractPreferenceForest> /** * The list of leaf nodes. */ - protected List> leaves; + protected List> leaves; @Override - public Stream> leaves() { - return leaves.stream(); + public List branches() { + return Lists.transform(leaves, BaseNode::getReachable); } @Override @@ -37,14 +39,15 @@ public void expand(Predicate branchFilter) { } @Override - public void expandOrdered(Predicate branchFilter) { - Objects.requireNonNull(branchFilter); - Stream.Builder> newLeaves = Stream.builder(); - for (BaseNode leaf : leaves) { - if (branchFilter.test(leaf.getReachable())) { - leaf.children().forEachOrdered(newLeaves); - } + public void expand(boolean[] mask) { + Objects.requireNonNull(mask); + if (mask.length != leaves.size()) { + throw new IllegalArgumentException(); + } + Stream.Builder> builder = Stream.builder(); + for (int i = 0; i < mask.length; i++) { + if (mask[i]) leaves.get(i).children().forEachOrdered(builder); } - leaves = newLeaves.build().collect(Collectors.toList()); + leaves = builder.build().collect(Collectors.toList()); } } diff --git a/src/main/java/it/poliba/enasca/ontocpnets/tree/BasePreferenceForest.java b/src/main/java/it/poliba/enasca/ontocpnets/tree/BasePreferenceForest.java index df356c9..4a2c6ed 100644 --- a/src/main/java/it/poliba/enasca/ontocpnets/tree/BasePreferenceForest.java +++ b/src/main/java/it/poliba/enasca/ontocpnets/tree/BasePreferenceForest.java @@ -4,7 +4,6 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.stream.BaseStream; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -22,8 +21,7 @@ *

After the N-th expansion, the forest will be empty. * *

The distinctive feature of a preference forest is the ability to control the expansion process - * by filtering branches. See {@link #expand(Predicate)} and {@link #expandOrdered(Predicate)} - * for more information. + * by filtering branches. See {@link #expand(Predicate)} for more information. * *

The following snippet shows how to fully expand a forest while retrieving * the list of branches at each level: @@ -37,58 +35,42 @@ * @param a stream type representing a branch */ public interface BasePreferenceForest> { - /** - * Returns the leaf nodes. - * @return - */ - Stream> leaves(); /** * Returns the list of branches. * @return */ - default List branches() { - return leaves().map(BaseNode::getReachable) - .collect(Collectors.toList()); - } + List branches(); /** * Returns true if all branches have been cut. * @return */ - default boolean isEmpty() { - return !leaves().findAny().isPresent(); - } - - /** - * Equivalent to - *

{@code expand(branch -> true); }
- */ - default void expand() { - expand(branch -> true); - } + boolean isEmpty(); /** * Expands each eligible branch by one level. - * A branch is eligible for expansion if it satisfies both of the following conditions: - *
    - *
  • applying the input Predicate on the branch produces true;
  • - *
  • the branch has reached its maximum length.
  • - *
- * If a branch is not eligible for expansion, it is cut from the forest. + * A branch is eligible for expansion if it has not yet reached its maximum length. + * Ineligible branches are cut from the forest. * *

After k invocations of this method on a forest constructed - * from the propositional variables - *

p1, p2, … pN
+ * from the propositional variables p1, p2, …, pN, * {@link #branches()} will return the k-subsets of - *
p1, not(p1), p2, not(p2), … pN, not(pN)
- * excluding: a) the ones in which a propositional variable p appears - * as both p and not(p); and b) the ones that were filtered out - * by the input Predicates + *
{ p1, not(p1), p2, not(p2), …, pN, not(pN) }
+ * excluding the ones in which a propositional variable p appears + * as both p and not(p). * *

After the N-th and subsequent invocations, {@link #isEmpty()} * will always evaluate to true. - * + */ + default void expand() { + expand(branch -> true); + } + + /** + * Inserts a filter in the expansion process of {@link #expand()}. + * Branches who do not match input the Predicate become ineligible for expansion. + *

This method aims for concurrent processing of the branches. * The input Predicate must follow the same behavioral constraints * specified by {@link Stream#filter(Predicate)}. @@ -99,20 +81,13 @@ default void expand() { void expand(Predicate branchFilter); /** - * Equivalent to - *

{@code expandOrdered(branch -> true); }
- */ - default void expandOrdered() { - expandOrdered(branch -> true); - } - - /** - * Equivalent to {@link #expand(Predicate)}, with the exception that - * branches are processed one at a time, in the encountered order. - * Consequently, the input Predicate is not restricted by behavioral constraints. - * @param branchFilter + * Inserts a filter in the expansion process of {@link #expand()}, based on a boolean mask. + * Branches who map to false become ineligible for expansion. + * @param mask + * @throws IllegalArgumentException if mask does not match + * the current number of branches. */ - void expandOrdered(Predicate branchFilter); + void expand(boolean[] mask); /** * A node that stores an element of type {@link P} and a reference to its parent. diff --git a/src/test/java/it/poliba/enasca/ontocpnets/OntologicalCPNetTest.java b/src/test/java/it/poliba/enasca/ontocpnets/OntologicalCPNetTest.java index 0fd5269..e396c9a 100644 --- a/src/test/java/it/poliba/enasca/ontocpnets/OntologicalCPNetTest.java +++ b/src/test/java/it/poliba/enasca/ontocpnets/OntologicalCPNetTest.java @@ -144,7 +144,7 @@ public void testPreferenceVariables() throws Exception { Map> domainMap = preferenceVariables.asMap(); Map> cpnetDomainMap = cpnet.getPreferenceGraph().domainMap(); Assert.assertEquals(cpnetDomainMap, domainMap, - Utils.reportSetDifference(cpnetDomainMap.entrySet(), domainMap.entrySet())); + TestUtils.reportSetDifference(cpnetDomainMap.entrySet(), domainMap.entrySet())); } @Test @@ -154,7 +154,7 @@ public void testComputeOptimum() throws Exception { ConstraintSet optimumSet = cpnet.toConstraintSet(this.optimalityConstraints); Assert.assertEquals(cpnetOptimumSet, optimumSet, - Utils.reportSetDifference(cpnetOptimumSet, optimumSet)); + TestUtils.reportSetDifference(cpnetOptimumSet, optimumSet)); } /** @@ -195,7 +195,7 @@ public void testHardPareto() throws Exception { .map(Outcome::getOutcomeAsValuationMap) .collect(Collectors.toSet()); Assert.assertEquals(hardParetoOutcomes, outcomesAsMaps, - Utils.reportSetDifference(hardParetoOutcomes, outcomesAsMaps)); + TestUtils.reportSetDifference(hardParetoOutcomes, outcomesAsMaps)); } /** diff --git a/src/test/java/it/poliba/enasca/ontocpnets/Utils.java b/src/test/java/it/poliba/enasca/ontocpnets/TestUtils.java similarity index 63% rename from src/test/java/it/poliba/enasca/ontocpnets/Utils.java rename to src/test/java/it/poliba/enasca/ontocpnets/TestUtils.java index c594fe4..79b79fd 100644 --- a/src/test/java/it/poliba/enasca/ontocpnets/Utils.java +++ b/src/test/java/it/poliba/enasca/ontocpnets/TestUtils.java @@ -1,13 +1,16 @@ package it.poliba.enasca.ontocpnets; import com.google.common.collect.Sets; +import org.testng.Assert; +import java.util.Arrays; import java.util.Set; +import java.util.stream.Collectors; /** - * Contains static helper methods. + * Contains static helper methods for tests. */ -public class Utils { +public class TestUtils { /** * This method assumes that expected.equals(actual) == false. * @param actual @@ -42,4 +45,19 @@ public static String reportSetDifference( .append(Sets.difference(actual, expected)) .toString(); } + + public static void assertEqualsUnordered(int[] actual, int[] expected) { + assertEqualsUnordered(actual, expected, null); + } + + public static void assertEqualsUnordered(int[] actual, int[] expected, String message) { + if (actual == expected) return; + if (actual == null || expected == null) { + Assert.fail(String.format("Expected '%s', but got '%s'", + Arrays.toString(expected), Arrays.toString(actual))); + } + Set actualSet = Arrays.stream(actual).boxed().collect(Collectors.toSet()); + Set expectedSet = Arrays.stream(expected).boxed().collect(Collectors.toSet()); + Assert.assertEquals(actualSet, expectedSet, message); + } } diff --git a/src/test/java/it/poliba/enasca/ontocpnets/tree/IntPreferenceForestTest.java b/src/test/java/it/poliba/enasca/ontocpnets/tree/IntPreferenceForestTest.java new file mode 100644 index 0000000..7c93bdb --- /dev/null +++ b/src/test/java/it/poliba/enasca/ontocpnets/tree/IntPreferenceForestTest.java @@ -0,0 +1,121 @@ +package it.poliba.enasca.ontocpnets.tree; + +import it.poliba.enasca.ontocpnets.TestUtils; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +/** + * Test class for {@link IntPreferenceForest}. + */ +public class IntPreferenceForestTest { + /** + * Tests the {@link IntPreferenceForest#expand()} method. + * @param maxLiterals + * @param levels + * @throws Exception + */ + @Test(dataProvider = "expandProvider") + public void testExpand(int maxLiterals, int[][][] levels) throws Exception { + testExpandInternal(maxLiterals, levels, + (forest, currentLevel) -> forest.expand()); + } + + @DataProvider(name = "expandProvider") + public static Object[][] expandProvider() { + return new Object[][]{ + {3, new int[][][]{ + // first level + {{1}, {-1}, {2}, {-2}, {3}, {-3}}, + // second level + {{2, 1}, {-2, 1}, {3, 1}, {-3, 1}, {2, -1}, {-2, -1}, {3, -1}, {-3, -1}, {3, 2}, {-3, 2}, {3, -2}, {-3, -2}}, + // third level + {{3, 2, 1}, {-3, 2, 1}, {3, -2, 1}, {-3, -2, 1}, {3, 2, -1}, {-3, 2, -1}, {3, -2, -1}, {-3, -2, -1}} + }} + }; + } + + @Test(dataProvider = "filterExpandProvider") + public void testFilterExpand(int maxLiterals, int[][][] levels, + Predicate branchFilter) throws Exception { + testExpandInternal(maxLiterals, levels, + (forest, currentLevel) -> forest.expand(branchFilter)); + } + + @DataProvider(name = "filterExpandProvider") + public static Object[][] filterExpandProvider() { + return new Object[][]{ + {3, new int[][][]{ + // first level + {{1}, {-1}, {2}, {-2}, {3}, {-3}}, + // second level + {{2, -1}, {-2, -1}, {3, -1}, {-3, -1}, {3, -2}, {-3, -2}}, + // third level + {{3, -2, -1}, {-3, -2, -1}} + }, (Predicate) branch -> branch.sum() < 0 } + }; + } + + @Test(dataProvider = "maskExpandProvider") + public void testMaskExpand(int maxLiterals, int[][][] levels, boolean[][] masks) + throws Exception { + testExpandInternal(maxLiterals, levels, + (forest, currentLevel) -> forest.expand(masks[currentLevel])); + } + + @DataProvider(name = "maskExpandProvider") + public static Object[][] maskExpandProvider() { + return new Object[][]{ + {3, new int[][][]{ + // first level + {{1}, {-1}, {2}, {-2}, {3}, {-3}}, + // second level + {{2, 1}, {-2, 1}, {3, 1}, {-3, 1}, {3, -2}, {-3, -2}}, + // third level + {{3, 2, 1}, {-3, 2, 1}} + }, new boolean[][]{ + {true, false, false, true, true, true}, + {true, false, false, true, true, true}, + {true, false}} + } + }; + } + + /** + * A generic test for the expansion process of {@link IntPreferenceForest}. + * @param maxLiterals the number of propositional variables. + * The {@link IntPreferenceForest} instance will be constructed from the + * DIMACS literals 1, 2, …, maxLiterals. + * @param levels the contents of the forest at each level. + * levels[i] represents the collection of branches at level i; + * levels[i][j] the j-th branch at level i. + * @param expandImpl the actual expansion function, which should contain an invocation + * to {@link IntPreferenceForest#expand()} or its overloaded versions. + * Accepts an {@link IntPreferenceForest} instance and the 0-based index + * of the current level. + */ + private void testExpandInternal(int maxLiterals, int[][][] levels, + BiConsumer expandImpl) + throws Exception { + IntPreferenceForest forest = new IntPreferenceForest(maxLiterals); + int lv; + // Iterate over levels. + for (lv = 0; !forest.isEmpty(); lv++) { + List actualBranches = forest.branches(); + int[][] expectedBranches = levels[lv]; + expandImpl.accept(forest, lv); + // Iterate over the branches at the current level. + IntStream.range(0, expectedBranches.length) + .forEach(i -> TestUtils.assertEqualsUnordered( + actualBranches.get(i).toArray(), + expectedBranches[i])); + } + Assert.assertEquals(lv, levels.length); + } + +} \ No newline at end of file diff --git a/src/test/java/it/poliba/enasca/ontocpnets/tree/PreferenceForestTest.java b/src/test/java/it/poliba/enasca/ontocpnets/tree/PreferenceForestTest.java deleted file mode 100644 index 001353c..0000000 --- a/src/test/java/it/poliba/enasca/ontocpnets/tree/PreferenceForestTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package it.poliba.enasca.ontocpnets.tree; - -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -/** - * Tests for implementers of {@link BasePreferenceForest}. - */ -public class PreferenceForestTest { - /** - * Tests the {@link PreferenceForest#expand()} method. - * @param dimacsLiterals a stream of DIMACS literals, which will serve as the - * input set for the preference forest - * @param levels the contents of the branches at each level - */ - @Test(dataProvider = "expandProvider") - public void testExpandGeneric(int[] dimacsLiterals, int[][][] levels) - throws Exception { - PreferenceForest forest = Arrays.stream(dimacsLiterals).boxed() - .collect(PreferenceForest.toForest(literal -> -literal)); - int level; - // Iterate over levels. - for (level = 0; !forest.isEmpty(); level++) { - int[][] expectedBranches = levels[level]; - List> actualBranches = forest.branches(); - forest.expand(); - // Iterate over the branches at the current level. - IntStream.range(0, expectedBranches.length).forEach(i -> { - Set expectedBranch = Arrays.stream(expectedBranches[i]).boxed() - .collect(Collectors.toSet()); - Set actualBranch = actualBranches.get(i) - .collect(Collectors.toSet()); - Assert.assertEquals(actualBranch, expectedBranch); - }); - } - Assert.assertEquals(level, levels.length); - } - - /** - * Tests the {@link IntPreferenceForest#expand()} method. - * @param dimacsLiterals a stream of DIMACS literals, which will serve as the - * input set for the preference forest - * @param levels the contents of the branches at each level - */ - @Test(dataProvider = "expandProvider") - public void testExpandInt(int[] dimacsLiterals, int[][][] levels) - throws Exception { - IntPreferenceForest forest = new IntPreferenceForest(dimacsLiterals.length); - int level; - // Iterate over levels. - for (level = 0; !forest.isEmpty(); level++) { - int[][] expectedBranches = levels[level]; - List actualBranches = forest.branches(); - forest.expand(); - // Iterate over the branches at the current level. - IntStream.range(0, expectedBranches.length).forEach(i -> { - Set expectedBranch = Arrays.stream(expectedBranches[i]).boxed() - .collect(Collectors.toSet()); - Set actualBranch = actualBranches.get(i).boxed() - .collect(Collectors.toSet()); - Assert.assertEquals(actualBranch, expectedBranch); - }); - } - Assert.assertEquals(level, levels.length); - } - - @DataProvider - public Object[][] expandProvider() { - return new Object[][]{ - {new int[]{1, 2, 3}, new int[][][]{ - // first level - {{1}, {-1}, {2}, {-2}, {3}, {-3}}, - // second level - {{2, 1}, {-2, 1}, {3, 1}, {-3, 1}, {2, -1}, {-2, -1}, {3, -1}, {-3, -1}, {3, 2}, {-3, 2}, {3, -2}, {-3, -2}}, - // third level - {{3, 2, 1}, {-3, 2, 1}, {3, -2, 1}, {-3, -2, 1}, {3, 2, -1}, {-3, 2, -1}, {3, -2, -1}, {-3, -2, -1}} - }} - }; - } - -} \ No newline at end of file