/*
* Copyright 2012 Odysseus Software GmbH
*
* 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 de.odysseus.ithaka.digraph.util.fas;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Formatter;
import java.util.Random;
import junit.framework.Assert;
import org.junit.Test;
import de.odysseus.ithaka.digraph.Digraph;
import de.odysseus.ithaka.digraph.EdgeWeights;
import de.odysseus.ithaka.digraph.SimpleDigraph;
import de.odysseus.ithaka.digraph.SimpleDigraphAdapter;
import de.odysseus.ithaka.digraph.WeightedDigraph;
import de.odysseus.ithaka.digraph.WeightedDigraphAdapter;
import de.odysseus.ithaka.digraph.util.fas.FeedbackArcSet;
import de.odysseus.ithaka.digraph.util.fas.FeedbackArcSetPolicy;
import de.odysseus.ithaka.digraph.util.fas.FeedbackArcSetProvider;
import de.odysseus.ithaka.digraph.util.fas.SimpleFeedbackArcSetProvider;
public class SimpleFeedbackArcSetProviderTest {
private SimpleDigraphAdapter<Integer> randomGraph(Random rng, int nodeCount, int arcCount) {
if (arcCount > nodeCount * (nodeCount - 1)) {
throw new IllegalArgumentException("Too many arcs!");
}
SimpleDigraphAdapter<Integer> graph = new SimpleDigraphAdapter<Integer>();
while (graph.getEdgeCount() < arcCount) {
int source = rng.nextInt(nodeCount);
int target = rng.nextInt(nodeCount);
if (source != target) {
graph.add(source, target);
}
};
return graph;
}
private EdgeWeights<Integer> randomWeights(final Digraph<Integer,?> graph, Random rng, int minWeight, int maxWeight) {
int maxNode = 0;
for (int node : graph.vertices()) {
if (node > maxNode) {
maxNode = node;
}
}
final int size = maxNode + 1;
class Weights implements EdgeWeights<Integer> {
int[][] values = new int[size][size];
@Override
public Integer get(Integer source, Integer target) {
return values[source][target];
}
@Override public String toString() {
Formatter formatter = new Formatter(new StringBuilder());
formatter.format(" 0 1 2 3 4 5 6 7 8 9".substring(0, 1 + 3*graph.getVertexCount()));
for (int source = 0; source < graph.getVertexCount(); source++) {
formatter.format("%n%1d", source);
for (int target = 0; target < graph.getVertexCount(); target++) {
formatter.format(" %2d", values[source][target]);
}
}
String result = formatter.toString();
formatter.close();
return result;
}
};
Weights weights = new Weights();
for (int source : graph.vertices()) {
for (int target : graph.targets(source)) {
weights.values[source][target] = minWeight + rng.nextInt(maxWeight + 1 - minWeight);
}
}
return weights;
}
private boolean isFeedbackSet(SimpleDigraph<Integer> graph, Digraph<Integer,?> set) {
for (int source : set.vertices()) {
for (int target : set.targets(source)){
if (!graph.remove(source, target)) {
return false;
}
}
}
boolean result = graph.isAcyclic();
for (int source : set.vertices()) {
for (int target : set.targets(source)) {
graph.add(source, target);
}
}
return result;
}
private <V> int weight(Digraph<V,?> graph, EdgeWeights<V> weights) {
int weight = 0;
for (V source : graph.vertices()) {
for (V target : graph.targets(source)) {
weight += weights.get(source, target);
}
}
return weight;
}
private long calculateFeedbackArcSets(Random rng, int nodeCount, int arcCount, int minWeight, int maxWeight) {
FeedbackArcSetProvider simpleProvider = new SimpleFeedbackArcSetProvider();
SimpleDigraph<Integer> graph = randomGraph(rng, nodeCount, arcCount);
EdgeWeights<Integer> weights = randomWeights(graph, rng, minWeight, maxWeight);
long time = System.currentTimeMillis();
Digraph<Integer,?> swfas = simpleProvider.getFeedbackArcSet(graph, weights, FeedbackArcSetPolicy.MIN_WEIGHT);
Digraph<Integer,?> ssfas = simpleProvider.getFeedbackArcSet(graph, weights, FeedbackArcSetPolicy.MIN_SIZE);
time = System.currentTimeMillis() - time;
if (graph.isAcyclic()) {
assertTrue(ssfas.getEdgeCount() == 0);
assertTrue(swfas.getEdgeCount() == 0);
}
assertTrue(isFeedbackSet(graph, swfas));
assertTrue(isFeedbackSet(graph, ssfas));
assertTrue(weight(swfas, weights) <= weight(ssfas, weights));
assertTrue(ssfas.getEdgeCount() <= swfas.getEdgeCount());
// System.out.println("G = " + graph);
// System.out.println(weights);
// System.out.println("swfas: size=" + swfas.getEdgeCount() + ", weight=" + weight(swfas, weights) + ", " + swfas);
// System.out.println("ssfas: size=" + ssfas.getEdgeCount() + ", weight=" + weight(ssfas, weights) + ", " + ssfas);
// System.out.println();
return time;
}
@Test
// @Ignore
public void testThreads() {
SimpleDigraphAdapter<Integer> graph = new SimpleDigraphAdapter<Integer>();
int tangleSize = 10;
int tangleCount = 4;
for (int nodeOffset = 0; nodeOffset < tangleCount * tangleSize; nodeOffset += tangleSize) {
for (int source = 0; source < tangleSize; source++) {
for (int target = 0; target < tangleSize; target++) {
if (source != target) {
graph.add(nodeOffset + source, nodeOffset + target);
}
}
}
}
Assert.assertEquals(tangleSize * (tangleSize - 1) * tangleCount, graph.getEdgeCount());
Assert.assertEquals(tangleSize * tangleCount, graph.getVertexCount());
EdgeWeights<Object> weights = EdgeWeights.UNIT_WEIGHTS;
for (int threads = tangleCount; threads >= 0; threads--) {
SimpleFeedbackArcSetProvider simpleProvider = new SimpleFeedbackArcSetProvider(threads);
Digraph<Integer,?> fas = simpleProvider.getFeedbackArcSet(graph, weights, FeedbackArcSetPolicy.MIN_WEIGHT);
Assert.assertEquals(graph.getVertexCount(), fas.getVertexCount());
Assert.assertEquals(graph.getEdgeCount() / 2, fas.getEdgeCount());
assertTrue(isFeedbackSet(graph, fas));
}
}
@Test
public void testRandomDigraphs() {
Random rng = new Random(7);
for (int nodeCount = 6; nodeCount <= 10; nodeCount++) {
for (int edgeCount = 22; edgeCount <= 25; edgeCount++) {
calculateFeedbackArcSets(rng, nodeCount, edgeCount, 1, 99);
calculateFeedbackArcSets(rng, nodeCount, edgeCount, 1, 10);
calculateFeedbackArcSets(rng, nodeCount, edgeCount, 1, 1);
calculateFeedbackArcSets(rng, nodeCount, edgeCount, 90, 99);
}
}
}
@Test
public void testPolicy() {
FeedbackArcSetProvider provider = new SimpleFeedbackArcSetProvider();
WeightedDigraph<Integer> graph = new WeightedDigraphAdapter<Integer>();
graph.put(1, 2, 3);
graph.put(2, 3, 1);
graph.put(3, 1, 2);
graph.put(2, 1, 1);
FeedbackArcSet<Integer,?> fas;
// minimum weight fas contains 2->3, 2->1
fas = provider.getFeedbackArcSet(graph, graph, FeedbackArcSetPolicy.MIN_WEIGHT);
assertFalse(fas.isExact());
assertEquals(2, fas.getEdgeCount());
assertEquals(2, fas.getWeight());
assertTrue(fas.contains(2, 3));
assertSame(graph.get(2, 3), fas.get(2, 3));
assertTrue(fas.contains(2, 1));
assertSame(graph.get(2, 1), fas.get(2, 1));
// minimum size fas contains 1->2
fas = provider.getFeedbackArcSet(graph, graph, FeedbackArcSetPolicy.MIN_SIZE);
assertFalse(fas.isExact());
assertEquals(1, fas.getEdgeCount());
assertEquals(3, fas.getWeight());
assertTrue(fas.contains(1, 2));
assertSame(graph.get(1, 2), fas.get(1, 2));
}
}