From b25401c254b06c8bac4a741106918fd37d43f56d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 20 Sep 2018 23:44:51 +0200 Subject: [PATCH] Added twofold-typed box scanner. --- src/db/db/dbBoxScanner.h | 371 +++++++++++++++++++++++++++++- src/db/unit_tests/dbBoxScanner.cc | 247 ++++++++++++++++++++ 2 files changed, 617 insertions(+), 1 deletion(-) diff --git a/src/db/db/dbBoxScanner.h b/src/db/db/dbBoxScanner.h index 52423afe0..4d6ee0f70 100644 --- a/src/db/db/dbBoxScanner.h +++ b/src/db/db/dbBoxScanner.h @@ -122,7 +122,7 @@ struct box_scanner_receiver */ void finish (const Obj * /*obj*/, const Prop & /*prop*/) { } - /* + /** * @brief Callback for an interaction of o1 with o2. * * This method is called when the object o1 interacts with o2 within the current @@ -389,6 +389,375 @@ private: std::string m_progress_desc; }; +/** + * @brief A template for the twofold box scanner output receiver + * + * This template specifies the methods or provides a default implementation for them + * for use as the output receiver of the twofold box scanner. + */ +template +struct box_scanner_receiver2 +{ + /** + * @brief Indicates that the given object of first type is no longer used + * + * The finish1 method is called when an object of the first type is no longer in the queue and can be + * discarded. + */ + void finish1 (const Obj1 * /*obj*/, const Prop1 & /*prop*/) { } + + /** + * @brief Indicates that the given object of second type is no longer used + * + * The finish method is called when an object of the second type is no longer in the queue and can be + * discarded. + */ + void finish2 (const Obj2 * /*obj*/, const Prop2 & /*prop*/) { } + + /** + * @brief Callback for an interaction of o1 with o2. + * + * This method is called when the object o1 interacts with o2 within the current + * definition. + */ + void add (const Obj1 * /*o1*/, const Prop1 & /*p1*/, const Obj2 * /*o2*/, const Prop2 & /*p2*/) { } +}; + +/** + * @brief A box scanner framework (twofold version) + * + * This implementation provides a box scanner for two different types. Apart from + * that it is similar to the uniform-type box scanner. + * + * It will not report interactions within the Obj1 or Obj2 group, but only + * interactions between Obj1 and Obj2 objects. + */ +template +class box_scanner2 +{ +public: + typedef Obj1 object_type1; + typedef Obj2 object_type2; + typedef std::vector > container_type1; + typedef std::vector > container_type2; + typedef typename container_type1::iterator iterator_type1; + typedef typename container_type2::iterator iterator_type2; + + /** + * @brief Default ctor + */ + box_scanner2 (bool report_progress = false, const std::string &progress_desc = std::string ()) + : m_fill_factor (2), m_scanner_thr (100), + m_report_progress (report_progress), m_progress_desc (progress_desc) + { + // .. nothing yet .. + } + + /** + * @brief Sets the scanner threshold + * + * This value determines for how many elements the implementation switches to the scanner + * implementation instead of the plain element-by-element interaction test. + * The default value is 100. + */ + void set_scanner_threshold (size_t n) + { + m_scanner_thr = n; + } + + /** + * @brief Gets the scanner threshold + */ + size_t scanner_threshold () const + { + return m_scanner_thr; + } + + /** + * @brief Sets the fill factor + * + * The fill factor determines how many new entries will be collected for a band. + * A fill factor of 2 means that the number of elements in the band will be + * doubled after elements outside of the band have been removed. + * The default fill factor is 2. + */ + void set_fill_factor (double ff) + { + m_fill_factor = ff; + } + + /** + * @brief Gets the fill factor + */ + double fill_factor () const + { + return m_fill_factor; + } + + /** + * @brief Reserve for n elements of Obj1 type + */ + void reserve1 (size_t n) + { + m_pp1.reserve (n); + } + + /** + * @brief Reserve for n elements of Obj2 type + */ + void reserve2 (size_t n) + { + m_pp2.reserve (n); + } + + /** + * @brief Clears the container + */ + void clear () + { + m_pp1.clear (); + m_pp2.clear (); + } + + /** + * @brief Inserts a new object of type Obj1 into the scanner + * + * The object's pointer is stored, so the object must remain valid until the + * scanner does not need it any longer. An additional property can be attached to + * the object which will be stored along with the object. + */ + void insert1 (const Obj1 *obj, const Prop1 &prop) + { + m_pp1.push_back (std::make_pair (obj, prop)); + } + + /** + * @brief Inserts a new object of type Obj2 into the scanner + * + * The object's pointer is stored, so the object must remain valid until the + * scanner does not need it any longer. An additional property can be attached to + * the object which will be stored along with the object. + */ + void insert2 (const Obj2 *obj, const Prop2 &prop) + { + m_pp2.push_back (std::make_pair (obj, prop)); + } + + /** + * @brief Get the interactions between the stored objects + * + * Two objects interact if the boxes of the objects enlarged by the given value overlap. + * The enlargement is specified in units of width and height, i.e. half of the enlargement + * is applied to one side before the overlap check. + * + * An enlargement of 1 means that boxes have to touch only in order to get an interaction. + * + * The box scanner will report all interactions of type Obj1 and Obj2 objects to the receiver object. + * See box_scanner_receiver2 for details about the methods that this object must provide. + * + * The box converter 1 must be capable of converting the Obj1 object into a box. + * It must provide a box_type typedef. The box converter 2 must be able to convert Obj2 to + * a box. The box type of both box converters must be identical. + */ + template + void process (Rec &rec, typename BoxConvert1::box_type::coord_type enl, const BoxConvert1 &bc1 = BoxConvert1 (), const BoxConvert2 &bc2 = BoxConvert2 ()) + { + typedef typename BoxConvert1::box_type box_type; // must be same as BoxConvert2::box_type + typedef typename box_type::coord_type coord_type; + typedef bs_side_compare_func > bottom_side_compare_func1; + typedef bs_side_compare_func > left_side_compare_func1; + typedef bs_side_compare_vs_const_func > below_func1; + typedef bs_side_compare_vs_const_func > left_func1; + typedef bs_side_compare_func > bottom_side_compare_func2; + typedef bs_side_compare_func > left_side_compare_func2; + typedef bs_side_compare_vs_const_func > below_func2; + typedef bs_side_compare_vs_const_func > left_func2; + + if (m_pp1.empty () || m_pp2.empty ()) { + + // trivial case + + for (iterator_type1 i = m_pp1.begin (); i != m_pp1.end (); ++i) { + rec.finish1 (i->first, i->second); + } + for (iterator_type2 i = m_pp2.begin (); i != m_pp2.end (); ++i) { + rec.finish2 (i->first, i->second); + } + + } else if (m_pp1.size () + m_pp2.size () <= m_scanner_thr) { + + // below m_scanner_thr elements use the brute force approach which is faster in that case + + for (iterator_type1 i = m_pp1.begin (); i != m_pp1.end (); ++i) { + for (iterator_type2 j = m_pp2.begin (); j != m_pp2.end (); ++j) { + if (bs_boxes_overlap (bc1 (*i->first), bc2 (*j->first), enl)) { + rec.add (i->first, i->second, j->first, j->second); + } + } + } + + for (iterator_type1 i = m_pp1.begin (); i != m_pp1.end (); ++i) { + rec.finish1 (i->first, i->second); + } + for (iterator_type2 i = m_pp2.begin (); i != m_pp2.end (); ++i) { + rec.finish2 (i->first, i->second); + } + + } else { + + std::set > seen1; + std::set > seen2; + + std::sort (m_pp1.begin (), m_pp1.end (), bottom_side_compare_func1 (bc1)); + std::sort (m_pp2.begin (), m_pp2.end (), bottom_side_compare_func2 (bc2)); + + coord_type y = std::min (bc1 (*m_pp1.front ().first).bottom (), bc2 (*m_pp2.front ().first).bottom ()); + + iterator_type1 current1 = m_pp1.begin (); + iterator_type1 future1 = m_pp1.begin (); + iterator_type2 current2 = m_pp2.begin (); + iterator_type2 future2 = m_pp2.begin (); + + std::auto_ptr progress (0); + if (m_report_progress) { + if (m_progress_desc.empty ()) { + progress.reset (new tl::RelativeProgress (tl::to_string (tr ("Processing")), m_pp1.size () + m_pp2.size (), 1000)); + } else { + progress.reset (new tl::RelativeProgress (m_progress_desc, m_pp1.size () + m_pp2.size (), 1000)); + } + } + + while (future1 != m_pp1.end () || future2 != m_pp2.end ()) { + + iterator_type1 cc1 = current1; + iterator_type2 cc2 = current2; + current1 = std::partition (current1, future1, below_func1 (bc1, y + 1 - enl)); + current2 = std::partition (current2, future2, below_func2 (bc2, y + 1 - enl)); + + while (cc1 != current1) { + rec.finish1 (cc1->first, cc1->second); + typename std::set >::iterator s; + s = seen1.lower_bound (std::make_pair (cc1->first, (const Obj2 *)0)); + while (s != seen1.end () && s->first == cc1->first) { + seen1.erase (s++); + } + ++cc1; + } + + while (cc2 != current2) { + rec.finish2 (cc2->first, cc2->second); + typename std::set >::iterator s; + s = seen2.lower_bound (std::make_pair (cc2->first, (const Obj1 *)0)); + while (s != seen2.end () && s->first == cc2->first) { + seen2.erase (s++); + } + ++cc2; + } + + // add at least the required items per band + size_t min_band_size = size_t ((future1 - current1) * m_fill_factor) + size_t ((future2 - current2) * m_fill_factor); + coord_type yy = y; + do { + if (future1 != m_pp1.end () && future2 != m_pp2.end ()) { + yy = std::min (bc1 (*future1->first).bottom (), bc2 (*future2->first).bottom ()); + } else if (future1 != m_pp1.end ()) { + yy = bc1 (*future1->first).bottom (); + } else { + yy = bc2 (*future2->first).bottom (); + } + while (future1 != m_pp1.end () && bc1 (*future1->first).bottom () == yy) { + ++future1; + } + while (future2 != m_pp2.end () && bc2 (*future2->first).bottom () == yy) { + ++future2; + } + } while ((future1 != m_pp1.end () || future2 != m_pp2.end ()) && size_t (future1 - current1) + size_t (future2 - current2) < min_band_size); + + if (current1 != future1 && current2 != future2) { + + std::sort (current1, future1, left_side_compare_func1 (bc1)); + std::sort (current2, future2, left_side_compare_func2 (bc2)); + + iterator_type1 c1 = current1; + iterator_type1 f1 = current1; + iterator_type2 c2 = current2; + iterator_type2 f2 = current2; + + coord_type x = std::min (bc1 (*c1->first).left (), bc2 (*c2->first).left ()); + + while (f1 != future1 || f2 != future2) { + + c1 = std::partition (c1, f1, left_func1 (bc1, x + 1 - enl)); + c2 = std::partition (c2, f2, left_func2 (bc2, x + 1 - enl)); + + // add at least the required items per band + size_t min_box_size = size_t ((f1 - c1) * m_fill_factor) + size_t ((f2 - c2) * m_fill_factor); + coord_type xx = x; + do { + if (f1 != future1 && f2 != future2) { + xx = std::min (bc1 (*f1->first).left (), bc2 (*f2->first).left ()); + } else if (f1 != future1) { + xx = bc1 (*f1->first).left (); + } else if (f2 != future2) { + xx = bc2 (*f2->first).left (); + } + while (f1 != future1 && bc1 (*f1->first).left () == xx) { + ++f1; + } + while (f2 != future2 && bc2 (*f2->first).left () == xx) { + ++f2; + } + } while ((f1 != future1 || f2 != future2) && size_t (f1 - c1) + size_t (f2 - c2) < min_box_size); + + if (c1 != f1 && c2 != f2) { + for (iterator_type1 i = c1; i != f1; ++i) { + for (iterator_type2 j = c2; j < f2; ++j) { + if (bs_boxes_overlap (bc1 (*i->first), bc2 (*j->first), enl)) { + if (seen1.insert (std::make_pair (i->first, j->first)).second) { + seen2.insert (std::make_pair (j->first, i->first)); + rec.add (i->first, i->second, j->first, j->second); + } + } + } + } + } + + x = xx; + + if (m_report_progress) { + progress->set (std::min (f1 - m_pp1.begin (), f2 - m_pp2.begin ())); + } + + } + + } + + y = yy; + + } + + while (current1 != m_pp1.end ()) { + rec.finish1 (current1->first, current1->second); + ++current1; + } + while (current2 != m_pp2.end ()) { + rec.finish2 (current2->first, current2->second); + ++current2; + } + + } + + } + +private: + container_type1 m_pp1; + container_type2 m_pp2; + double m_fill_factor; + size_t m_scanner_thr; + bool m_report_progress; + std::string m_progress_desc; +}; + /** * @brief A cluster template that stores properties * diff --git a/src/db/unit_tests/dbBoxScanner.cc b/src/db/unit_tests/dbBoxScanner.cc index 9ecc06d6b..211b5a4fa 100644 --- a/src/db/unit_tests/dbBoxScanner.cc +++ b/src/db/unit_tests/dbBoxScanner.cc @@ -56,6 +56,37 @@ struct BoxScannerTestRecorder2 std::set > interactions; }; +struct BoxScannerTestRecorderTwo +{ + void finish1 (const db::Box * /*box*/, size_t p) { + str += "<" + tl::to_string (p) + ">"; + } + + void finish2 (const db::SimplePolygon * /*poly*/, int p) { + str += "<" + tl::to_string (p) + ">"; + } + + void add (const db::Box * /*b1*/, size_t p1, const db::SimplePolygon * /*b2*/, int p2) + { + str += "(" + tl::to_string (p1) + "-" + tl::to_string (p2) + ")"; + } + + std::string str; +}; + +struct BoxScannerTestRecorder2Two +{ + void finish1 (const db::Box *, size_t) { } + void finish2 (const db::SimplePolygon *, int) { } + + void add (const db::Box * /*b1*/, size_t p1, const db::SimplePolygon * /*b2*/, int p2) + { + interactions.insert (std::make_pair (p1, p2)); + } + + std::set > interactions; +}; + TEST(1) { db::box_scanner bs; @@ -787,3 +818,219 @@ TEST(100) } + +TEST(two_1) +{ + db::box_scanner2 bs; + + std::vector bb; + std::vector bb2; + bb.push_back (db::Box (0, 210, 200, 310)); + bb.push_back (db::Box (10, 220, 210, 320)); + bb.push_back (db::Box (0, 0, 100, 100)); + bb.push_back (db::Box (50, 50, 150, 150)); + bb.push_back (db::Box (10, 10, 110, 110)); + bb.push_back (db::Box (100, 10, 200, 110)); + + for (std::vector::const_iterator b = bb.begin (); b != bb.end (); ++b) { + bb2.push_back (db::SimplePolygon (*b)); + } + + for (std::vector::const_iterator b = bb.begin (); b != bb.end (); ++b) { + bs.insert1 (&*b, b - bb.begin ()); + } + for (std::vector::const_iterator b = bb2.begin (); b != bb2.end (); ++b) { + bs.insert2 (&*b, int (b - bb2.begin ()) + 10); + } + + BoxScannerTestRecorderTwo tr; + bs.set_fill_factor (0.0); + db::box_convert bc1; + db::box_convert bc2; + bs.set_scanner_threshold (0); + bs.process (tr, 1, bc1, bc2); + EXPECT_EQ (tr.str, "(2-12)(2-14)(4-12)(4-14)(2-15)(4-15)(5-12)(5-14)(5-15)(2-13)(4-13)(3-12)(3-14)(3-13)(3-15)(5-13)(0-10)<2><5><4><3><12><15><14><13>(0-11)(1-10)(1-11)<0><1><10><11>"); +} + +TEST(two_1a) +{ + db::box_scanner2 bs; + + std::vector bb; + bb.push_back (db::Box (0, 210, 200, 310)); + //bb.push_back (db::Box (10, 220, 210, 320)); + //bb.push_back (db::Box (0, 0, 100, 100)); + bb.push_back (db::Box (50, 50, 150, 150)); + bb.push_back (db::Box (10, 10, 110, 110)); + //bb.push_back (db::Box (100, 10, 200, 110)); + + std::vector bb2; + //bb2.push_back (db::SimplePolygon (db::Box (0, 210, 200, 310))); + bb2.push_back (db::SimplePolygon (db::Box (10, 220, 210, 320))); + bb2.push_back (db::SimplePolygon (db::Box (0, 0, 100, 100))); + //bb2.push_back (db::SimplePolygon (db::Box (50, 50, 150, 150))); + //bb2.push_back (db::SimplePolygon (db::Box (10, 10, 110, 110))); + bb2.push_back (db::SimplePolygon (db::Box (100, 10, 200, 110))); + + for (std::vector::const_iterator b = bb.begin (); b != bb.end (); ++b) { + bs.insert1 (&*b, b - bb.begin ()); + } + for (std::vector::const_iterator b = bb2.begin (); b != bb2.end (); ++b) { + bs.insert2 (&*b, int (b - bb2.begin ()) + 10); + } + + BoxScannerTestRecorderTwo tr; + bs.set_fill_factor (0.0); + db::box_convert bc1; + db::box_convert bc2; + bs.set_scanner_threshold (0); + bs.process (tr, 1, bc1, bc2); + EXPECT_EQ (tr.str, "(2-11)(2-12)(1-11)(1-12)<1><2><11><12>(0-10)<0><10>"); +} + +TEST(two_1b) +{ + db::box_scanner2 bs; + + std::vector bb; + //bb.push_back (db::Box (0, 210, 200, 310)); + bb.push_back (db::Box (10, 220, 210, 320)); + bb.push_back (db::Box (0, 0, 100, 100)); + //bb.push_back (db::Box (50, 50, 150, 150)); + //bb.push_back (db::Box (10, 10, 110, 110)); + bb.push_back (db::Box (100, 10, 200, 110)); + + std::vector bb2; + bb2.push_back (db::SimplePolygon (db::Box (0, 210, 200, 310))); + //bb2.push_back (db::SimplePolygon (db::Box (10, 220, 210, 320))); + //bb2.push_back (db::SimplePolygon (db::Box (0, 0, 100, 100))); + bb2.push_back (db::SimplePolygon (db::Box (50, 50, 150, 150))); + bb2.push_back (db::SimplePolygon (db::Box (10, 10, 110, 110))); + //bb2.push_back (db::SimplePolygon (db::Box (100, 10, 200, 110))); + + for (std::vector::const_iterator b = bb.begin (); b != bb.end (); ++b) { + bs.insert1 (&*b, b - bb.begin ()); + } + for (std::vector::const_iterator b = bb2.begin (); b != bb2.end (); ++b) { + bs.insert2 (&*b, int (b - bb2.begin ()) + 10); + } + + BoxScannerTestRecorderTwo tr; + bs.set_fill_factor (0.0); + db::box_convert bc1; + db::box_convert bc2; + bs.set_scanner_threshold (0); + bs.process (tr, 1, bc1, bc2); + EXPECT_EQ (tr.str, "(1-12)(2-12)(1-11)(2-11)<1><2><11><12>(0-10)<0><10>"); +} + +void run_test2_two (tl::TestBase *_this, size_t n, double ff, db::Coord spread, bool touch = true) +{ + std::vector bb; + for (size_t i = 0; i < n; ++i) { + db::Coord x = rand () % spread; + db::Coord y = rand () % spread; + bb.push_back (db::Box (x, y, x + 100, y + 100)); + // std::cout << "Box 1" << bb.back ().to_string () << std::endl; + } + + std::vector bb2; + for (size_t i = 0; i < n; ++i) { + db::Coord x = rand () % spread; + db::Coord y = rand () % spread; + bb2.push_back (db::SimplePolygon (db::Box (x, y, x + 100, y + 100))); + // std::cout << "Polygon 2" << bb2.back ().to_string () << std::endl; + } + + db::box_scanner2 bs; + for (std::vector::const_iterator b = bb.begin (); b != bb.end (); ++b) { + bs.insert1 (&*b, b - bb.begin ()); + } + for (std::vector::const_iterator b2 = bb2.begin (); b2 != bb2.end (); ++b2) { + bs.insert2 (&*b2, int (b2 - bb2.begin ())); + } + + BoxScannerTestRecorder2Two tr; + bs.set_fill_factor (ff); + db::box_convert bc1; + db::box_convert bc2; + { + tl::SelfTimer timer ("box-scanner"); + bs.set_scanner_threshold (0); + bs.process (tr, touch ? 1 : 0, bc1, bc2); + } + + std::set > interactions; + { + tl::SelfTimer timer ("brute-force"); + for (size_t i = 0; i < bb.size (); ++i) { + for (size_t j = 0; j < bb2.size (); ++j) { + if ((touch && bb[i].touches (bb2[j].box ())) || (!touch && bb[i].overlaps (bb2[j].box ()))) { + interactions.insert (std::make_pair (i, int (j))); + } + } + } + } + + if (interactions != tr.interactions) { + tl::info << "Interactions 1-2 in 'brute force' but not in 'box-scanner':"; + for (std::set >::const_iterator i = interactions.begin (); i != interactions.end (); ++i) { + if (tr.interactions.find (*i) == tr.interactions.end ()) { + tl::info << " " << i->first << "-" << i->second; + } + } + tl::info << "Interactions 1-2 in 'box-scanner' but not in 'brute force':"; + for (std::set >::const_iterator i = tr.interactions.begin (); i != tr.interactions.end (); ++i) { + if (interactions.find (*i) == interactions.end ()) { + tl::info << " " << i->first << "-" << i->second; + } + } + } + EXPECT_EQ (interactions == tr.interactions, true); + +} + +TEST(two_2a) +{ + run_test2_two(_this, 10, 0.0, 1000); +} + +TEST(two_2b) +{ + run_test2_two(_this, 10, 0.0, 100); +} + +TEST(two_2c) +{ + run_test2_two(_this, 10, 0.0, 10); +} + +TEST(two_2d) +{ + run_test2_two(_this, 1000, 0.0, 1000); +} + +TEST(two_2e) +{ + run_test2_two(_this, 1000, 2, 1000); +} + +TEST(two_2f) +{ + run_test2_two(_this, 1000, 2, 1000, false); +} + +TEST(two_2g) +{ + run_test2_two(_this, 1000, 2, 500); +} + +TEST(two_2h) +{ + run_test2_two(_this, 1000, 2, 100); +} + +TEST(two_2i) +{ + run_test2_two(_this, 10000, 2, 10000); +}