1// replace.h
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15//
16// \file
17// Functions and classes for the recursive replacement of Fsts.
18//
19
20#ifndef FST_LIB_REPLACE_H__
21#define FST_LIB_REPLACE_H__
22
23#include <ext/hash_map>
24using __gnu_cxx::hash_map;
25
26#include "fst/lib/fst.h"
27#include "fst/lib/cache.h"
28#include "fst/lib/test-properties.h"
29
30namespace fst {
31
32// By default ReplaceFst will copy the input label of the 'replace arc'.
33// For acceptors we do not want this behaviour. Instead we need to
34// create an epsilon arc when recursing into the appropriate Fst.
35// The epsilon_on_replace option can be used to toggle this behaviour.
36struct ReplaceFstOptions : CacheOptions {
37  int64 root;    // root rule for expansion
38  bool  epsilon_on_replace;
39
40  ReplaceFstOptions(const CacheOptions &opts, int64 r)
41      : CacheOptions(opts), root(r), epsilon_on_replace(false) {}
42  explicit ReplaceFstOptions(int64 r)
43      : root(r), epsilon_on_replace(false) {}
44  ReplaceFstOptions(int64 r, bool epsilon_replace_arc)
45      : root(r), epsilon_on_replace(epsilon_replace_arc) {}
46  ReplaceFstOptions()
47      : root(kNoLabel), epsilon_on_replace(false) {}
48};
49
50//
51// \class ReplaceFstImpl
52// \brief Implementation class for replace class Fst
53//
54// The replace implementation class supports a dynamic
55// expansion of a recursive transition network represented as Fst
56// with dynamic replacable arcs.
57//
58template <class A>
59class ReplaceFstImpl : public CacheImpl<A> {
60 public:
61  using FstImpl<A>::SetType;
62  using FstImpl<A>::SetProperties;
63  using FstImpl<A>::Properties;
64  using FstImpl<A>::SetInputSymbols;
65  using FstImpl<A>::SetOutputSymbols;
66  using FstImpl<A>::InputSymbols;
67  using FstImpl<A>::OutputSymbols;
68
69  using CacheImpl<A>::HasStart;
70  using CacheImpl<A>::HasArcs;
71  using CacheImpl<A>::SetStart;
72
73  typedef typename A::Label   Label;
74  typedef typename A::Weight  Weight;
75  typedef typename A::StateId StateId;
76  typedef CacheState<A> State;
77  typedef A Arc;
78  typedef hash_map<Label, Label> NonTerminalHash;
79
80
81  // \struct StateTuple
82  // \brief Tuple of information that uniquely defines a state
83  struct StateTuple {
84    typedef int PrefixId;
85
86    StateTuple() {}
87    StateTuple(PrefixId p, StateId f, StateId s) :
88        prefix_id(p), fst_id(f), fst_state(s) {}
89
90    PrefixId prefix_id;  // index in prefix table
91    StateId fst_id;      // current fst being walked
92    StateId fst_state;   // current state in fst being walked, not to be
93                         // confused with the state_id of the combined fst
94  };
95
96  // constructor for replace class implementation.
97  // \param fst_tuples array of label/fst tuples, one for each non-terminal
98  ReplaceFstImpl(const vector< pair<Label, const Fst<A>* > >& fst_tuples,
99                 const ReplaceFstOptions &opts)
100      : CacheImpl<A>(opts), opts_(opts) {
101    SetType("replace");
102    if (fst_tuples.size() > 0) {
103      SetInputSymbols(fst_tuples[0].second->InputSymbols());
104      SetOutputSymbols(fst_tuples[0].second->OutputSymbols());
105    }
106
107    fst_array_.push_back(0);
108    for (size_t i = 0; i < fst_tuples.size(); ++i)
109      AddFst(fst_tuples[i].first, fst_tuples[i].second);
110
111    SetRoot(opts.root);
112  }
113
114  explicit ReplaceFstImpl(const ReplaceFstOptions &opts)
115      : CacheImpl<A>(opts), opts_(opts), root_(kNoLabel) {
116    fst_array_.push_back(0);
117  }
118
119  ReplaceFstImpl(const ReplaceFstImpl& impl)
120      : opts_(impl.opts_), state_tuples_(impl.state_tuples_),
121        state_hash_(impl.state_hash_),
122        prefix_hash_(impl.prefix_hash_),
123        stackprefix_array_(impl.stackprefix_array_),
124        nonterminal_hash_(impl.nonterminal_hash_),
125        root_(impl.root_) {
126    SetType("replace");
127    SetProperties(impl.Properties(), kCopyProperties);
128    SetInputSymbols(InputSymbols());
129    SetOutputSymbols(OutputSymbols());
130    fst_array_.reserve(impl.fst_array_.size());
131    fst_array_.push_back(0);
132    for (size_t i = 1; i < impl.fst_array_.size(); ++i)
133      fst_array_.push_back(impl.fst_array_[i]->Copy());
134  }
135
136  ~ReplaceFstImpl() {
137    for (size_t i = 1; i < fst_array_.size(); ++i) {
138      delete fst_array_[i];
139    }
140  }
141
142  // Add to Fst array
143  void AddFst(Label label, const Fst<A>* fst) {
144    nonterminal_hash_[label] = fst_array_.size();
145    fst_array_.push_back(fst->Copy());
146    if (fst_array_.size() > 1) {
147      vector<uint64> inprops(fst_array_.size());
148
149      for (size_t i = 1; i < fst_array_.size(); ++i) {
150        inprops[i] = fst_array_[i]->Properties(kCopyProperties, false);
151      }
152      SetProperties(ReplaceProperties(inprops));
153
154      const SymbolTable* isymbols = fst_array_[1]->InputSymbols();
155      const SymbolTable* osymbols = fst_array_[1]->OutputSymbols();
156      for (size_t i = 2; i < fst_array_.size(); ++i) {
157        if (!CompatSymbols(isymbols, fst_array_[i]->InputSymbols())) {
158          LOG(FATAL) << "ReplaceFst::AddFst input symbols of Fst " << i-1
159                     << " does not match input symbols of base Fst (0'th fst)";
160        }
161        if (!CompatSymbols(osymbols, fst_array_[i]->OutputSymbols())) {
162          LOG(FATAL) << "ReplaceFst::AddFst output symbols of Fst " << i-1
163                     << " does not match output symbols of base Fst "
164                     << "(0'th fst)";
165        }
166      }
167    }
168  }
169
170  // Computes the dependency graph of the replace class and returns
171  // true if the dependencies are cyclic. Cyclic dependencies will result
172  // in an un-expandable replace fst.
173  bool CyclicDependencies() const {
174    StdVectorFst depfst;
175
176    // one state for each fst
177    for (size_t i = 1; i < fst_array_.size(); ++i)
178      depfst.AddState();
179
180    // an arc from each state (representing the fst) to the
181    // state representing the fst being replaced
182    for (size_t i = 1; i < fst_array_.size(); ++i) {
183      for (StateIterator<Fst<A> > siter(*(fst_array_[i]));
184           !siter.Done(); siter.Next()) {
185        for (ArcIterator<Fst<A> > aiter(*(fst_array_[i]), siter.Value());
186             !aiter.Done(); aiter.Next()) {
187          const A& arc = aiter.Value();
188
189          typename NonTerminalHash::const_iterator it =
190              nonterminal_hash_.find(arc.olabel);
191          if (it != nonterminal_hash_.end()) {
192            Label j = it->second - 1;
193            depfst.AddArc(i - 1, A(arc.olabel, arc.olabel, Weight::One(), j));
194          }
195        }
196      }
197    }
198
199    depfst.SetStart(root_ - 1);
200    depfst.SetFinal(root_ - 1, Weight::One());
201    return depfst.Properties(kCyclic, true);
202  }
203
204  // set root rule for expansion
205  void SetRoot(Label root) {
206    Label nonterminal = nonterminal_hash_[root];
207    root_ = (nonterminal > 0) ? nonterminal : 1;
208  }
209
210  // Change Fst array
211  void SetFst(Label label, const Fst<A>* fst) {
212    Label nonterminal = nonterminal_hash_[label];
213    delete fst_array_[nonterminal];
214    fst_array_[nonterminal] = fst->Copy();
215  }
216
217  // Return or compute start state of replace fst
218  StateId Start() {
219    if (!HasStart()) {
220      if (fst_array_.size() == 1) {      // no fsts defined for replace
221        SetStart(kNoStateId);
222        return kNoStateId;
223      } else {
224        const Fst<A>* fst = fst_array_[root_];
225        StateId fst_start = fst->Start();
226        if (fst_start == kNoStateId)  // root Fst is empty
227          return kNoStateId;
228
229        int prefix = PrefixId(StackPrefix());
230        StateId start = FindState(StateTuple(prefix, root_, fst_start));
231        SetStart(start);
232        return start;
233      }
234    } else {
235      return CacheImpl<A>::Start();
236    }
237  }
238
239  // return final weight of state (kInfWeight means state is not final)
240  Weight Final(StateId s) {
241    if (!HasFinal(s)) {
242      const StateTuple& tuple  = state_tuples_[s];
243      const StackPrefix& stack = stackprefix_array_[tuple.prefix_id];
244      const Fst<A>* fst = fst_array_[tuple.fst_id];
245      StateId fst_state = tuple.fst_state;
246
247      if (fst->Final(fst_state) != Weight::Zero() && stack.Depth() == 0)
248        SetFinal(s, fst->Final(fst_state));
249      else
250        SetFinal(s, Weight::Zero());
251    }
252    return CacheImpl<A>::Final(s);
253  }
254
255  size_t NumArcs(StateId s) {
256    if (!HasArcs(s))
257      Expand(s);
258    return CacheImpl<A>::NumArcs(s);
259  }
260
261  size_t NumInputEpsilons(StateId s) {
262    if (!HasArcs(s))
263      Expand(s);
264    return CacheImpl<A>::NumInputEpsilons(s);
265  }
266
267  size_t NumOutputEpsilons(StateId s) {
268    if (!HasArcs(s))
269      Expand(s);
270    return CacheImpl<A>::NumOutputEpsilons(s);
271  }
272
273  // return the base arc iterator, if arcs have not been computed yet,
274  // extend/recurse for new arcs.
275  void InitArcIterator(StateId s, ArcIteratorData<A> *data) {
276    if (!HasArcs(s))
277      Expand(s);
278    CacheImpl<A>::InitArcIterator(s, data);
279  }
280
281  // Find/create an Fst state given a StateTuple.  Only create a new
282  // state if StateTuple is not found in the state hash.
283  StateId FindState(const StateTuple& tuple) {
284    typename StateTupleHash::iterator it = state_hash_.find(tuple);
285    if (it == state_hash_.end()) {
286      StateId new_state_id = state_tuples_.size();
287      state_tuples_.push_back(tuple);
288      state_hash_[tuple] = new_state_id;
289      return new_state_id;
290    } else {
291      return it->second;
292    }
293  }
294
295  // extend current state (walk arcs one level deep)
296  void Expand(StateId s) {
297    StateTuple tuple  = state_tuples_[s];
298    const Fst<A>* fst = fst_array_[tuple.fst_id];
299    StateId fst_state = tuple.fst_state;
300    if (fst_state == kNoStateId) {
301      SetArcs(s);
302      return;
303    }
304
305    // if state is final, pop up stack
306    const StackPrefix& stack = stackprefix_array_[tuple.prefix_id];
307    if (fst->Final(fst_state) != Weight::Zero() && stack.Depth()) {
308      int prefix_id = PopPrefix(stack);
309      const PrefixTuple& top = stack.Top();
310
311      StateId nextstate =
312        FindState(StateTuple(prefix_id, top.fst_id, top.nextstate));
313      AddArc(s, A(0, 0, fst->Final(fst_state), nextstate));
314    }
315
316    // extend arcs leaving the state
317    for (ArcIterator< Fst<A> > aiter(*fst, fst_state);
318         !aiter.Done(); aiter.Next()) {
319      const Arc& arc = aiter.Value();
320      if (arc.olabel == 0) {  // expand local fst
321        StateId nextstate =
322          FindState(StateTuple(tuple.prefix_id, tuple.fst_id, arc.nextstate));
323        AddArc(s, A(arc.ilabel, arc.olabel, arc.weight, nextstate));
324      } else {
325        // check for non terminal
326        typename NonTerminalHash::const_iterator it =
327            nonterminal_hash_.find(arc.olabel);
328        if (it != nonterminal_hash_.end()) {  // recurse into non terminal
329          Label nonterminal = it->second;
330          const Fst<A>* nt_fst = fst_array_[nonterminal];
331          int nt_prefix = PushPrefix(stackprefix_array_[tuple.prefix_id],
332                                     tuple.fst_id, arc.nextstate);
333
334          // if start state is valid replace, else arc is implicitly
335          // deleted
336          StateId nt_start = nt_fst->Start();
337          if (nt_start != kNoStateId) {
338            StateId nt_nextstate = FindState(
339                StateTuple(nt_prefix, nonterminal, nt_start));
340            Label ilabel = (opts_.epsilon_on_replace) ? 0 : arc.ilabel;
341            AddArc(s, A(ilabel, 0, arc.weight, nt_nextstate));
342          }
343        } else {
344          StateId nextstate =
345            FindState(
346                StateTuple(tuple.prefix_id, tuple.fst_id, arc.nextstate));
347          AddArc(s, A(arc.ilabel, arc.olabel, arc.weight, nextstate));
348        }
349      }
350    }
351
352    SetArcs(s);
353  }
354
355
356  // private helper classes
357 private:
358  static const int kPrime0 = 7853;
359  static const int kPrime1 = 7867;
360
361  // \class StateTupleEqual
362  // \brief Compare two StateTuples for equality
363  class StateTupleEqual {
364   public:
365    bool operator()(const StateTuple& x, const StateTuple& y) const {
366      return ((x.prefix_id == y.prefix_id) && (x.fst_id == y.fst_id) &&
367              (x.fst_state == y.fst_state));
368    }
369  };
370
371  // \class StateTupleKey
372  // \brief Hash function for StateTuple to Fst states
373  class StateTupleKey {
374   public:
375    size_t operator()(const StateTuple& x) const {
376      return static_cast<size_t>(x.prefix_id +
377                                 x.fst_id * kPrime0 +
378                                 x.fst_state * kPrime1);
379    }
380  };
381
382  typedef hash_map<StateTuple, StateId, StateTupleKey, StateTupleEqual>
383  StateTupleHash;
384
385  // \class PrefixTuple
386  // \brief Tuple of fst_id and destination state (entry in stack prefix)
387  struct PrefixTuple {
388    PrefixTuple(Label f, StateId s) : fst_id(f), nextstate(s) {}
389
390    Label   fst_id;
391    StateId nextstate;
392  };
393
394  // \class StackPrefix
395  // \brief Container for stack prefix.
396  class StackPrefix {
397   public:
398    StackPrefix() {}
399
400    // copy constructor
401    StackPrefix(const StackPrefix& x) :
402        prefix_(x.prefix_) {
403    }
404
405    void Push(int fst_id, StateId nextstate) {
406      prefix_.push_back(PrefixTuple(fst_id, nextstate));
407    }
408
409    void Pop() {
410      prefix_.pop_back();
411    }
412
413    const PrefixTuple& Top() const {
414      return prefix_[prefix_.size()-1];
415    }
416
417    size_t Depth() const {
418      return prefix_.size();
419    }
420
421   public:
422    vector<PrefixTuple> prefix_;
423  };
424
425
426  // \class StackPrefixEqual
427  // \brief Compare two stack prefix classes for equality
428  class StackPrefixEqual {
429   public:
430    bool operator()(const StackPrefix& x, const StackPrefix& y) const {
431      if (x.prefix_.size() != y.prefix_.size()) return false;
432      for (size_t i = 0; i < x.prefix_.size(); ++i) {
433        if (x.prefix_[i].fst_id    != y.prefix_[i].fst_id ||
434           x.prefix_[i].nextstate != y.prefix_[i].nextstate) return false;
435      }
436      return true;
437    }
438  };
439
440  //
441  // \class StackPrefixKey
442  // \brief Hash function for stack prefix to prefix id
443  class StackPrefixKey {
444   public:
445    size_t operator()(const StackPrefix& x) const {
446      int sum = 0;
447      for (size_t i = 0; i < x.prefix_.size(); ++i) {
448        sum += x.prefix_[i].fst_id + x.prefix_[i].nextstate*kPrime0;
449      }
450      return (size_t) sum;
451    }
452  };
453
454  typedef hash_map<StackPrefix, int, StackPrefixKey, StackPrefixEqual>
455  StackPrefixHash;
456
457  // private methods
458 private:
459  // hash stack prefix (return unique index into stackprefix array)
460  int PrefixId(const StackPrefix& prefix) {
461    typename StackPrefixHash::iterator it = prefix_hash_.find(prefix);
462    if (it == prefix_hash_.end()) {
463      int prefix_id = stackprefix_array_.size();
464      stackprefix_array_.push_back(prefix);
465      prefix_hash_[prefix] = prefix_id;
466      return prefix_id;
467    } else {
468      return it->second;
469    }
470  }
471
472  // prefix id after a stack pop
473  int PopPrefix(StackPrefix prefix) {
474    prefix.Pop();
475    return PrefixId(prefix);
476  }
477
478  // prefix id after a stack push
479  int PushPrefix(StackPrefix prefix, Label fst_id, StateId nextstate) {
480    prefix.Push(fst_id, nextstate);
481    return PrefixId(prefix);
482  }
483
484
485  // private data
486 private:
487  // runtime options
488  ReplaceFstOptions opts_;
489
490  // maps from StateId to StateTuple
491  vector<StateTuple> state_tuples_;
492
493  // hashes from StateTuple to StateId
494  StateTupleHash state_hash_;
495
496  // cross index of unique stack prefix
497  // could potentially have one copy of prefix array
498  StackPrefixHash prefix_hash_;
499  vector<StackPrefix> stackprefix_array_;
500
501  NonTerminalHash nonterminal_hash_;
502  vector<const Fst<A>*> fst_array_;
503
504  Label root_;
505
506  void operator=(const ReplaceFstImpl<A> &);  // disallow
507};
508
509
510//
511// \class ReplaceFst
512// \brief Recursivively replaces arcs in the root Fst with other Fsts.
513// This version is a delayed Fst.
514//
515// ReplaceFst supports dynamic replacement of arcs in one Fst with
516// another Fst. This replacement is recursive.  ReplaceFst can be used
517// to support a variety of delayed constructions such as recursive
518// transition networks, union, or closure.  It is constructed with an
519// array of Fst(s). One Fst represents the root (or topology)
520// machine. The root Fst refers to other Fsts by recursively replacing
521// arcs labeled as non-terminals with the matching non-terminal
522// Fst. Currently the ReplaceFst uses the output symbols of the arcs
523// to determine whether the arc is a non-terminal arc or not. A
524// non-terminal can be any label that is not a non-zero terminal label
525// in the output alphabet.
526//
527// Note that the constructor uses a vector of pair<>. These correspond
528// to the tuple of non-terminal Label and corresponding Fst. For example
529// to implement the closure operation we need 2 Fsts. The first root
530// Fst is a single Arc on the start State that self loops, it references
531// the particular machine for which we are performing the closure operation.
532//
533template <class A>
534class ReplaceFst : public Fst<A> {
535 public:
536  friend class ArcIterator< ReplaceFst<A> >;
537  friend class CacheStateIterator< ReplaceFst<A> >;
538  friend class CacheArcIterator< ReplaceFst<A> >;
539
540  typedef A Arc;
541  typedef typename A::Label   Label;
542  typedef typename A::Weight  Weight;
543  typedef typename A::StateId StateId;
544  typedef CacheState<A> State;
545
546  ReplaceFst(const vector<pair<Label, const Fst<A>* > >& fst_array,
547             Label root)
548      : impl_(new ReplaceFstImpl<A>(fst_array, ReplaceFstOptions(root))) {}
549
550  ReplaceFst(const vector<pair<Label, const Fst<A>* > >& fst_array,
551             const ReplaceFstOptions &opts)
552      : impl_(new ReplaceFstImpl<A>(fst_array, opts)) {}
553
554  ReplaceFst(const ReplaceFst<A>& fst) :
555      impl_(new ReplaceFstImpl<A>(*(fst.impl_))) {}
556
557  virtual ~ReplaceFst() {
558    delete impl_;
559  }
560
561  virtual StateId Start() const {
562    return impl_->Start();
563  }
564
565  virtual Weight Final(StateId s) const {
566    return impl_->Final(s);
567  }
568
569  virtual size_t NumArcs(StateId s) const {
570    return impl_->NumArcs(s);
571  }
572
573  virtual size_t NumInputEpsilons(StateId s) const {
574    return impl_->NumInputEpsilons(s);
575  }
576
577  virtual size_t NumOutputEpsilons(StateId s) const {
578    return impl_->NumOutputEpsilons(s);
579  }
580
581  virtual uint64 Properties(uint64 mask, bool test) const {
582    if (test) {
583      uint64 known, test = TestProperties(*this, mask, &known);
584      impl_->SetProperties(test, known);
585      return test & mask;
586    } else {
587      return impl_->Properties(mask);
588    }
589  }
590
591  virtual const string& Type() const {
592    return impl_->Type();
593  }
594
595  virtual ReplaceFst<A>* Copy() const {
596    return new ReplaceFst<A>(*this);
597  }
598
599  virtual const SymbolTable* InputSymbols() const {
600    return impl_->InputSymbols();
601  }
602
603  virtual const SymbolTable* OutputSymbols() const {
604    return impl_->OutputSymbols();
605  }
606
607  virtual inline void InitStateIterator(StateIteratorData<A> *data) const;
608
609  virtual void InitArcIterator(StateId s, ArcIteratorData<A> *data) const {
610    impl_->InitArcIterator(s, data);
611  }
612
613  bool CyclicDependencies() const {
614    return impl_->CyclicDependencies();
615  }
616
617 private:
618  ReplaceFstImpl<A>* impl_;
619};
620
621
622// Specialization for ReplaceFst.
623template<class A>
624class StateIterator< ReplaceFst<A> >
625    : public CacheStateIterator< ReplaceFst<A> > {
626 public:
627  explicit StateIterator(const ReplaceFst<A> &fst)
628      : CacheStateIterator< ReplaceFst<A> >(fst) {}
629
630 private:
631  DISALLOW_EVIL_CONSTRUCTORS(StateIterator);
632};
633
634// Specialization for ReplaceFst.
635template <class A>
636class ArcIterator< ReplaceFst<A> >
637    : public CacheArcIterator< ReplaceFst<A> > {
638 public:
639  typedef typename A::StateId StateId;
640
641  ArcIterator(const ReplaceFst<A> &fst, StateId s)
642      : CacheArcIterator< ReplaceFst<A> >(fst, s) {
643    if (!fst.impl_->HasArcs(s))
644      fst.impl_->Expand(s);
645  }
646
647 private:
648  DISALLOW_EVIL_CONSTRUCTORS(ArcIterator);
649};
650
651template <class A> inline
652void ReplaceFst<A>::InitStateIterator(StateIteratorData<A> *data) const {
653  data->base = new StateIterator< ReplaceFst<A> >(*this);
654}
655
656typedef ReplaceFst<StdArc> StdReplaceFst;
657
658
659// // Recursivively replaces arcs in the root Fst with other Fsts.
660// This version writes the result of replacement to an output MutableFst.
661//
662// Replace supports replacement of arcs in one Fst with another
663// Fst. This replacement is recursive.  Replace takes an array of
664// Fst(s). One Fst represents the root (or topology) machine. The root
665// Fst refers to other Fsts by recursively replacing arcs labeled as
666// non-terminals with the matching non-terminal Fst. Currently Replace
667// uses the output symbols of the arcs to determine whether the arc is
668// a non-terminal arc or not. A non-terminal can be any label that is
669// not a non-zero terminal label in the output alphabet.  Note that
670// input argument is a vector of pair<>. These correspond to the tuple
671// of non-terminal Label and corresponding Fst.
672template<class Arc>
673void Replace(const vector<pair<typename Arc::Label,
674             const Fst<Arc>* > >& ifst_array,
675             MutableFst<Arc> *ofst, typename Arc::Label root) {
676  ReplaceFstOptions opts(root);
677  opts.gc_limit = 0;  // Cache only the last state for fastest copy.
678  *ofst = ReplaceFst<Arc>(ifst_array, opts);
679}
680
681}
682
683#endif  // FST_LIB_REPLACE_H__
684