// See www.openfst.org for extensive documentation on this weighted // finite-state transducer library. // // Utility class for regression testing of FST weights. #ifndef FST_TEST_WEIGHT_TESTER_H_ #define FST_TEST_WEIGHT_TESTER_H_ #include #include #include #include #include namespace fst { // This class tests a variety of identities and properties that must // hold for the Weight class to be well-defined. It calls function object // WEIGHT_GENERATOR to select weights that are used in the tests. template class WeightTester { public: WeightTester(WeightGenerator generator) : weight_generator_(std::move(generator)) {} void Test(int iterations, bool test_division = true) { for (int i = 0; i < iterations; ++i) { // Selects the test weights. const Weight w1(weight_generator_()); const Weight w2(weight_generator_()); const Weight w3(weight_generator_()); VLOG(1) << "weight type = " << Weight::Type(); VLOG(1) << "w1 = " << w1; VLOG(1) << "w2 = " << w2; VLOG(1) << "w3 = " << w3; TestSemiring(w1, w2, w3); if (test_division) TestDivision(w1, w2); TestReverse(w1, w2); TestEquality(w1, w2, w3); TestIO(w1); TestCopy(w1); } } private: // Note in the tests below we use ApproxEqual rather than == and add // kDelta to inequalities where the weights might be inexact. // Tests (Plus, Times, Zero, One) defines a commutative semiring. void TestSemiring(Weight w1, Weight w2, Weight w3) { // Checks that the operations are closed. CHECK(Plus(w1, w2).Member()); CHECK(Times(w1, w2).Member()); // Checks that the operations are associative. CHECK(ApproxEqual(Plus(w1, Plus(w2, w3)), Plus(Plus(w1, w2), w3))); CHECK(ApproxEqual(Times(w1, Times(w2, w3)), Times(Times(w1, w2), w3))); // Checks the identity elements. CHECK(Plus(w1, Weight::Zero()) == w1); CHECK(Plus(Weight::Zero(), w1) == w1); CHECK(Times(w1, Weight::One()) == w1); CHECK(Times(Weight::One(), w1) == w1); // Check the no weight element. CHECK(!Weight::NoWeight().Member()); CHECK(!Plus(w1, Weight::NoWeight()).Member()); CHECK(!Plus(Weight::NoWeight(), w1).Member()); CHECK(!Times(w1, Weight::NoWeight()).Member()); CHECK(!Times(Weight::NoWeight(), w1).Member()); // Checks that the operations commute. CHECK(ApproxEqual(Plus(w1, w2), Plus(w2, w1))); if (Weight::Properties() & kCommutative) CHECK(ApproxEqual(Times(w1, w2), Times(w2, w1))); // Checks Zero() is the annihilator. CHECK(Times(w1, Weight::Zero()) == Weight::Zero()); CHECK(Times(Weight::Zero(), w1) == Weight::Zero()); // Check Power(w, 0) is Weight::One() CHECK(Power(w1, 0) == Weight::One()); // Check Power(w, 1) is w CHECK(Power(w1, 1) == w1); // Check Power(w, 3) is Times(w, Times(w, w)) CHECK(Power(w1, 3) == Times(w1, Times(w1, w1))); // Checks distributivity. if (Weight::Properties() & kLeftSemiring) { CHECK(ApproxEqual(Times(w1, Plus(w2, w3)), Plus(Times(w1, w2), Times(w1, w3)))); } if (Weight::Properties() & kRightSemiring) CHECK(ApproxEqual(Times(Plus(w1, w2), w3), Plus(Times(w1, w3), Times(w2, w3)))); if (Weight::Properties() & kIdempotent) CHECK(Plus(w1, w1) == w1); if (Weight::Properties() & kPath) CHECK(Plus(w1, w2) == w1 || Plus(w1, w2) == w2); // Ensure weights form a left or right semiring. CHECK(Weight::Properties() & (kLeftSemiring | kRightSemiring)); // Check when Times() is commutative that it is marked as a semiring. if (Weight::Properties() & kCommutative) CHECK(Weight::Properties() & kSemiring); } // Tests division operation. void TestDivision(Weight w1, Weight w2) { Weight p = Times(w1, w2); if (Weight::Properties() & kLeftSemiring) { Weight d = Divide(p, w1, DIVIDE_LEFT); if (d.Member()) CHECK(ApproxEqual(p, Times(w1, d))); CHECK(!Divide(w1, Weight::NoWeight(), DIVIDE_LEFT).Member()); CHECK(!Divide(Weight::NoWeight(), w1, DIVIDE_LEFT).Member()); } if (Weight::Properties() & kRightSemiring) { Weight d = Divide(p, w2, DIVIDE_RIGHT); if (d.Member()) CHECK(ApproxEqual(p, Times(d, w2))); CHECK(!Divide(w1, Weight::NoWeight(), DIVIDE_RIGHT).Member()); CHECK(!Divide(Weight::NoWeight(), w1, DIVIDE_RIGHT).Member()); } if (Weight::Properties() & kCommutative) { Weight d = Divide(p, w1, DIVIDE_RIGHT); if (d.Member()) CHECK(ApproxEqual(p, Times(d, w1))); } } // Tests reverse operation. void TestReverse(Weight w1, Weight w2) { typedef typename Weight::ReverseWeight ReverseWeight; ReverseWeight rw1 = w1.Reverse(); ReverseWeight rw2 = w2.Reverse(); CHECK(rw1.Reverse() == w1); CHECK(Plus(w1, w2).Reverse() == Plus(rw1, rw2)); CHECK(Times(w1, w2).Reverse() == Times(rw2, rw1)); } // Tests == is an equivalence relation. void TestEquality(Weight w1, Weight w2, Weight w3) { // Checks reflexivity. CHECK(w1 == w1); // Checks symmetry. CHECK((w1 == w2) == (w2 == w1)); // Checks transitivity. if (w1 == w2 && w2 == w3) CHECK(w1 == w3); } // Tests binary serialization and textual I/O. void TestIO(Weight w) { // Tests binary I/O { std::ostringstream os; w.Write(os); os.flush(); std::istringstream is(os.str()); Weight v; v.Read(is); CHECK_EQ(w, v); } // Tests textual I/O. { std::ostringstream os; os << w; std::istringstream is(os.str()); Weight v(Weight::One()); is >> v; CHECK(ApproxEqual(w, v)); } } // Tests copy constructor and assignment operator void TestCopy(Weight w) { Weight x = w; CHECK(w == x); x = Weight(w); CHECK(w == x); x.operator=(x); CHECK(w == x); } // Generates weights used in testing. WeightGenerator weight_generator_; }; } // namespace fst #endif // FST_TEST_WEIGHT_TESTER_H_