Skip to content

Commit

Permalink
Add a filtering method to BasePreferenceForest
Browse files Browse the repository at this point in the history
and streamline the tests for the 'tree' package.
  • Loading branch information
enasca committed Feb 16, 2017
1 parent 18a6a3b commit d55692e
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 154 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,11 +17,11 @@ public abstract class AbstractPreferenceForest<P, S extends BaseStream<P, S>>
/**
* The list of leaf nodes.
*/
protected List<? extends BaseNode<P, S>> leaves;
protected List<BaseNode<P, S>> leaves;

@Override
public Stream<? extends BaseNode<P, S>> leaves() {
return leaves.stream();
public List<S> branches() {
return Lists.transform(leaves, BaseNode::getReachable);
}

@Override
Expand All @@ -37,14 +39,15 @@ public void expand(Predicate<S> branchFilter) {
}

@Override
public void expandOrdered(Predicate<S> branchFilter) {
Objects.requireNonNull(branchFilter);
Stream.Builder<BaseNode<P, S>> newLeaves = Stream.builder();
for (BaseNode<P, S> 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<BaseNode<P, S>> 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -22,8 +21,7 @@
* <p>After the <em>N</em>-th expansion, the forest will be empty.
*
* <p>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.
*
* <p>The following snippet shows how to fully expand a forest while retrieving
* the list of branches at each level:
Expand All @@ -37,58 +35,42 @@
* @param <S> a stream type representing a branch
*/
public interface BasePreferenceForest<P, S extends BaseStream<P, S>> {
/**
* Returns the leaf nodes.
* @return
*/
Stream<? extends BaseNode<P, S>> leaves();

/**
* Returns the list of branches.
* @return
*/
default List<S> branches() {
return leaves().map(BaseNode::getReachable)
.collect(Collectors.toList());
}
List<S> branches();

/**
* Returns <code>true</code> if all branches have been cut.
* @return
*/
default boolean isEmpty() {
return !leaves().findAny().isPresent();
}

/**
* Equivalent to
* <pre>{@code expand(branch -> true); }</pre>
*/
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:
* <ul>
* <li>applying the input <code>Predicate</code> on the branch produces <code>true</code>;</li>
* <li>the branch has reached its maximum length.</li>
* </ul>
* 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.
*
* <p>After <em>k</em> invocations of this method on a forest constructed
* from the propositional variables
* <pre>p1, p2, &hellip; pN</pre>
* from the propositional variables <em>p1, p2, &hellip;, pN</em>,
* {@link #branches()} will return the <em>k-subsets</em> of
* <pre>p1, not(p1), p2, not(p2), &hellip; pN, not(pN)</pre>
* excluding: <em>a)</em> the ones in which a propositional variable <em>p</em> appears
* as both <em>p</em> and <em>not(p)</em>; and <em>b)</em> the ones that were filtered out
* by the input <code>Predicate</code>s
* <pre>{ p1, not(p1), p2, not(p2), &hellip;, pN, not(pN) }</pre>
* excluding the ones in which a propositional variable <em>p</em> appears
* as both <em>p</em> and <em>not(p)</em>.
*
* <p>After the <em>N</em>-th and subsequent invocations, {@link #isEmpty()}
* will always evaluate to <code>true</code>.
*
*/
default void expand() {
expand(branch -> true);
}

/**
* Inserts a filter in the expansion process of {@link #expand()}.
* Branches who do not match input the <code>Predicate</code> become ineligible for expansion.
* <p>This method aims for concurrent processing of the branches.
* The input <code>Predicate</code> must follow the same behavioral constraints
* specified by {@link Stream#filter(Predicate)}.
Expand All @@ -99,20 +81,13 @@ default void expand() {
void expand(Predicate<S> branchFilter);

/**
* Equivalent to
* <pre>{@code expandOrdered(branch -> true); }</pre>
*/
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 <code>Predicate</code> 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 <code>false</code> become ineligible for expansion.
* @param mask
* @throws IllegalArgumentException if <code>mask</code> does not match
* the current number of branches.
*/
void expandOrdered(Predicate<S> branchFilter);
void expand(boolean[] mask);

/**
* A node that stores an element of type {@link P} and a reference to its parent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public void testPreferenceVariables() throws Exception {
Map<String, Collection<String>> domainMap = preferenceVariables.asMap();
Map<String, Set<String>> cpnetDomainMap = cpnet.getPreferenceGraph().domainMap();
Assert.assertEquals(cpnetDomainMap, domainMap,
Utils.reportSetDifference(cpnetDomainMap.entrySet(), domainMap.entrySet()));
TestUtils.reportSetDifference(cpnetDomainMap.entrySet(), domainMap.entrySet()));
}

@Test
Expand All @@ -154,7 +154,7 @@ public void testComputeOptimum() throws Exception {
ConstraintSet<OptimalityConstraint> optimumSet =
cpnet.toConstraintSet(this.optimalityConstraints);
Assert.assertEquals(cpnetOptimumSet, optimumSet,
Utils.reportSetDifference(cpnetOptimumSet, optimumSet));
TestUtils.reportSetDifference(cpnetOptimumSet, optimumSet));
}

/**
Expand Down Expand Up @@ -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));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <code>expected.equals(actual) == false</code>.
* @param actual
Expand Down Expand Up @@ -42,4 +45,19 @@ public static <T> 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<Integer> actualSet = Arrays.stream(actual).boxed().collect(Collectors.toSet());
Set<Integer> expectedSet = Arrays.stream(expected).boxed().collect(Collectors.toSet());
Assert.assertEquals(actualSet, expectedSet, message);
}
}
Original file line number Diff line number Diff line change
@@ -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<IntStream> 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<IntStream>) 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 <code>1, 2, &hellip;, maxLiterals</code>.
* @param levels the contents of the forest at each level.
* <code>levels[i]</code> represents the collection of branches at level <code>i</code>;
* <code>levels[i][j]</code> the <code>j-th</code> branch at level <code>i</code>.
* @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<IntPreferenceForest, Integer> expandImpl)
throws Exception {
IntPreferenceForest forest = new IntPreferenceForest(maxLiterals);
int lv;
// Iterate over levels.
for (lv = 0; !forest.isEmpty(); lv++) {
List<IntStream> 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);
}

}
Loading

0 comments on commit d55692e

Please sign in to comment.