/* * Copyright (C) 2019 Claus Schätzle (schaetzc@tf.uni-freiburg.de) * Copyright (C) 2019 University of Freiburg * * This file is part of the ULTIMATE Util Library. * * The ULTIMATE Util Library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE Util Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE Util Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE Util Library, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE Util Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.util.datastructures; import java.util.ArrayDeque; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Queue; import java.util.function.Function; import java.util.stream.Stream; import de.uni_freiburg.informatik.ultimate.core.model.models.IDirectedGraph; import de.uni_freiburg.informatik.ultimate.util.TgfBuilder; /** * Converts any graph-based structure into a string using the Trivial Graph Format (TGF) file format. * Nodes can be labeled by a custom function. Edges are not represented explicitly. For a TGF converter * that also labels edges see {@link TgfBuilder}. *

* This class is meant to be used in the toString methods of other classes and to debug graph-based structures. *

* Some graph-based structures might represent edges non-symmetrically, that is * a node U can have successor V not having U as a predecessor. To show such issues the generated TGF * represents directed edges by forward edges and (hopefully anti-parallel) backward edges. * * @author schaetzc@tf.uni-freiburg.de * * @param Type of the graph nodes * * @see TgfBuilder Can be used to convert anything into TGF format manually without having to create an IDirectedGraph */ public class GraphToTgf { private final StringBuilder mTgfNodes = new StringBuilder(); private final StringBuilder mTgfEdges = new StringBuilder(); private final Map mVisitedNodeToId = new HashMap<>(); private final Queue mWorklist = new ArrayDeque<>(); private final Function mLabelOf; private final Function> mSuccessorsOf; private final Function> mPredecessorsOf; public GraphToTgf(final Function> successorsOf, final Function> predecessorsOf, final Function nodeToLabel) { mSuccessorsOf = successorsOf; mPredecessorsOf = predecessorsOf; mLabelOf = nodeToLabel; } public static > GraphToTgf graph(final Function labelOf) { return new GraphToTgf(GN::getOutgoingNodes, GN::getIncomingNodes, labelOf); } public static > GraphToTgf graph(final GN startingNode) { return graph(GN::toString).includeComponentOf(startingNode); } /** * Returns the trivial graph format (TGF) representation of all weakly connected components visited by * {@link #includeComponentOf(N)} and similar methods. * * @return String in TGF format representing a graph */ public String getTgf() { return mTgfNodes + "#\n" + mTgfEdges; } /** * Includes the weakly connected component of a given node into the resulting TGF. * The TGF can be retrieved using {@link #getTgf()}. *

* This method is idempotent; calling it twice on the same component does not change anything. * * @param startingNode Node whose connected component will be included * @return This converter to allow chaining multiple calls */ public GraphToTgf includeComponentOf(final N startingNode) { if (isUnvisited(startingNode)) { visit(startingNode); } while (!mWorklist.isEmpty()) { final N current = mWorklist.remove(); visitNeighbors(current); addEdges(current); } return this; } /** * @see #includeComponentOf(Object) */ public GraphToTgf includeComponentsOf(final Collection nodes) { for (final N node : nodes) { includeComponentOf(node); } return this; } private void visitNeighbors(final N node) { Stream.concat(mPredecessorsOf.apply(node).stream(), mSuccessorsOf.apply(node).stream()) .filter(this::isUnvisited) .forEach(this::visit); } private int visit(final N node) { final int id = mVisitedNodeToId.size(); mVisitedNodeToId.put(node, id); mTgfNodes.append(id).append(' ').append(mLabelOf.apply(node)).append("\n"); mWorklist.add(node); return id; } private void addEdges(final N source) { final int sourceId = idOf(source); mPredecessorsOf.apply(source).forEach(target -> addEdge(sourceId, idOf(target), "backward")); mSuccessorsOf.apply(source).forEach(target -> addEdge(sourceId, idOf(target), "forward")); } private void addEdge(final int source, final int target, final String label) { mTgfEdges.append(source).append(' ').append(target).append(" ").append(label).append("\n"); } private int idOf(final N node) { return mVisitedNodeToId.get(node); } private boolean isUnvisited(final N node) { return !mVisitedNodeToId.containsKey(node); } }