Alternative depth-based DOT layout ordering in FST's Utils.

https://issues.apache.org/jira/browse/LUCENE-2934

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1072978 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Dawid Weiss 2011-02-21 14:23:48 +00:00
parent 3f1e769798
commit 7097021329
2 changed files with 155 additions and 80 deletions

View File

@ -467,9 +467,13 @@ public class FST<T> {
return arc;
}
/** Follow the follow arc and read the first arc of its
* target; this changes the provide arc (2nd arg) in-place
* and returns it. */
/**
* Follow the <code>follow</code> arc and read the first arc of its target;
* this changes the provided <code>arc</code> (2nd arg) in-place and returns
* it.
*
* @returns Returns the second argument (<code>arc</code>).
*/
public Arc<T> readFirstTargetArc(Arc<T> follow, Arc<T> arc) throws IOException {
//int pos = address;
//System.out.println(" readFirstTarget follow.target=" + follow.target + " isFinal=" + follow.isFinal());

View File

@ -17,12 +17,8 @@ package org.apache.lucene.util.automaton.fst;
* limitations under the License.
*/
import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.io.*;
import java.util.*;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.IntsRef;
@ -160,90 +156,165 @@ public final class Util {
return output;
}
}
// NOTE: this consumes alot of RAM!
// arcs w/ NEXT opto are in blue
/*
eg:
PrintStream ps = new PrintStream("out.dot");
fst.toDot(ps);
ps.close();
System.out.println("SAVED out.dot");
then dot -Tpng out.dot > /x/tmp/out.png
*/
public static<T> void toDot(FST<T> fst, PrintStream out) throws IOException {
/**
* Dumps an {@link FST} to a GraphViz's <code>dot</code> language description
* for visualization. Example of use:
*
* <pre>
* PrintStream ps = new PrintStream(&quot;out.dot&quot;);
* fst.toDot(ps);
* ps.close();
* </pre>
*
* and then, from command line:
*
* <pre>
* dot -Tpng -o out.png out.dot
* </pre>
*
* <p>
* Note: larger FSTs (a few thousand nodes) won't even render, don't bother.
*
* @param sameRank
* If <code>true</code>, the resulting <code>dot</code> file will try
* to order states in layers of breadth-first traversal. This may
* mess up arcs, but makes the output FST's structure a bit clearer.
*
* @param labelStates
* If <code>true</code> states will have labels equal to their offsets in their
* binary format. Expands the graph considerably.
*
* @see "http://www.graphviz.org/"
*/
public static <T> void toDot(FST<T> fst, Writer out, boolean sameRank, boolean labelStates)
throws IOException {
// This is the start arc in the automaton (from the epsilon state to the first state
// with outgoing transitions.
final FST.Arc<T> startArc = fst.getFirstArc(new FST.Arc<T>());
final List<FST.Arc<T>> queue = new ArrayList<FST.Arc<T>>();
queue.add(startArc);
// A queue of transitions to consider for the next level.
final List<FST.Arc<T>> thisLevelQueue = new ArrayList<FST.Arc<T>>();
final Set<Integer> seen = new HashSet<Integer>();
seen.add(startArc.target);
// A queue of transitions to consider when processing the next level.
final List<FST.Arc<T>> nextLevelQueue = new ArrayList<FST.Arc<T>>();
nextLevelQueue.add(startArc);
out.println("digraph FST {");
out.println(" rankdir = LR;");
//out.println(" " + startNode + " [shape=circle label=" + startNode + "];");
out.println(" " + startArc.target + " [label=\"\" shape=circle];");
out.println(" initial [shape=point color=white label=\"\"];");
out.println(" initial -> " + startArc.target);
// A list of states on the same level (for ranking).
final List<Integer> sameLevelStates = new ArrayList<Integer>();
// A bitset of already seen states (target offset).
final BitSet seen = new BitSet();
seen.set(startArc.target);
// Shape for states.
final String stateShape = "circle";
// Emit DOT prologue.
out.write("digraph FST {\n");
out.write(" rankdir = LR; splines=true; concentrate=true; ordering=out; ranksep=2.5; \n");
if (!labelStates) {
out.write(" node [shape=circle, width=.2, height=.2, style=filled]\n");
}
emitDotState(out, "initial", "point", "white", "");
emitDotState(out, Integer.toString(startArc.target), stateShape, null, "");
out.write(" initial -> " + startArc.target + "\n");
final T NO_OUTPUT = fst.outputs.getNoOutput();
int level = 0;
while(queue.size() != 0) {
FST.Arc<T> arc = queue.get(queue.size()-1);
queue.remove(queue.size()-1);
//System.out.println("dot cycle target=" + arc.target);
while (!nextLevelQueue.isEmpty()) {
// we could double buffer here, but it doesn't matter probably.
thisLevelQueue.addAll(nextLevelQueue);
nextLevelQueue.clear();
if (fst.targetHasArcs(arc)) {
// scan all arcs
final int node = arc.target;
fst.readFirstTargetArc(arc, arc);
while(true) {
//System.out.println(" cycle label=" + arc.label + " (" + (char) arc.label + ") target=" + arc.target);
if (!seen.contains(arc.target)) {
final String shape;
if (arc.target == -1) {
shape = "doublecircle";
} else {
shape = "circle";
}
out.println(" " + arc.target + " [shape=" + shape + "];");
seen.add(arc.target);
queue.add(new FST.Arc<T>().copyFrom(arc));
//System.out.println(" new!");
}
String outs;
if (arc.output != NO_OUTPUT) {
outs = "/" + fst.outputs.outputToString(arc.output);
} else {
outs = "";
}
final char cl;
if (arc.label == FST.END_LABEL) {
cl = '~';
} else {
cl = (char) arc.label;
}
out.println(" " + node + " -> " + arc.target + " [label=\"" + cl + outs + "\"]");
//if (arc.flag(FST.BIT_TARGET_NEXT)) {
//out.print(" color=blue");
//}
//out.println("];");
level++;
out.write("\n // Transitions and states at level: " + level + "\n");
while (!thisLevelQueue.isEmpty()) {
final FST.Arc<T> arc = thisLevelQueue.remove(thisLevelQueue.size() - 1);
if (arc.isLast()) {
break;
} else {
if (fst.targetHasArcs(arc)) {
// scan all arcs
final int node = arc.target;
fst.readFirstTargetArc(arc, arc);
while (true) {
// Emit the unseen state and add it to the queue for the next level.
if (arc.target >= 0 && !seen.get(arc.target)) {
emitDotState(out, Integer.toString(arc.target), stateShape, null,
labelStates ? Integer.toString(arc.target) : "");
seen.set(arc.target);
nextLevelQueue.add(new FST.Arc<T>().copyFrom(arc));
sameLevelStates.add(arc.target);
}
String outs;
if (arc.output != NO_OUTPUT) {
outs = "/" + fst.outputs.outputToString(arc.output);
} else {
outs = "";
}
final String cl;
if (arc.label == FST.END_LABEL) {
cl = "~";
} else {
cl = printableLabel(arc.label);
}
out.write(" " + node + " -> " + arc.target + " [label=\"" + cl + outs + "\"]\n");
// Break the loop if we're on the last arc of this state.
if (arc.isLast()) {
break;
}
fst.readNextArc(arc);
}
}
}
// Emit state ranking information.
if (sameRank && sameLevelStates.size() > 1) {
out.write(" {rank=same; ");
for (int state : sameLevelStates) {
out.write(state + "; ");
}
out.write(" }\n");
}
sameLevelStates.clear();
}
// Emit terminating state (always there anyway).
out.write(" -1 [style=filled, color=black, shape=circle, label=\"\"]\n\n");
out.write(" {rank=sink; -1 } ");
out.write("}\n");
out.flush();
}
/**
* Emit a single state in the <code>dot</code> language.
*/
private static void emitDotState(Writer out, String name, String shape,
String color, String label) throws IOException {
out.write(" " + name
+ " ["
+ (shape != null ? "shape=" + shape : "") + " "
+ (color != null ? "color=" + color : "") + " "
+ (label != null ? "label=\"" + label + "\"" : "label=\"\"") + " "
+ "]\n");
}
/**
* Ensures an arc's label is indeed printable (dot uses US-ASCII).
*/
private static String printableLabel(int label) {
if (label >= 0x20 && label <= 0x7d) {
return Character.toString((char) label);
} else {
return "0x" + Integer.toHexString(label);
}
out.println("}");
}
}