/* * 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 java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import de.odysseus.ithaka.digraph.Digraph; import de.odysseus.ithaka.digraph.Digraphs; import de.odysseus.ithaka.digraph.EdgeWeights; import de.odysseus.ithaka.digraph.MapDigraph; /** * Abstract feedback arc set provider. */ public abstract class AbstractFeedbackArcSetProvider implements FeedbackArcSetProvider { class FeedbackTask<V,E> implements Callable<FeedbackArcSet<V,E>> { final Digraph<V,E> digraph; final EdgeWeights<? super V> weights; final FeedbackArcSetPolicy policy; final Set<V> scc; FeedbackTask(Digraph<V,E> digraph, EdgeWeights<? super V> weights, FeedbackArcSetPolicy policy, Set<V> scc) { this.digraph = digraph; this.weights = weights; this.policy = policy; this.scc = scc; } @Override public FeedbackArcSet<V, E> call() { return fas(digraph.subgraph(scc), weights, policy); } } private final boolean decompose; private final int numberOfThreads; /** * Create provider which calculates a feedback arc set on a digraph (in the * current thread). * If the <code>decompose</code> flag set to <code>true</code>, the provider * decomposes a digraph into strongly connected components and computes * feedback arc sets on the components and combines the results. * If the <code>decompose</code> flag set to <code>false</code>, the * {@link #mfas(Digraph, EdgeWeights)} and {@link #lfas(Digraph, EdgeWeights)} * implementation methods must be able to handle arbitrary digraphs or the * {@link #getFeedbackArcSet(Digraph, EdgeWeights, FeedbackArcSetPolicy)} * method <em>must</em> be called with strongly connected components only! * @param decompose whether to decompose into strongly connected components. */ protected AbstractFeedbackArcSetProvider(boolean decompose) { this.decompose = decompose; this.numberOfThreads = 0; } /** * Create provider which decomposes a digraph into strongly connected components * and computes feedback arc sets on the components and combines the results. * Feedback calculations can be distributed to a given number of threads. * If <code>numberOfThreads == 0</code>, calculation is done in the current thread. * @param numberOfThreads number */ protected AbstractFeedbackArcSetProvider(int numberOfThreads) { this.decompose = true; this.numberOfThreads = numberOfThreads; } /** * Compute minimum feedback arc set. * @param digraph * @param weights * @return feedback arc set or <code>null</code> */ protected <V,E> Digraph<V,E> mfas(Digraph<V,E> digraph, EdgeWeights<? super V> weights) { return null; } /** * Compute light feedback arc set. * @param digraph original graph or tangle of it (if decompose == true) * @param weights * @return feedback arc set */ protected abstract <V,E> Digraph<V,E> lfas(Digraph<V,E> digraph, EdgeWeights<? super V> weights); private <V,E> FeedbackArcSet<V,E> fas(Digraph<V,E> digraph, EdgeWeights<? super V> weights, FeedbackArcSetPolicy policy) { EdgeWeights<? super V> filteredWeights = weights; if (policy == FeedbackArcSetPolicy.MIN_SIZE) { /* * Manipulate graph weights if the feedback arc set has to be of minimum size (i.e., #arcs): * all weights are increased by an amount (delta) equal to the sum of all weights, * so that every arc is heavier than any arc subset with the original weights. * A minimum weight feedback arc set (mwfas) of the resulting graph has a total weight of * #arcs(mwfas) * delta + origWeight(mwfas). * Since origWeight(mwfas) < delta, the determined mwfas has a minimum #arcs and * from all those feedback arc sets of minimum size it has minimum original weight (we could * have obtained the first result easily by setting all weights to 1, but not the second). */ final EdgeWeights<? super V> origWeights = weights; final int delta = totalWeight(digraph, origWeights); filteredWeights = new EdgeWeights<V>() { @Override public Integer get(V source, V target) { return origWeights.get(source, target) + delta; } }; } Digraph<V, E> result = mfas(digraph, filteredWeights); boolean exact = true; if (result == null) { result = lfas(digraph, filteredWeights); exact = false; } return new FeedbackArcSet<V, E>(result, totalWeight(result, weights), policy, exact); } protected <V,E> int totalWeight(Digraph<V,E> digraph, EdgeWeights<? super V> weights) { int weight = 0; for (V source : digraph.vertices()) { for (V target : digraph.targets(source)) { weight += weights.get(source, target); } } return weight; } private <V,E> List<FeedbackArcSet<V,E>> executeAll(List<FeedbackTask<V,E>> tasks) { List<FeedbackArcSet<V, E>> result = new ArrayList<FeedbackArcSet<V,E>>(); if (numberOfThreads <= 0) { for (FeedbackTask<V,E> task : tasks) { result.add(task.call()); } } else { ExecutorService executor = Executors.newFixedThreadPool(Math.min(numberOfThreads, tasks.size())); try { for (Future<FeedbackArcSet<V, E>> future : executor.invokeAll(tasks)) { result.add(future.get()); } } catch (ExecutionException e) { return null; // should not happen } catch (InterruptedException e) { return null; // should not happen } finally { executor.shutdown(); } } return result; } @Override public <V,E> FeedbackArcSet<V,E> getFeedbackArcSet(Digraph<V,E> digraph, EdgeWeights<? super V> weights, FeedbackArcSetPolicy policy) { if (digraph.isAcyclic()) { return new FeedbackArcSet<V, E>(Digraphs.<V, E>emptyDigraph(), 0, policy, true); } if (decompose) { List<FeedbackTask<V,E>> tasks = new ArrayList<FeedbackTask<V,E>>(); for (Set<V> component : Digraphs.scc(digraph)) { if (component.size() > 1) { tasks.add(new FeedbackTask<V, E>(digraph, weights, policy, component)); } } List<FeedbackArcSet<V,E>> feedbacks = executeAll(tasks); if (feedbacks == null) { return null; } int weight = 0; boolean exact = true; Digraph<V, E> result = new MapDigraph<V, E>(); for (FeedbackArcSet<V,E> feedback : feedbacks) { for (V source : feedback.vertices()) { for (V target : feedback.targets(source)) { result.put(source, target, digraph.get(source, target)); } } exact &= feedback.isExact(); weight += feedback.getWeight(); } return new FeedbackArcSet<V, E>(result, weight, policy, exact); } else { return fas(digraph, weights, policy); } } }