Skip to content

Commit bf09456

Browse files
committed
visualize FSA via GraphViz.
1 parent f75a615 commit bf09456

6 files changed

+180
-3
lines changed

k2/csrc/CMakeLists.txt

+18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ add_library(fsa_util fsa_util.cc)
66
target_include_directories(fsa_util PUBLIC ${CMAKE_SOURCE_DIR})
77
target_compile_features(fsa_util PUBLIC cxx_std_11)
88

9+
add_library(fsa_renderer fsa_renderer.cc)
10+
target_include_directories(fsa_renderer PUBLIC ${CMAKE_SOURCE_DIR})
11+
target_compile_features(fsa_renderer PUBLIC cxx_std_11)
12+
913
add_executable(properties_test properties_test.cc)
1014

1115
target_link_libraries(properties_test
@@ -34,4 +38,18 @@ add_test(NAME Test.fsa_util_test
3438
$<TARGET_FILE:fsa_util_test>
3539
)
3640

41+
add_executable(fsa_renderer_test fsa_renderer_test.cc)
42+
43+
target_link_libraries(fsa_renderer_test
44+
PRIVATE
45+
fsa_renderer
46+
gtest
47+
gtest_main
48+
)
49+
50+
add_test(NAME Test.fsa_renderer_test
51+
COMMAND
52+
$<TARGET_FILE:fsa_renderer_test>
53+
)
54+
3755
# TODO(fangjun): write some helper functions to create targets.

k2/csrc/fsa_renderer.cc

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// k2/csrc/fsa_renderer.cc
2+
3+
// Copyright (c) 2020 Fangjun Kuang (csukuangfj@gmail.com)
4+
5+
// See ../../LICENSE for clarification regarding multiple authors
6+
7+
#include "k2/csrc/fsa_renderer.h"
8+
9+
#include <sstream>
10+
#include <string>
11+
12+
namespace {
13+
14+
std::string GeneratePrologue() {
15+
// TODO(fangjun): the following options can be passed from outside
16+
std::string header = R"header(
17+
digraph FSA {
18+
rankdir = LR;
19+
size = "8.5,11";
20+
label = "";
21+
center = 1;
22+
orientation = Portrait;
23+
ranksep = "0.4"
24+
nodesep = "0.25"
25+
)header";
26+
return header;
27+
}
28+
29+
std::string GenerateEpilogue() { return "}"; }
30+
31+
using k2::Arc;
32+
using k2::Fsa;
33+
using k2::Label;
34+
using k2::StateId;
35+
36+
std::string ProcessState(const Fsa &fsa, int32_t state) {
37+
std::ostringstream os;
38+
os << " " << state << " [label = \"" << state
39+
<< "\", shape = circle, style = bold, fontsize=14]"
40+
<< "\n";
41+
42+
int32_t begin = fsa.leaving_arcs[state].begin;
43+
int32_t end = fsa.leaving_arcs[state].end;
44+
45+
for (; begin != end; ++begin) {
46+
const auto &arc = fsa.arcs[begin];
47+
StateId src = arc.src_state;
48+
StateId dest = arc.dest_state;
49+
Label label = arc.label;
50+
os << " " << src << " -> " << dest << " [label = \"" << label
51+
<< "\", fontsize = 14];"
52+
<< "\n";
53+
}
54+
55+
return os.str();
56+
}
57+
58+
} // namespace
59+
60+
namespace k2 {
61+
62+
FsaRenderer::FsaRenderer(const Fsa &fsa) : fsa_(fsa) {}
63+
64+
std::string FsaRenderer::Render() const {
65+
int32_t num_states = fsa_.NumStates();
66+
if (num_states == 0) return "";
67+
68+
std::ostringstream os;
69+
os << GeneratePrologue();
70+
71+
for (int32_t i = 0; i != num_states - 1; ++i) {
72+
os << ProcessState(fsa_, i);
73+
}
74+
75+
// now for the final state
76+
os << " " << (num_states - 1) << " [label = \"" << (num_states - 1)
77+
<< "\", shape = doublecircle, style = solid, fontsize = 14]"
78+
<< "\n";
79+
80+
os << GenerateEpilogue() << "\n";
81+
82+
return os.str();
83+
}
84+
85+
} // namespace k2

k2/csrc/fsa_renderer.h

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// k2/csrc/fsa_renderer.h
2+
3+
// Copyright (c) 2020 Fangjun Kuang (csukuangfj@gmail.com)
4+
5+
// See ../../LICENSE for clarification regarding multiple authors
6+
7+
#include <string>
8+
9+
#include "k2/csrc/fsa.h"
10+
11+
#ifndef K2_CSRC_FSA_RENDERER_H_
12+
#define K2_CSRC_FSA_RENDERER_H_
13+
14+
namespace k2 {
15+
16+
// Get a GraphViz representation of an fsa.
17+
class FsaRenderer {
18+
public:
19+
explicit FsaRenderer(const Fsa &fsa);
20+
21+
// Return a GraphViz representation of the fsa
22+
std::string Render() const;
23+
24+
private:
25+
const Fsa &fsa_;
26+
};
27+
28+
} // namespace k2
29+
30+
#endif // K2_CSRC_FSA_RENDERER_H_

k2/csrc/fsa_renderer_test.cc

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// k2/csrc/fsa_renderer_test.cc
2+
3+
// Copyright (c) 2020 Fangjun Kuang (csukuangfj@gmail.com)
4+
5+
// See ../../LICENSE for clarification regarding multiple authors
6+
7+
#include "k2/csrc/fsa_renderer.h"
8+
9+
#include <utility>
10+
#include <vector>
11+
12+
#include "gtest/gtest.h"
13+
14+
namespace k2 {
15+
16+
// NOTE(fangjun): this test always passes.
17+
// Its purpose is to get a Graphviz representation
18+
// of the fsa and convert it to a viewable format **offline**.
19+
//
20+
// For example, you can run
21+
//
22+
// ./k2/csrc/fsa_renderer_test 2>&1 >/dev/null | dot -Tpdf > test.pdf
23+
//
24+
// and then open the generated "test.pdf" to verify FsaRenderer works
25+
// as expected.
26+
TEST(FsaRenderer, Render) {
27+
std::vector<Arc> arcs = {
28+
{0, 1, 2}, {0, 2, 1}, {1, 2, 0}, {1, 3, 5}, {2, 3, 6},
29+
};
30+
std::vector<Range> leaving_arcs = {
31+
{0, 2}, {2, 4}, {4, 5}, {0, 0}, // the last state has no leaving arcs
32+
};
33+
34+
Fsa fsa;
35+
fsa.leaving_arcs = std::move(leaving_arcs);
36+
fsa.arcs = std::move(arcs);
37+
38+
FsaRenderer renderer(fsa);
39+
std::cerr << renderer.Render();
40+
}
41+
42+
} // namespace k2

k2/csrc/fsa_util.cc

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ namespace k2 {
1414
void GetEnteringArcs(const Fsa &fsa, VecOfVec *entering_arcs) {
1515
// CHECK(CheckProperties(fsa, KTopSorted));
1616

17-
int num_states = fsa.NumStates();
17+
int32_t num_states = fsa.NumStates();
1818
std::vector<std::vector<std::pair<Label, StateId>>> vec(num_states);
19-
int num_arcs = 0;
19+
int32_t num_arcs = 0;
2020
for (const auto &arc : fsa.arcs) {
2121
auto src_state = arc.src_state;
2222
auto dest_state = arc.dest_state;
@@ -27,6 +27,8 @@ void GetEnteringArcs(const Fsa &fsa, VecOfVec *entering_arcs) {
2727

2828
auto &ranges = entering_arcs->ranges;
2929
auto &values = entering_arcs->values;
30+
ranges.clear();
31+
values.clear();
3032
ranges.reserve(num_states);
3133
values.reserve(num_arcs);
3234

k2/csrc/fsa_util_test.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ TEST(FsaUtil, GetEnteringArcs) {
1818
{0, 1, 2}, {0, 2, 1}, {1, 2, 0}, {1, 3, 5}, {2, 3, 6},
1919
};
2020
std::vector<Range> leaving_arcs = {
21-
{0, 2}, {2, 4}, {4, 5}, {0, 0}, // the last state has no entering arcs
21+
{0, 2}, {2, 4}, {4, 5}, {0, 0}, // the last state has no leaving arcs
2222
};
2323

2424
Fsa fsa;

0 commit comments

Comments
 (0)