Blame view
tools/openfst-1.6.7/include/fst/isomorphic.h
5.67 KB
8dcb6dfcb first commit |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
// See www.openfst.org for extensive documentation on this weighted // finite-state transducer library. // // Function to test two FSTs are isomorphic, i.e., they are equal up to a state // and arc re-ordering. FSTs should be deterministic when viewed as // unweighted automata. #ifndef FST_ISOMORPHIC_H_ #define FST_ISOMORPHIC_H_ #include <algorithm> #include <list> #include <type_traits> #include <vector> #include <fst/log.h> #include <fst/fst.h> namespace fst { namespace internal { // Orders weights for equality checking. template <class Weight, typename std::enable_if< IsIdempotent<Weight>::value>::type * = nullptr> bool WeightCompare(const Weight &w1, const Weight &w2, float delta, bool *error) { return NaturalLess<Weight>()(w1, w2); } template <class Weight, typename std::enable_if< !IsIdempotent<Weight>::value>::type * = nullptr> bool WeightCompare(const Weight &w1, const Weight &w2, float delta, bool *error) { // No natural order; use hash. const auto q1 = w1.Quantize(delta); const auto q2 = w2.Quantize(delta); auto n1 = q1.Hash(); auto n2 = q2.Hash(); // Hash not unique; very unlikely to happen. if (n1 == n2 && q1 != q2) { VLOG(1) << "Isomorphic: Weight hash collision"; *error = true; } return n1 < n2; } template <class Arc> class Isomorphism { using StateId = typename Arc::StateId; public: Isomorphism(const Fst<Arc> &fst1, const Fst<Arc> &fst2, float delta) : fst1_(fst1.Copy()), fst2_(fst2.Copy()), delta_(delta), error_(false), comp_(delta, &error_) {} // Checks if input FSTs are isomorphic. bool IsIsomorphic() { if (fst1_->Start() == kNoStateId && fst2_->Start() == kNoStateId) { return true; } if (fst1_->Start() == kNoStateId || fst2_->Start() == kNoStateId) { return false; } PairState(fst1_->Start(), fst2_->Start()); while (!queue_.empty()) { const auto &pr = queue_.front(); if (!IsIsomorphicState(pr.first, pr.second)) return false; queue_.pop_front(); } return true; } bool Error() const { return error_; } private: // Orders arcs for equality checking. class ArcCompare { public: ArcCompare(float delta, bool *error) : delta_(delta), error_(error) {} bool operator()(const Arc &arc1, const Arc &arc2) const { if (arc1.ilabel < arc2.ilabel) return true; if (arc1.ilabel > arc2.ilabel) return false; if (arc1.olabel < arc2.olabel) return true; if (arc1.olabel > arc2.olabel) return false; return WeightCompare(arc1.weight, arc2.weight, delta_, error_); } private: float delta_; bool *error_; }; // Maintains state correspondences and queue. bool PairState(StateId s1, StateId s2) { if (state_pairs_.size() <= s1) state_pairs_.resize(s1 + 1, kNoStateId); if (state_pairs_[s1] == s2) { return true; // already seen this pair } else if (state_pairs_[s1] != kNoStateId) { return false; // s1 already paired with another s2 } state_pairs_[s1] = s2; queue_.push_back(std::make_pair(s1, s2)); return true; } // Checks if state pair is isomorphic bool IsIsomorphicState(StateId s1, StateId s2); std::unique_ptr<Fst<Arc>> fst1_; std::unique_ptr<Fst<Arc>> fst2_; float delta_; // Weight equality delta. std::vector<Arc> arcs1_; // For sorting arcs on FST1. std::vector<Arc> arcs2_; // For sorting arcs on FST2. std::vector<StateId> state_pairs_; // Maintains state correspondences. std::list<std::pair<StateId, StateId>> queue_; // Queue of state pairs. bool error_; // Error flag. ArcCompare comp_; }; template <class Arc> bool Isomorphism<Arc>::IsIsomorphicState(StateId s1, StateId s2) { if (!ApproxEqual(fst1_->Final(s1), fst2_->Final(s2), delta_)) return false; auto narcs1 = fst1_->NumArcs(s1); auto narcs2 = fst2_->NumArcs(s2); if (narcs1 != narcs2) return false; ArcIterator<Fst<Arc>> aiter1(*fst1_, s1); ArcIterator<Fst<Arc>> aiter2(*fst2_, s2); arcs1_.clear(); arcs1_.reserve(narcs1); arcs2_.clear(); arcs2_.reserve(narcs2); for (; !aiter1.Done(); aiter1.Next(), aiter2.Next()) { arcs1_.push_back(aiter1.Value()); arcs2_.push_back(aiter2.Value()); } std::sort(arcs1_.begin(), arcs1_.end(), comp_); std::sort(arcs2_.begin(), arcs2_.end(), comp_); for (size_t i = 0; i < arcs1_.size(); ++i) { const auto &arc1 = arcs1_[i]; const auto &arc2 = arcs2_[i]; if (arc1.ilabel != arc2.ilabel) return false; if (arc1.olabel != arc2.olabel) return false; if (!ApproxEqual(arc1.weight, arc2.weight, delta_)) return false; if (!PairState(arc1.nextstate, arc2.nextstate)) return false; if (i > 0) { // Checks for non-determinism. const auto &arc0 = arcs1_[i - 1]; if (arc1.ilabel == arc0.ilabel && arc1.olabel == arc0.olabel && ApproxEqual(arc1.weight, arc0.weight, delta_)) { VLOG(1) << "Isomorphic: Non-determinism as an unweighted automaton"; error_ = true; return false; } } } return true; } } // namespace internal // Tests if two FSTs have the same states and arcs up to a reordering. // Inputs should be non-deterministic when viewed as unweighted automata. template <class Arc> bool Isomorphic(const Fst<Arc> &fst1, const Fst<Arc> &fst2, float delta = kDelta) { internal::Isomorphism<Arc> iso(fst1, fst2, delta); bool result = iso.IsIsomorphic(); if (iso.Error()) { FSTERROR() << "Isomorphic: Cannot determine if inputs are isomorphic"; return false; } else { return result; } } } // namespace fst #endif // FST_ISOMORPHIC_H_ |