// replace.h // // 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. // // // \file // Functions and classes for the recursive replacement of Fsts. // #ifndef FST_LIB_REPLACE_H__ #define FST_LIB_REPLACE_H__ #include #include "fst/lib/fst.h" #include "fst/lib/cache.h" #include "fst/lib/test-properties.h" namespace fst { // By default ReplaceFst will copy the input label of the 'replace arc'. // For acceptors we do not want this behaviour. Instead we need to // create an epsilon arc when recursing into the appropriate Fst. // The epsilon_on_replace option can be used to toggle this behaviour. struct ReplaceFstOptions : CacheOptions { int64 root; // root rule for expansion bool epsilon_on_replace; ReplaceFstOptions(const CacheOptions &opts, int64 r) : CacheOptions(opts), root(r), epsilon_on_replace(false) {} explicit ReplaceFstOptions(int64 r) : root(r), epsilon_on_replace(false) {} ReplaceFstOptions(int64 r, bool epsilon_replace_arc) : root(r), epsilon_on_replace(epsilon_replace_arc) {} ReplaceFstOptions() : root(kNoLabel), epsilon_on_replace(false) {} }; // // \class ReplaceFstImpl // \brief Implementation class for replace class Fst // // The replace implementation class supports a dynamic // expansion of a recursive transition network represented as Fst // with dynamic replacable arcs. // template class ReplaceFstImpl : public CacheImpl { public: using FstImpl::SetType; using FstImpl::SetProperties; using FstImpl::Properties; using FstImpl::SetInputSymbols; using FstImpl::SetOutputSymbols; using FstImpl::InputSymbols; using FstImpl::OutputSymbols; using CacheImpl::HasStart; using CacheImpl::HasArcs; using CacheImpl::SetStart; typedef typename A::Label Label; typedef typename A::Weight Weight; typedef typename A::StateId StateId; typedef CacheState State; typedef A Arc; typedef std::unordered_map NonTerminalHash; // \struct StateTuple // \brief Tuple of information that uniquely defines a state struct StateTuple { typedef int PrefixId; StateTuple() {} StateTuple(PrefixId p, StateId f, StateId s) : prefix_id(p), fst_id(f), fst_state(s) {} PrefixId prefix_id; // index in prefix table StateId fst_id; // current fst being walked StateId fst_state; // current state in fst being walked, not to be // confused with the state_id of the combined fst }; // constructor for replace class implementation. // \param fst_tuples array of label/fst tuples, one for each non-terminal ReplaceFstImpl(const vector< pair* > >& fst_tuples, const ReplaceFstOptions &opts) : CacheImpl(opts), opts_(opts) { SetType("replace"); if (fst_tuples.size() > 0) { SetInputSymbols(fst_tuples[0].second->InputSymbols()); SetOutputSymbols(fst_tuples[0].second->OutputSymbols()); } fst_array_.push_back(0); for (size_t i = 0; i < fst_tuples.size(); ++i) AddFst(fst_tuples[i].first, fst_tuples[i].second); SetRoot(opts.root); } explicit ReplaceFstImpl(const ReplaceFstOptions &opts) : CacheImpl(opts), opts_(opts), root_(kNoLabel) { fst_array_.push_back(0); } ReplaceFstImpl(const ReplaceFstImpl& impl) : opts_(impl.opts_), state_tuples_(impl.state_tuples_), state_hash_(impl.state_hash_), prefix_hash_(impl.prefix_hash_), stackprefix_array_(impl.stackprefix_array_), nonterminal_hash_(impl.nonterminal_hash_), root_(impl.root_) { SetType("replace"); SetProperties(impl.Properties(), kCopyProperties); SetInputSymbols(InputSymbols()); SetOutputSymbols(OutputSymbols()); fst_array_.reserve(impl.fst_array_.size()); fst_array_.push_back(0); for (size_t i = 1; i < impl.fst_array_.size(); ++i) fst_array_.push_back(impl.fst_array_[i]->Copy()); } ~ReplaceFstImpl() { for (size_t i = 1; i < fst_array_.size(); ++i) { delete fst_array_[i]; } } // Add to Fst array void AddFst(Label label, const Fst* fst) { nonterminal_hash_[label] = fst_array_.size(); fst_array_.push_back(fst->Copy()); if (fst_array_.size() > 1) { vector inprops(fst_array_.size()); for (size_t i = 1; i < fst_array_.size(); ++i) { inprops[i] = fst_array_[i]->Properties(kCopyProperties, false); } SetProperties(ReplaceProperties(inprops)); const SymbolTable* isymbols = fst_array_[1]->InputSymbols(); const SymbolTable* osymbols = fst_array_[1]->OutputSymbols(); for (size_t i = 2; i < fst_array_.size(); ++i) { if (!CompatSymbols(isymbols, fst_array_[i]->InputSymbols())) { LOG(FATAL) << "ReplaceFst::AddFst input symbols of Fst " << i-1 << " does not match input symbols of base Fst (0'th fst)"; } if (!CompatSymbols(osymbols, fst_array_[i]->OutputSymbols())) { LOG(FATAL) << "ReplaceFst::AddFst output symbols of Fst " << i-1 << " does not match output symbols of base Fst " << "(0'th fst)"; } } } } // Computes the dependency graph of the replace class and returns // true if the dependencies are cyclic. Cyclic dependencies will result // in an un-expandable replace fst. bool CyclicDependencies() const { StdVectorFst depfst; // one state for each fst for (size_t i = 1; i < fst_array_.size(); ++i) depfst.AddState(); // an arc from each state (representing the fst) to the // state representing the fst being replaced for (size_t i = 1; i < fst_array_.size(); ++i) { for (StateIterator > siter(*(fst_array_[i])); !siter.Done(); siter.Next()) { for (ArcIterator > aiter(*(fst_array_[i]), siter.Value()); !aiter.Done(); aiter.Next()) { const A& arc = aiter.Value(); typename NonTerminalHash::const_iterator it = nonterminal_hash_.find(arc.olabel); if (it != nonterminal_hash_.end()) { Label j = it->second - 1; depfst.AddArc(i - 1, A(arc.olabel, arc.olabel, Weight::One(), j)); } } } } depfst.SetStart(root_ - 1); depfst.SetFinal(root_ - 1, Weight::One()); return depfst.Properties(kCyclic, true); } // set root rule for expansion void SetRoot(Label root) { Label nonterminal = nonterminal_hash_[root]; root_ = (nonterminal > 0) ? nonterminal : 1; } // Change Fst array void SetFst(Label label, const Fst* fst) { Label nonterminal = nonterminal_hash_[label]; delete fst_array_[nonterminal]; fst_array_[nonterminal] = fst->Copy(); } // Return or compute start state of replace fst StateId Start() { if (!HasStart()) { if (fst_array_.size() == 1) { // no fsts defined for replace SetStart(kNoStateId); return kNoStateId; } else { const Fst* fst = fst_array_[root_]; StateId fst_start = fst->Start(); if (fst_start == kNoStateId) // root Fst is empty return kNoStateId; int prefix = PrefixId(StackPrefix()); StateId start = FindState(StateTuple(prefix, root_, fst_start)); SetStart(start); return start; } } else { return CacheImpl::Start(); } } // return final weight of state (kInfWeight means state is not final) Weight Final(StateId s) { if (!HasFinal(s)) { const StateTuple& tuple = state_tuples_[s]; const StackPrefix& stack = stackprefix_array_[tuple.prefix_id]; const Fst* fst = fst_array_[tuple.fst_id]; StateId fst_state = tuple.fst_state; if (fst->Final(fst_state) != Weight::Zero() && stack.Depth() == 0) SetFinal(s, fst->Final(fst_state)); else SetFinal(s, Weight::Zero()); } return CacheImpl::Final(s); } size_t NumArcs(StateId s) { if (!HasArcs(s)) Expand(s); return CacheImpl::NumArcs(s); } size_t NumInputEpsilons(StateId s) { if (!HasArcs(s)) Expand(s); return CacheImpl::NumInputEpsilons(s); } size_t NumOutputEpsilons(StateId s) { if (!HasArcs(s)) Expand(s); return CacheImpl::NumOutputEpsilons(s); } // return the base arc iterator, if arcs have not been computed yet, // extend/recurse for new arcs. void InitArcIterator(StateId s, ArcIteratorData *data) { if (!HasArcs(s)) Expand(s); CacheImpl::InitArcIterator(s, data); } // Find/create an Fst state given a StateTuple. Only create a new // state if StateTuple is not found in the state hash. StateId FindState(const StateTuple& tuple) { typename StateTupleHash::iterator it = state_hash_.find(tuple); if (it == state_hash_.end()) { StateId new_state_id = state_tuples_.size(); state_tuples_.push_back(tuple); state_hash_[tuple] = new_state_id; return new_state_id; } else { return it->second; } } // extend current state (walk arcs one level deep) void Expand(StateId s) { StateTuple tuple = state_tuples_[s]; const Fst* fst = fst_array_[tuple.fst_id]; StateId fst_state = tuple.fst_state; if (fst_state == kNoStateId) { SetArcs(s); return; } // if state is final, pop up stack const StackPrefix& stack = stackprefix_array_[tuple.prefix_id]; if (fst->Final(fst_state) != Weight::Zero() && stack.Depth()) { int prefix_id = PopPrefix(stack); const PrefixTuple& top = stack.Top(); StateId nextstate = FindState(StateTuple(prefix_id, top.fst_id, top.nextstate)); AddArc(s, A(0, 0, fst->Final(fst_state), nextstate)); } // extend arcs leaving the state for (ArcIterator< Fst > aiter(*fst, fst_state); !aiter.Done(); aiter.Next()) { const Arc& arc = aiter.Value(); if (arc.olabel == 0) { // expand local fst StateId nextstate = FindState(StateTuple(tuple.prefix_id, tuple.fst_id, arc.nextstate)); AddArc(s, A(arc.ilabel, arc.olabel, arc.weight, nextstate)); } else { // check for non terminal typename NonTerminalHash::const_iterator it = nonterminal_hash_.find(arc.olabel); if (it != nonterminal_hash_.end()) { // recurse into non terminal Label nonterminal = it->second; const Fst* nt_fst = fst_array_[nonterminal]; int nt_prefix = PushPrefix(stackprefix_array_[tuple.prefix_id], tuple.fst_id, arc.nextstate); // if start state is valid replace, else arc is implicitly // deleted StateId nt_start = nt_fst->Start(); if (nt_start != kNoStateId) { StateId nt_nextstate = FindState( StateTuple(nt_prefix, nonterminal, nt_start)); Label ilabel = (opts_.epsilon_on_replace) ? 0 : arc.ilabel; AddArc(s, A(ilabel, 0, arc.weight, nt_nextstate)); } } else { StateId nextstate = FindState( StateTuple(tuple.prefix_id, tuple.fst_id, arc.nextstate)); AddArc(s, A(arc.ilabel, arc.olabel, arc.weight, nextstate)); } } } SetArcs(s); } // private helper classes private: static const int kPrime0 = 7853; static const int kPrime1 = 7867; // \class StateTupleEqual // \brief Compare two StateTuples for equality class StateTupleEqual { public: bool operator()(const StateTuple& x, const StateTuple& y) const { return ((x.prefix_id == y.prefix_id) && (x.fst_id == y.fst_id) && (x.fst_state == y.fst_state)); } }; // \class StateTupleKey // \brief Hash function for StateTuple to Fst states class StateTupleKey { public: size_t operator()(const StateTuple& x) const { return static_cast(x.prefix_id + x.fst_id * kPrime0 + x.fst_state * kPrime1); } }; typedef std::unordered_map StateTupleHash; // \class PrefixTuple // \brief Tuple of fst_id and destination state (entry in stack prefix) struct PrefixTuple { PrefixTuple(Label f, StateId s) : fst_id(f), nextstate(s) {} Label fst_id; StateId nextstate; }; // \class StackPrefix // \brief Container for stack prefix. class StackPrefix { public: StackPrefix() {} // copy constructor StackPrefix(const StackPrefix& x) : prefix_(x.prefix_) { } void Push(int fst_id, StateId nextstate) { prefix_.push_back(PrefixTuple(fst_id, nextstate)); } void Pop() { prefix_.pop_back(); } const PrefixTuple& Top() const { return prefix_[prefix_.size()-1]; } size_t Depth() const { return prefix_.size(); } public: vector prefix_; }; // \class StackPrefixEqual // \brief Compare two stack prefix classes for equality class StackPrefixEqual { public: bool operator()(const StackPrefix& x, const StackPrefix& y) const { if (x.prefix_.size() != y.prefix_.size()) return false; for (size_t i = 0; i < x.prefix_.size(); ++i) { if (x.prefix_[i].fst_id != y.prefix_[i].fst_id || x.prefix_[i].nextstate != y.prefix_[i].nextstate) return false; } return true; } }; // // \class StackPrefixKey // \brief Hash function for stack prefix to prefix id class StackPrefixKey { public: size_t operator()(const StackPrefix& x) const { int sum = 0; for (size_t i = 0; i < x.prefix_.size(); ++i) { sum += x.prefix_[i].fst_id + x.prefix_[i].nextstate*kPrime0; } return (size_t) sum; } }; typedef std::unordered_map StackPrefixHash; // private methods private: // hash stack prefix (return unique index into stackprefix array) int PrefixId(const StackPrefix& prefix) { typename StackPrefixHash::iterator it = prefix_hash_.find(prefix); if (it == prefix_hash_.end()) { int prefix_id = stackprefix_array_.size(); stackprefix_array_.push_back(prefix); prefix_hash_[prefix] = prefix_id; return prefix_id; } else { return it->second; } } // prefix id after a stack pop int PopPrefix(StackPrefix prefix) { prefix.Pop(); return PrefixId(prefix); } // prefix id after a stack push int PushPrefix(StackPrefix prefix, Label fst_id, StateId nextstate) { prefix.Push(fst_id, nextstate); return PrefixId(prefix); } // private data private: // runtime options ReplaceFstOptions opts_; // maps from StateId to StateTuple vector state_tuples_; // hashes from StateTuple to StateId StateTupleHash state_hash_; // cross index of unique stack prefix // could potentially have one copy of prefix array StackPrefixHash prefix_hash_; vector stackprefix_array_; NonTerminalHash nonterminal_hash_; vector*> fst_array_; Label root_; void operator=(const ReplaceFstImpl &); // disallow }; // // \class ReplaceFst // \brief Recursivively replaces arcs in the root Fst with other Fsts. // This version is a delayed Fst. // // ReplaceFst supports dynamic replacement of arcs in one Fst with // another Fst. This replacement is recursive. ReplaceFst can be used // to support a variety of delayed constructions such as recursive // transition networks, union, or closure. It is constructed with an // array of Fst(s). One Fst represents the root (or topology) // machine. The root Fst refers to other Fsts by recursively replacing // arcs labeled as non-terminals with the matching non-terminal // Fst. Currently the ReplaceFst uses the output symbols of the arcs // to determine whether the arc is a non-terminal arc or not. A // non-terminal can be any label that is not a non-zero terminal label // in the output alphabet. // // Note that the constructor uses a vector of pair<>. These correspond // to the tuple of non-terminal Label and corresponding Fst. For example // to implement the closure operation we need 2 Fsts. The first root // Fst is a single Arc on the start State that self loops, it references // the particular machine for which we are performing the closure operation. // template class ReplaceFst : public Fst { public: friend class ArcIterator< ReplaceFst >; friend class CacheStateIterator< ReplaceFst >; friend class CacheArcIterator< ReplaceFst >; typedef A Arc; typedef typename A::Label Label; typedef typename A::Weight Weight; typedef typename A::StateId StateId; typedef CacheState State; ReplaceFst(const vector* > >& fst_array, Label root) : impl_(new ReplaceFstImpl(fst_array, ReplaceFstOptions(root))) {} ReplaceFst(const vector* > >& fst_array, const ReplaceFstOptions &opts) : impl_(new ReplaceFstImpl(fst_array, opts)) {} ReplaceFst(const ReplaceFst& fst) : impl_(new ReplaceFstImpl(*(fst.impl_))) {} virtual ~ReplaceFst() { delete impl_; } virtual StateId Start() const { return impl_->Start(); } virtual Weight Final(StateId s) const { return impl_->Final(s); } virtual size_t NumArcs(StateId s) const { return impl_->NumArcs(s); } virtual size_t NumInputEpsilons(StateId s) const { return impl_->NumInputEpsilons(s); } virtual size_t NumOutputEpsilons(StateId s) const { return impl_->NumOutputEpsilons(s); } virtual uint64 Properties(uint64 mask, bool test) const { if (test) { uint64 known, test = TestProperties(*this, mask, &known); impl_->SetProperties(test, known); return test & mask; } else { return impl_->Properties(mask); } } virtual const string& Type() const { return impl_->Type(); } virtual ReplaceFst* Copy() const { return new ReplaceFst(*this); } virtual const SymbolTable* InputSymbols() const { return impl_->InputSymbols(); } virtual const SymbolTable* OutputSymbols() const { return impl_->OutputSymbols(); } virtual inline void InitStateIterator(StateIteratorData *data) const; virtual void InitArcIterator(StateId s, ArcIteratorData *data) const { impl_->InitArcIterator(s, data); } bool CyclicDependencies() const { return impl_->CyclicDependencies(); } private: ReplaceFstImpl* impl_; }; // Specialization for ReplaceFst. template class StateIterator< ReplaceFst > : public CacheStateIterator< ReplaceFst > { public: explicit StateIterator(const ReplaceFst &fst) : CacheStateIterator< ReplaceFst >(fst) {} private: DISALLOW_EVIL_CONSTRUCTORS(StateIterator); }; // Specialization for ReplaceFst. template class ArcIterator< ReplaceFst > : public CacheArcIterator< ReplaceFst > { public: typedef typename A::StateId StateId; ArcIterator(const ReplaceFst &fst, StateId s) : CacheArcIterator< ReplaceFst >(fst, s) { if (!fst.impl_->HasArcs(s)) fst.impl_->Expand(s); } private: DISALLOW_EVIL_CONSTRUCTORS(ArcIterator); }; template inline void ReplaceFst::InitStateIterator(StateIteratorData *data) const { data->base = new StateIterator< ReplaceFst >(*this); } typedef ReplaceFst StdReplaceFst; // // Recursivively replaces arcs in the root Fst with other Fsts. // This version writes the result of replacement to an output MutableFst. // // Replace supports replacement of arcs in one Fst with another // Fst. This replacement is recursive. Replace takes an array of // Fst(s). One Fst represents the root (or topology) machine. The root // Fst refers to other Fsts by recursively replacing arcs labeled as // non-terminals with the matching non-terminal Fst. Currently Replace // uses the output symbols of the arcs to determine whether the arc is // a non-terminal arc or not. A non-terminal can be any label that is // not a non-zero terminal label in the output alphabet. Note that // input argument is a vector of pair<>. These correspond to the tuple // of non-terminal Label and corresponding Fst. template void Replace(const vector* > >& ifst_array, MutableFst *ofst, typename Arc::Label root) { ReplaceFstOptions opts(root); opts.gc_limit = 0; // Cache only the last state for fastest copy. *ofst = ReplaceFst(ifst_array, opts); } } #endif // FST_LIB_REPLACE_H__