// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <net.h>
#include <netaddress.h>
#include <test/util/net.h>
#include <test/util/setup_common.h>

#include <boost/test/unit_test.hpp>

#include <functional>
#include <numeric>
#include <optional>
#include <unordered_set>
#include <vector>

BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup)

// Create `num_peers` random nodes, apply setup function `candidate_setup_fn`,
// call ProtectEvictionCandidatesByRatio() to apply protection logic, and then
// return true if all of `protected_peer_ids` and none of `unprotected_peer_ids`
// are protected from eviction, i.e. removed from the eviction candidates.
bool IsProtected(
    int num_peers,
    std::function<void(NodeEvictionCandidate &)> candidate_setup_fn,
    const std::unordered_set<NodeId> &protected_peer_ids,
    const std::unordered_set<NodeId> &unprotected_peer_ids,
    FastRandomContext &random_context) {
    std::vector<NodeEvictionCandidate> candidates{
        GetRandomNodeEvictionCandidates(num_peers, random_context)};
    for (NodeEvictionCandidate &candidate : candidates) {
        candidate_setup_fn(candidate);
    }
    Shuffle(candidates.begin(), candidates.end(), random_context);

    const size_t size{candidates.size()};
    // Expect half the candidates will be protected.
    const size_t expected{size - size / 2};
    ProtectEvictionCandidatesByRatio(candidates);
    BOOST_CHECK_EQUAL(candidates.size(), expected);

    size_t unprotected_count{0};
    for (const NodeEvictionCandidate &candidate : candidates) {
        if (protected_peer_ids.count(candidate.id)) {
            // this peer should have been removed from the eviction candidates
            BOOST_TEST_MESSAGE(strprintf(
                "expected candidate to be protected: %d", candidate.id));
            return false;
        }
        if (unprotected_peer_ids.count(candidate.id)) {
            // this peer remains in the eviction candidates, as expected
            ++unprotected_count;
        }
    }

    const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
    if (!is_protected) {
        BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
                                     unprotected_peer_ids.size(),
                                     unprotected_count));
    }
    return is_protected;
}

BOOST_AUTO_TEST_CASE(peer_protection_test) {
    FastRandomContext random_context{true};
    int num_peers{12};

    // Expect half of the peers with greatest uptime (the lowest m_connected)
    // to be protected from eviction.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = false;
            c.m_network = NET_IPV4;
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 4, 5},
        /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11}, random_context));

    // Verify in the opposite direction.
    BOOST_CHECK(IsProtected(
        num_peers,
        [num_peers](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{num_peers - c.id};
            c.m_is_local = false;
            c.m_network = NET_IPV6;
        },
        /* protected_peer_ids */ {6, 7, 8, 9, 10, 11},
        /* unprotected_peer_ids */ {0, 1, 2, 3, 4, 5}, random_context));

    // Test protection of onion, localhost, and I2P peers...

    // Expect 1/4 onion peers to be protected from eviction,
    // if no localhost or I2P peers.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_is_local = false;
            c.m_network =
                (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4;
        },
        /* protected_peer_ids */ {3, 8, 9},
        /* unprotected_peer_ids */ {}, random_context));

    // Expect 1/4 onion peers and 1/4 of the other peers to be protected,
    // sorted by longest uptime (lowest m_connected), if no localhost or I2P
    // peers.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = false;
            c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6;
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 8, 9},
        /* unprotected_peer_ids */ {4, 5, 6, 7, 10, 11}, random_context));

    // Expect 1/4 localhost peers to be protected from eviction,
    // if no onion or I2P peers.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
            c.m_network = NET_IPV4;
        },
        /* protected_peer_ids */ {1, 9, 11},
        /* unprotected_peer_ids */ {}, random_context));

    // Expect 1/4 localhost peers and 1/4 of the other peers to be protected,
    // sorted by longest uptime (lowest m_connected), if no onion or I2P
    // peers.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id > 6);
            c.m_network = NET_IPV6;
        },
        /* protected_peer_ids */ {0, 1, 2, 7, 8, 9},
        /* unprotected_peer_ids */ {3, 4, 5, 6, 10, 11}, random_context));

    // Expect 1/4 I2P peers to be protected from eviction,
    // if no onion or localhost peers.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_is_local = false;
            c.m_network =
                (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4;
        },
        /* protected_peer_ids */ {2, 7, 10},
        /* unprotected_peer_ids */ {}, random_context));

    // Expect 1/4 I2P peers and 1/4 of the other peers to be protected,
    // sorted by longest uptime (lowest m_connected), if no onion or
    // localhost peers.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = false;
            c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6;
        },
        /* protected_peer_ids */ {0, 1, 2, 4, 9, 10},
        /* unprotected_peer_ids */ {3, 5, 6, 7, 8, 11}, random_context));

    // Tests with 2 networks...

    // Combined test: expect having 1 localhost and 1 onion peer out of 4 to
    // protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime;
    // stable sort breaks tie with array order of localhost first.
    BOOST_CHECK(IsProtected(
        4,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 4);
            c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4;
        },
        /* protected_peer_ids */ {0, 4},
        /* unprotected_peer_ids */ {1, 2}, random_context));

    // Combined test: expect having 1 localhost and 1 onion peer out of 7 to
    // protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by
    // uptime; stable sort breaks tie with array order of localhost first.
    BOOST_CHECK(IsProtected(
        7,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 6);
            c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
        },
        /* protected_peer_ids */ {0, 1, 6},
        /* unprotected_peer_ids */ {2, 3, 4, 5}, random_context));

    // Combined test: expect having 1 localhost and 1 onion peer out of 8 to
    // protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted
    // by uptime; stable sort breaks tie with array order of localhost first.
    BOOST_CHECK(IsProtected(
        8,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 6);
            c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
        },
        /* protected_peer_ids */ {0, 1, 5, 6},
        /* unprotected_peer_ids */ {2, 3, 4, 7}, random_context));

    // Combined test: expect having 3 localhost and 3 onion peers out of 12 to
    // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest
    // uptime; stable sort breaks ties with the array order of localhost first.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11);
            c.m_network =
                (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
        },
        /* protected_peer_ids */ {0, 1, 2, 6, 7, 9},
        /* unprotected_peer_ids */ {3, 4, 5, 8, 10, 11}, random_context));

    // Combined test: expect having 4 localhost and 1 onion peer out of 12 to
    // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest
    // uptime.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id > 4 && c.id < 9);
            c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
        },
        /* protected_peer_ids */ {0, 1, 2, 5, 6, 10},
        /* unprotected_peer_ids */ {3, 4, 7, 8, 9, 11}, random_context));

    // Combined test: expect having 4 localhost and 2 onion peers out of 16 to
    // protect 2 localhost and 2 onions, plus 4 other peers, sorted by longest
    // uptime.
    BOOST_CHECK(IsProtected(
        16,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12);
            c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 6, 8, 9, 10},
        /* unprotected_peer_ids */ {4, 5, 7, 11, 12, 13, 14, 15},
        random_context));

    // Combined test: expect having 5 localhost and 1 onion peer out of 16 to
    // protect 3 localhost (recovering the unused onion slot), 1 onion, and 4
    // others, sorted by longest uptime.
    BOOST_CHECK(IsProtected(
        16,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id > 10);
            c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 10, 11, 12, 13},
        /* unprotected_peer_ids */ {4, 5, 6, 7, 8, 9, 14, 15}, random_context));

    // Combined test: expect having 1 localhost and 4 onion peers out of 16 to
    // protect 1 localhost and 3 onions (recovering the unused localhost slot),
    // plus 4 others, sorted by longest uptime.
    BOOST_CHECK(IsProtected(
        16,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 15);
            c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6;
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15},
        /* unprotected_peer_ids */ {5, 6, 10, 11, 12, 13, 14}, random_context));

    // Combined test: expect having 2 onion and 4 I2P out of 12 peers to protect
    // 2 onion (prioritized for having fewer candidates) and 1 I2P, plus 3
    // others, sorted by longest uptime.
    BOOST_CHECK(IsProtected(
        num_peers,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = false;
            if (c.id == 8 || c.id == 10) {
                c.m_network = NET_ONION;
            } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) {
                c.m_network = NET_I2P;
            } else {
                c.m_network = NET_IPV4;
            }
        },
        /* protected_peer_ids */ {0, 1, 2, 6, 8, 10},
        /* unprotected_peer_ids */ {3, 4, 5, 7, 9, 11}, random_context));

    // Tests with 3 networks...

    // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4
    // to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted
    // by longest uptime; stable sort breaks tie with array order of I2P first.
    BOOST_CHECK(IsProtected(
        4,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 3);
            if (c.id == 4) {
                c.m_network = NET_I2P;
            } else if (c.id == 2) {
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV6;
            }
        },
        /* protected_peer_ids */ {0, 4},
        /* unprotected_peer_ids */ {1, 2}, random_context));

    // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7
    // to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted
    // by longest uptime; stable sort breaks tie with array order of I2P first.
    BOOST_CHECK(IsProtected(
        7,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 4);
            if (c.id == 6) {
                c.m_network = NET_I2P;
            } else if (c.id == 5) {
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV6;
            }
        },
        /* protected_peer_ids */ {0, 1, 6},
        /* unprotected_peer_ids */ {2, 3, 4, 5}, random_context));

    // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 8
    // to protect 1 I2P, 1 localhost, 0 onion and 2 other peers (4 total) sorted
    // by uptime; stable sort breaks tie with array order of I2P then localhost.
    BOOST_CHECK(IsProtected(
        8,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 6);
            if (c.id == 5) {
                c.m_network = NET_I2P;
            } else if (c.id == 4) {
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV6;
            }
        },
        /* protected_peer_ids */ {0, 1, 5, 6},
        /* unprotected_peer_ids */ {2, 3, 4, 7}, random_context));

    // Combined test: expect having 4 localhost, 2 I2P, and 2 onion peers out of
    // 16 to protect 1 localhost, 2 I2P, and 1 onion (4/16 total), plus 4 others
    // for 8 total, sorted by longest uptime.
    BOOST_CHECK(IsProtected(
        16,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 6 || c.id > 11);
            if (c.id == 7 || c.id == 11) {
                c.m_network = NET_I2P;
            } else if (c.id == 9 || c.id == 10) {
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV4;
            }
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 6, 7, 9, 11},
        /* unprotected_peer_ids */ {4, 5, 8, 10, 12, 13, 14, 15},
        random_context));

    // Combined test: expect having 1 localhost, 8 I2P and 1 onion peer out of
    // 24 to protect 1, 4, and 1 (6 total), plus 6 others for 12/24 total,
    // sorted by longest uptime.
    BOOST_CHECK(IsProtected(
        24,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 12);
            if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2
                c.m_network = NET_I2P;
            } else if (c.id == 23) {
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV6;
            }
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23},
        /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22},
        random_context));

    // Combined test: expect having 1 localhost, 3 I2P and 6 onion peers out of
    // 24 to protect 1, 3, and 2 (6 total, I2P has fewer candidates and so gets
    // the unused localhost slot), plus 6 others for 12/24 total, sorted by
    // longest uptime.
    BOOST_CHECK(IsProtected(
        24,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 15);
            if (c.id == 12 || c.id == 14 || c.id == 17) {
                c.m_network = NET_I2P;
            } else if (c.id > 17) { // 4 protected instead of usual 2
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV4;
            }
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19},
        /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23},
        random_context));

    // Combined test: expect having 1 localhost, 7 I2P and 4 onion peers out of
    // 24 to protect 1 localhost, 2 I2P, and 3 onions (6 total), plus 6 others
    // for 12/24 total, sorted by longest uptime.
    BOOST_CHECK(IsProtected(
        24,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id == 13);
            if (c.id > 16) {
                c.m_network = NET_I2P;
            } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV6;
            }
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18},
        /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23},
        random_context));

    // Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of
    // 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted
    // by longest uptime.
    BOOST_CHECK(IsProtected(
        24,
        [](NodeEvictionCandidate &c) {
            c.m_connected = std::chrono::seconds{c.id};
            c.m_is_local = (c.id > 15);
            if (c.id > 10 && c.id < 15) {
                c.m_network = NET_I2P;
            } else if (c.id > 6 && c.id < 10) {
                c.m_network = NET_ONION;
            } else {
                c.m_network = NET_IPV4;
            }
        },
        /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17},
        /* unprotected_peer_ids */
        {6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23}, random_context));
}

// Returns true if any of the node ids in node_ids are selected for eviction.
bool IsEvicted(std::vector<NodeEvictionCandidate> candidates,
               const std::unordered_set<NodeId> &node_ids,
               FastRandomContext &random_context) {
    Shuffle(candidates.begin(), candidates.end(), random_context);
    const std::optional<NodeId> evicted_node_id =
        SelectNodeToEvict(std::move(candidates));
    if (!evicted_node_id) {
        return false;
    }
    return node_ids.count(*evicted_node_id);
}

// Create number_of_nodes random nodes, apply setup function candidate_setup_fn,
// apply eviction logic and then return true if any of the node ids in node_ids
// are selected for eviction.
bool IsEvicted(const int number_of_nodes,
               std::function<void(NodeEvictionCandidate &)> candidate_setup_fn,
               const std::unordered_set<NodeId> &node_ids,
               FastRandomContext &random_context) {
    std::vector<NodeEvictionCandidate> candidates =
        GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
    for (NodeEvictionCandidate &candidate : candidates) {
        candidate_setup_fn(candidate);
    }
    return IsEvicted(candidates, node_ids, random_context);
}

BOOST_AUTO_TEST_CASE(node_eviction_test) {
    FastRandomContext random_context{true};

    for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) {
        // Four nodes with the highest keyed netgroup values should be
        // protected from eviction.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
            },
            {0, 1, 2, 3}, random_context));

        // Eight nodes with the lowest minimum ping time should be protected
        // from eviction.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [](NodeEvictionCandidate &candidate) {
                candidate.m_min_ping_time =
                    std::chrono::microseconds{candidate.id};
            },
            {0, 1, 2, 3, 4, 5, 6, 7}, random_context));

        // Four nodes that most recently sent us novel transactions accepted
        // into our mempool should be protected from eviction.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                candidate.m_last_tx_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
            },
            {0, 1, 2, 3}, random_context));

        // Four nodes that most recently sent us novel proofs accepted
        // into our proof pool should be protected from eviction.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                candidate.m_last_proof_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
            },
            {0, 1, 2, 3}, random_context));

        // Up to eight non-tx-relay peers that most recently sent us novel
        // blocks should be protected from eviction.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                candidate.m_last_block_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
                if (candidate.id <= 7) {
                    candidate.fRelayTxes = false;
                    candidate.fRelevantServices = true;
                }
            },
            {0, 1, 2, 3, 4, 5, 6, 7}, random_context));

        // Four peers that most recently sent us novel blocks should be
        // protected from eviction.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                candidate.m_last_block_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
            },
            {0, 1, 2, 3}, random_context));

        // Combination of the previous two tests.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                candidate.m_last_block_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
                if (candidate.id <= 7) {
                    candidate.fRelayTxes = false;
                    candidate.fRelevantServices = true;
                }
            },
            {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));

        // Combination of all tests above.
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                // 4 protected
                candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
                // 8 protected
                candidate.m_min_ping_time =
                    std::chrono::microseconds{candidate.id};
                // 4 protected
                candidate.m_last_tx_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
                // 4 protected
                candidate.m_last_proof_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
                // 4 protected
                candidate.m_last_block_time =
                    std::chrono::seconds{number_of_nodes - candidate.id};
            },
            {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11,
             12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
            random_context));

        // 128 peers with the highest availability score should be protected
        // from eviction.
        std::vector<NodeId> protectedNodes(128);
        std::iota(protectedNodes.begin(), protectedNodes.end(), 0);
        BOOST_CHECK(!IsEvicted(
            number_of_nodes,
            [number_of_nodes](NodeEvictionCandidate &candidate) {
                candidate.availabilityScore =
                    double(number_of_nodes - candidate.id);
            },
            std::unordered_set<NodeId>(protectedNodes.begin(),
                                       protectedNodes.end()),
            random_context));

        // An eviction is expected given >= 161 random eviction candidates.
        // The eviction logic protects at most four peers by net group,
        // eight by lowest ping time, four by last time of novel tx, four by
        // last time of novel proof, up to eight non-tx-relay peers by last
        // novel block time, four by last novel block time, and 128 more by
        // avalanche availability score.
        if (number_of_nodes >= 161) {
            BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(
                number_of_nodes, random_context)));
        }

        // No eviction is expected given <= 24 random eviction candidates.
        // The eviction logic protects at least four peers by net group,
        // eight by lowest ping time, four by last time of novel tx, four by
        // last time of novel proof, four peers by last novel block time.
        if (number_of_nodes <= 24) {
            BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(
                number_of_nodes, random_context)));
        }

        // Cases left to test:
        // * "If any remaining peers are preferred for eviction consider
        // only them. [...]"
        // * "Identify the network group with the most connections and
        // youngest member. [...]"
    }
}

BOOST_AUTO_TEST_SUITE_END()


$NetBSD: patch-lib_usrp_usrp2_usrp2__impl.cpp,v 1.1 2021/04/24 16:34:54 tnn Exp $

libuhd.so.3.15.0: undefined reference to `int boost::math::sign<double>(double const&)'

--- lib/usrp/usrp2/usrp2_impl.cpp.orig	2020-01-01 04:21:49.000000000 +0000
+++ lib/usrp/usrp2/usrp2_impl.cpp
@@ -835,6 +835,9 @@ void usrp2_impl::set_tx_fe_corrections(c
     }
 }

+#include <boost/math/special_functions/round.hpp>
+#include <boost/math/special_functions/sign.hpp>
+
 double usrp2_impl::set_tx_dsp_freq(
     const std::string &mb,
     const double freq_
@@ -878,8 +881,6 @@ meta_range_t usrp2_impl::get_tx_dsp_freq
     );
 }

-#include <boost/math/special_functions/round.hpp>
-#include <boost/math/special_functions/sign.hpp>

 void usrp2_impl::update_clock_source(const std::string &mb, const std::string &source){
     //NOTICE: U2_REG_MISC_CTRL_CLOCK is on the wb clock, and cannot be set from fifo_ctrl


// Project: libv.ui, File: src/libv/ui/component/panel_grid.cpp, Author: Császár Mátyás [Vader]

// hpp
#include <libv/ui/component/panel_grid.hpp>
// ext
#include <boost/container/small_vector.hpp>
#include <range/v3/view/chunk.hpp>
#include <range/v3/view/enumerate.hpp>
//#include <range/v3/view/zip.hpp>
// libv
#include <libv/algo/bisect.hpp>
#include <libv/algo/sum.hpp>
#include <libv/range/view_deinterleave.hpp>
#include <libv/utility/approx.hpp>
#include <libv/utility/observer_ref.hpp>
#include <libv/utility/to_underlying.hpp>
// pro
#include <libv/ui/core_component.hpp>
#include <libv/ui/component/base_panel.lpp>
#include <libv/ui/context/context_layout.hpp>
#include <libv/ui/context/context_style.hpp>
#include <libv/ui/layout/view_layouted.lpp>
#include <libv/ui/property_access_context.hpp>

namespace libv {
namespace ui {

// -------------------------------------------------------------------------------------------------

class CorePanelGrid : public CoreBasePanel {
	friend class PanelGrid;
	[[nodiscard]] inline auto handler() { return PanelGrid{this}; }

private:
	struct Properties {
		PropertyL<ColumnCount> column_count;
		PropertyL<Orientation2> orientation2;
	} property;

	struct ChildProperties {
	};

	template <typename T> static void access_properties(T& ctx);
	template <typename T> static void access_child_properties(T& ctx);

//	static ComponentPropertyDescription description;
//	static ComponentPropertyDescription child_description;

public:
	using CoreBasePanel::CoreBasePanel;

private:
	virtual void doStyle(ContextStyle& context) override;
	virtual void doStyle(ContextStyle& context, ChildID childID) override;
	virtual libv::vec3f doLayout1(const ContextLayout1& le) override;
	virtual void doLayout2(const ContextLayout2& le) override;
};

// -------------------------------------------------------------------------------------------------

namespace {

struct Orientation2Data {
	uint32_t _X_;
	uint32_t _Y_;
	uint32_t _Z_;
	libv::vec3f start;
	libv::vec3f direction;
	libv::vec3f penCorner;
};

static constexpr Orientation2Data Orientation2Table[] = {
	{1u, 0u, 2u, {1.f, 1.f, 0.f}, {-1.f, -1.f, +1.f}, {-1.f, -1.f, +0.f}}, // DOWN_LEFT
	{1u, 0u, 2u, {0.f, 1.f, 0.f}, {+1.f, -1.f, +1.f}, {+0.f, -1.f, +0.f}}, // DOWN_RIGHT
	{0u, 1u, 2u, {1.f, 1.f, 0.f}, {-1.f, -1.f, +1.f}, {-1.f, -1.f, +0.f}}, // LEFT_DOWN
	{0u, 1u, 2u, {1.f, 0.f, 0.f}, {-1.f, +1.f, +1.f}, {-1.f, +0.f, +0.f}}, // LEFT_UP
	{0u, 1u, 2u, {0.f, 1.f, 0.f}, {+1.f, -1.f, +1.f}, {+0.f, -1.f, +0.f}}, // RIGHT_DOWN
	{0u, 1u, 2u, {0.f, 0.f, 0.f}, {+1.f, +1.f, +1.f}, {+0.f, +0.f, +0.f}}, // RIGHT_UP
	{1u, 0u, 2u, {1.f, 0.f, 0.f}, {-1.f, +1.f, +1.f}, {-1.f, +0.f, +0.f}}, // UP_LEFT
	{1u, 0u, 2u, {0.f, 0.f, 0.f}, {+1.f, +1.f, +1.f}, {+0.f, +0.f, +0.f}}, // UP_RIGHT
};

template <typename Range>
inline auto buildLayoutedChildrenRandomAccessRange(Range& children, libv::vec3f env_bounds) {
	// Build a random access range from the layouted children

	struct Element {
		libv::observer_ref<Component> component;
		libv::vec3f dynamic;

		inline Component* operator->() noexcept {
			return &*component;
		}
		inline const Component* operator->() const noexcept {
			return &*component;
		}
	};

	boost::container::small_vector<Element, 32> result;

	for (auto& child : children | view_layouted()) {
		const auto child_dynamic = child.size().has_dynamic() ?
				AccessLayout::layout1(child.core(), ContextLayout1{env_bounds}) :
				libv::vec3f{};

		result.emplace_back(libv::make_observer_ref(child), child_dynamic);
	}

	return result;
}

} // namespace

// -------------------------------------------------------------------------------------------------

template <typename T>
void CorePanelGrid::access_properties(T& ctx) {
	ctx.property(
			[](auto& c) -> auto& { return c.column_count; },
			ColumnCount{2},
			pgr::layout, pnm::column_count,
			"Column count of the secondary dimension"
	);
	ctx.property(
			[](auto& c) -> auto& { return c.orientation2; },
			Orientation2::RIGHT_DOWN,
			pgr::layout, pnm::orientation2,
			"Two dimensional orientation of subsequent components"
	);
}

template <typename T>
void CorePanelGrid::access_child_properties(T& ctx) {
	(void) ctx;
}

// -------------------------------------------------------------------------------------------------

void CorePanelGrid::doStyle(ContextStyle& ctx) {
	PropertyAccessContext<Properties> setter{property, ctx.component, ctx.style, ctx.component.context()};
	access_properties(setter);
	CoreComponent::access_properties(setter);
}

void CorePanelGrid::doStyle(ContextStyle& ctx, ChildID childID) {
	(void) ctx;
	(void) childID;
}

// -------------------------------------------------------------------------------------------------

libv::vec3f CorePanelGrid::doLayout1(const ContextLayout1& layout_env) {
	const auto env_size = layout_env.size - padding_size3();

	const auto l_children = buildLayoutedChildrenRandomAccessRange(children, env_size);

	const auto column_count = property.column_count();
	const auto& orient = Orientation2Table[libv::to_value(property.orientation2())];
	const auto _X_ = orient._X_;
	const auto _Y_ = orient._Y_;
	const auto _Z_ = orient._Z_;

	auto result = libv::vec3f{};

	boost::container::small_vector<libv::vec3f, 32> child_dynamics(children.size(), libv::vec3f{});

	for (auto& child : l_children) {
		result[_Z_] = std::max(result[_Z_],
				child->size()[_Z_].pixel +
				(child->size()[_Z_].dynamic ? child.dynamic[_Z_] : 0.f));
	}

	const auto attemptX = [&](int32_t parentSize) {
		if (parentSize < 0)
			return -1;

		float parentSizeX = static_cast<float>(parentSize);
		float usedGridSizeX = 0.f;
		for (const auto& column : l_children | libv::view::deinterleave(column_count)) {
			float usedColumnSizeX = 0.f;
			for (const auto& child : column) {
				usedColumnSizeX = std::max(usedColumnSizeX,
						child->size()[_X_].pixel +
						child->size()[_X_].percent * parentSizeX * 0.01f +
						(child->size()[_X_].dynamic ? child.dynamic[_X_] : 0.f));
			}
			usedGridSizeX += usedColumnSizeX;
		}

		return parentSize - static_cast<int32_t>(usedGridSizeX + 0.5f);
	};

	const auto attemptY = [&](int32_t parentSize) {
		if (parentSize < 0)
			return -1;

		float parentSizeY = static_cast<float>(parentSize);
		float usedGridSizeY = 0.f;
		for (const auto& row : l_children | ranges::view::chunk(column_count)) {
			float usedRowSizeY = 0.f;
			for (const auto& child : row) {
				usedRowSizeY = std::max(usedRowSizeY,
						child->size()[_Y_].pixel +
						child->size()[_Y_].percent * parentSizeY * 0.01f +
						(child->size()[_Y_].dynamic ? child.dynamic[_Y_] : 0.f));
			}
			usedGridSizeY += usedRowSizeY;
		}

		return parentSize - static_cast<int32_t>(usedGridSizeY + 0.5f);
	};

	// TODO P4: implement a infinite cycle protection (occurs when the percent sum is greaten than 100% with a non zero fix size sum)
	result[_X_] = static_cast<float>(libv::bisect_rampup_3way(attemptX, 0, 0));
	result[_Y_] = static_cast<float>(libv::bisect_rampup_3way(attemptY, 0, 0));

	return result + padding_size3();
}

void CorePanelGrid::doLayout2(const ContextLayout2& layout_env) {
	const auto env_size = layout_env.size - padding_size3();

	const auto l_children = buildLayoutedChildrenRandomAccessRange(children, env_size);

	// TODO P4: generalize a way for table lookup for various table lookup and handle invalid enum values
	const auto column_count = property.column_count();
	const auto& orient = Orientation2Table[libv::to_value(property.orientation2())];
	const auto _X_ = orient._X_;
	const auto _Y_ = orient._Y_;
	const auto _Z_ = orient._Z_;

	// --- Size ----------------------------------------------------------------------------------------

	// Calculate size ratio contribution
	const auto calculateRatioContribution = [&](const auto _D_, const auto& firstDimensionView) {
		float ratioScale = 0.f;
		bool hasRatio = false;

		// Guarantee initial over-shoot by rewarding the whole parent size to the first found ratio
		for (const auto& child : l_children) {
			const auto ratio = child->size()[_D_].ratio;
			if (ratio != libv::Approx(0.f)) {
				ratioScale = env_size[_D_] / ratio;
				hasRatio = true;
				break;
			}
		}

		// Not a single ratio found, skip the calculation
		if (not hasRatio)
			return ratioScale;

		// Determine ratio contribution
		while (true) {
			float size = 0.f;
			float ratioCount = 0.f;

			for (const auto& firstDimension : l_children | firstDimensionView) {
				float firstSubDimSize = 0.f;
				float firstSubDimRatioCount = 0.f;

				for (const auto& child : firstDimension) {
					const auto childSize =
							child->size()[_D_].pixel +
							child->size()[_D_].percent * env_size[_D_] * 0.01f +
							child->size()[_D_].ratio * ratioScale +
							(child->size()[_D_].dynamic ? child.dynamic[_D_] : 0.f);

					if (childSize > firstSubDimSize) {
						firstSubDimSize = childSize;
						firstSubDimRatioCount = child->size()[_D_].ratio;
					}
				}

				size += firstSubDimSize;
				ratioCount += firstSubDimRatioCount;
			}

			float overshoot = size - env_size[_D_];
			ratioScale -= overshoot / ratioCount;

			if (ratioCount == libv::Approx(0.f))
				break;

			if (overshoot == libv::Approx(0.f))
				break;
		}

		return ratioScale;
	};

	const auto calculateRatioContribution3 = [&](const auto _D_) {
		float ratioScale = 0.f;
		bool hasRatio = false;

		// Guarantee initial over-shoot by rewarding the whole parent size to the first found ratio
		for (const auto& child : l_children) {
			const auto ratio = child->size()[_D_].ratio;
			if (ratio != libv::Approx(0.f)) {
				ratioScale = env_size[_D_] / ratio;
				hasRatio = true;
				break;
			}
		}

		// Not a single ratio found, skip the calculation
		if (not hasRatio)
			return 0.f;

		// Determine ratio contribution
		for (auto& child : l_children) {
			if (child->size()[_D_].ratio == libv::Approx(0.f))
				continue;

			const auto used =
					child->size()[_D_].pixel +
					child->size()[_D_].percent * env_size[_D_] * 0.01f +
					(child->size()[_D_].dynamic ? child.dynamic[_D_] : 0.f);
			const auto leftover = env_size[_D_] - used;
			const auto contribution = leftover / child->size()[_D_].ratio;

			ratioScale = std::min(ratioScale, contribution);
		}

		return ratioScale;
	};

	libv::vec3f ratioContribution;
	ratioContribution[_X_] = calculateRatioContribution(_X_, libv::view::deinterleave(column_count));
	ratioContribution[_Y_] = calculateRatioContribution(_Y_, ranges::view::chunk(column_count));
	ratioContribution[_Z_] = calculateRatioContribution3(_Z_);

	// Assign size

	boost::container::small_vector<libv::vec3f, 32> childSizes(l_children.size(), libv::vec3f{});
	boost::container::small_vector<float, 32> advanceX(column_count, 0.f);
	boost::container::small_vector<float, 32> advanceY(l_children.size() / column_count + 1, 0.f);

	for (const auto& [y, row] : l_children | ranges::view::chunk(column_count) | ranges::view::enumerate) {
		for (const auto& [x, child] : row | ranges::view::enumerate) {
			auto& childSize = childSizes[y * column_count + x];

			childSize = libv::build_vec<3>([&](const auto i) {
				return child->size()[i].pixel +
						child->size()[i].percent * env_size[i] * 0.01f +
						child->size()[i].ratio * ratioContribution[i] +
						(child->size()[i].dynamic ? child.dynamic[i] : 0.f);
			});

			advanceX[x] = std::max(advanceX[x], childSize[_X_]);
			advanceY[y] = std::max(advanceY[y], childSize[_Y_]);
		}
	}

	// --- Position ------------------------------------------------------------------------------------

	auto sizeContent = libv::vec3f{};
	sizeContent[_X_] = libv::sum(advanceX, 0);
	sizeContent[_Y_] = libv::sum(advanceY, 0);

	const auto originToContent = (env_size - sizeContent) * anchor().to_info();
	const auto contentToStart = sizeContent * orient.start;
	auto startToPen = vec3f{};

	for (const auto& [y, row] : l_children | ranges::view::chunk(column_count) | ranges::view::enumerate) {
		for (const auto& [x, child] : row | ranges::view::enumerate) {
			const auto& childSize = childSizes[y * column_count + x];

			auto cellSize = libv::vec3f{};
			cellSize[_X_] = advanceX[x];
			cellSize[_Y_] = advanceY[y];
			cellSize[_Z_] = env_size[_Z_];

			const auto penToCell = orient.penCorner * cellSize;
			const auto cellToPosition = (cellSize - childSize) * child->anchor().to_info();

			const auto position = padding_LB3() + originToContent + contentToStart + startToPen + penToCell + cellToPosition;
			const auto roundedPosition = libv::vec::round(position);
			const auto roundedSize = libv::vec::round(position + childSize) - roundedPosition;

			AccessLayout::layout2(
					child.component->core(),
					layout_env.enter(roundedPosition, roundedSize)
			);

			startToPen[_X_] += orient.direction[_X_] * advanceX[x];
		}
		startToPen[_X_] = 0;
		startToPen[_Y_] += orient.direction[_Y_] * advanceY[y];
	}
}

// =================================================================================================

PanelGrid::PanelGrid(std::string name) :
	ComponentHandler<CorePanelGrid, EventHostGeneral<PanelGrid>>(std::move(name)) { }

PanelGrid::PanelGrid(GenerateName_t gen, const std::string_view type) :
	ComponentHandler<CorePanelGrid, EventHostGeneral<PanelGrid>>(gen, type) { }

PanelGrid::PanelGrid(core_ptr core) noexcept :
	ComponentHandler<CorePanelGrid, EventHostGeneral<PanelGrid>>(core) { }

// -------------------------------------------------------------------------------------------------

void PanelGrid::column_count(ColumnCount value) {
	AccessProperty::manual(self(), self().property.column_count, value);
}

ColumnCount PanelGrid::column_count() const noexcept{
	return self().property.column_count();
}

void PanelGrid::orientation2(Orientation2 value) {
	AccessProperty::manual(self(), self().property.orientation2, value);
}

Orientation2 PanelGrid::orientation2() const noexcept{
	return self().property.orientation2();
}

// -------------------------------------------------------------------------------------------------

void PanelGrid::add(Component component) {
	self().add(std::move(component));
}

void PanelGrid::remove(Component& component) {
	self().remove(component);
}

void PanelGrid::clear() {
	self().clear();
}

// -------------------------------------------------------------------------------------------------

} // namespace ui
} // namespace libv


//          Copyright Gavin Band 2008 - 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef QCTOOL_CLUSTER_FIT_COMPUTATION_HPP
#define QCTOOL_CLUSTER_FIT_COMPUTATION_HPP

#include <string>
#include <memory>
#include <vector>
#include <map>
#include <boost/noncopyable.hpp>
#include <boost/function.hpp>
#include <Eigen/Core>
#include "genfile/VariantIdentifyingData.hpp"
#include "genfile/VariantEntry.hpp"
#include "genfile/VariantDataReader.hpp"
#include "components/SNPSummaryComponent/SNPSummaryComputation.hpp"

namespace stats {
	struct ClusterFitComputation: public SNPSummaryComputation {
		typedef std::auto_ptr< ClusterFitComputation > UniquePtr ;
		ClusterFitComputation(
			double nu,
			double regularisationVariance = 0.5,
			double regularisationWeight = 10,
			double call_threshhold = 0.9
		) ;
		void operator()( VariantIdentifyingData const&, Genotypes const&, Ploidy const&, genfile::VariantDataReader&, ResultCallback ) ;
		std::string get_summary( std::string const& prefix = "", std::size_t column_width = 20 ) const ;
		void set_scale( std::string const& scale ) ;
	private:
		double const m_call_threshhold ;
		double const m_nu ;
		std::string m_scale ;
		std::string m_xAxisName ;
		std::string m_yAxisName ;

		Eigen::MatrixXd const m_regularisingSigma ;
		double const m_regularisingWeight ;
		typedef Eigen::MatrixXd IntensityMatrix ;
		IntensityMatrix m_intensities ;
		IntensityMatrix m_nonmissingness ;
	} ;
}

#endif


#include <boost/graph/planar_face_traversal.hpp>


#ifndef QUADRATIC_B_SPLINE_2D_HPP
#define QUADRATIC_B_SPLINE_2D_HPP

#include <polyfem/QuadraticBSpline.hpp>

#include <array>
#include <cassert>

#include <Eigen/Dense>

namespace polyfem {
	class QuadraticBSpline2d {
	public:
		QuadraticBSpline2d()
		{ }

		QuadraticBSpline2d(const std::array<double, 4> &knots_u, const std::array<double, 4> &knots_v)
		: spline_u_(knots_u), spline_v_(knots_v)
		{ }

		void init(const std::array<double, 4> &knots_u, const std::array<double, 4> &knots_v);

		void interpolate(const Eigen::MatrixXd &ts, Eigen::MatrixXd &result) const;
		double interpolate(const double u, const double v) const;

		void derivative(const Eigen::MatrixXd &ts, Eigen::MatrixXd &result) const;
	private:
		QuadraticBSpline spline_u_;
		QuadraticBSpline spline_v_;
	};
}
#endif //QUADRATIC_B_SPLINE_2D_HPP


// General ROS functionality
#include <ros/ros.h>

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

// Simulator services
#include <sim/Insert.h>
#include <sim/Resume.h>
#include <sim/Pause.h>

//#include "pi/global.h"
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/foreach.hpp>
// Quadrotor services
#include <hal_ugv/State.h>
#include <hal_ugv/SetTruth.h>
#include <hal_ugv/GetTruth.h>
#include <hal_ugv/GetEstimate.h>

using namespace std;

vector<string> oidunits;
int ounits;
unsigned long int seed = 0;
gsl_rng *r;				// Random seed.
bool arg_seed = false;

// Main entry point of application
int main(int argc, char **argv)
{
	if (argc<2) {
	  ROS_FATAL("requires xml_file as argument");
	  return 1;
	}
	else if (argc==2) {
		ROS_INFO("using seed from file");
	}
	else {
		seed = atoi(argv[2]);
		arg_seed = true;
		ROS_INFO("Using seed = %s", argv[2]);
	}

	try {
		ifstream is(argv[1]);
		// read parameters from xml file
		using boost::property_tree::ptree;
		ptree pt;
		if (!is.is_open()) {
		  ROS_FATAL("xml config file required! %s", argv[1]);
		  return 1;
		}
		read_xml(is, pt);

		// task parameters (only units and idunits are required)
		ounits = pt.get<int>("ounits");
		BOOST_FOREACH(ptree::value_type &v,	pt.get_child("oidunits")) {
			oidunits.push_back(v.second.get<string>(""));
		}
		if (!arg_seed) {
			// and seed to start in random position
			seed =pt.get<int>("seed");
		}
	} catch (int e) {
		cout << "Exception" << e << endl;
	}

	// Initialise the ROS client
	string nodestr = string("example");
	ros::init(argc, argv, nodestr.c_str());

	// Create a node handle, which this C code will use to bind to the ROS server
	ros::NodeHandle n;

	// Create a client for interacting with the Simulator insert and Resume services
	ros::ServiceClient srvInsert = n.serviceClient<sim::Insert>("/simulator/Insert");
	ros::ServiceClient srvResume = n.serviceClient<sim::Resume>("/simulator/Resume");
	ros::ServiceClient srvPause  = n.serviceClient<sim::Pause> ("/simulator/Pause");

	// insert UVAs
	vector<ros::Subscriber> top_state(ounits);
	for (int i=0; i<ounits; i++) {

		// Create a resume message, which takes no arguments
		sim::Pause msgPause;

		// Call the client with this message
		if (!srvPause.call(msgPause)) {
		  ROS_FATAL("Failed to pause the simulator");
		  return 1;
		}

		// Create a message instructing a quadrotor UAV0 to be inserted in to the world
		sim::Insert msgInsert;
		msgInsert.request.model_name = oidunits[i];
		msgInsert.request.model_type = "model://obstacle_create";

		// Call the client with this message
		if (!srvInsert.call(msgInsert))
		{
		  ROS_FATAL("Failed to insert a hummingbird quadrotor into the simulator");
		  return 1;
		}

		// You should now see the quadrotor
		cout << oidunits[i] << endl;
		ROS_INFO("Successfully inserted the model into the world");

		// Create a resume message, which takes no arguments
		sim::Resume msgResume;

		// Call the client with this message
		if (!srvResume.call(msgResume)) {
			ROS_FATAL("Failed to resume the simulator");
			return 1;
		}
	}

	// init random seed
	gsl_rng_default_seed = seed;
	r = gsl_rng_alloc (gsl_rng_default);

	sleep(2);

	for (int i=0; i<ounits; i++) {

		// Create a resume message, which takes no arguments
		sim::Pause msgPause;

		// Call the client with this message
		if (!srvPause.call(msgPause)) {
		  ROS_FATAL("Failed to pause the simulator");
		  return 1;
		}

		hal_ugv::SetTruth msgSetTruth;
		string name = string("/hal/ugv/obstacle/") + string(oidunits[i]) + string("/SetTruth");
		ros::ServiceClient srvSetTruth = n.serviceClient<hal_ugv::SetTruth>(name.c_str());

		if (arg_seed) {
			// choose random initial state
			msgSetTruth.request.state.x = gsl_rng_uniform(r)*ounits - ounits/2;
			msgSetTruth.request.state.y = gsl_rng_uniform(r)*ounits - ounits/2;
		}
		else {
			// choose circular initial state
			double alpha = (double)i*2*3.1415/(double)ounits;
			msgSetTruth.request.state.x = 5*cos(alpha);
			msgSetTruth.request.state.y = 5*sin(alpha);
			msgSetTruth.request.state.yaw = alpha+3.14/2;
			ROS_INFO("%lf,%lf",msgSetTruth.request.state.x,msgSetTruth.request.state.y);

		}

	 	if (!srvSetTruth.call(msgSetTruth)) {
			ROS_FATAL("Failed SetTruth");
			return 1;
		}

		// Create a resume message, which takes no arguments
		sim::Resume msgResume;

		// Call the client with this message
		if (!srvResume.call(msgResume)) {
			ROS_FATAL("Failed to resume the simulator");
			return 1;
		}

	}

  gsl_rng_free(r);

  // Success!
  return 0;
}


#include "SCEngine.hpp"

#include <string>
#include <glog/logging.h>
#include <algorithm>
#include <fstream>
#include <cmath>
#include <mutex>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <petuum_ps_common/include/petuum_ps.hpp>
#include <io/general_fstream.hpp>

#include "util/Eigen/Dense"
#include "util/context.hpp"

namespace sparsecoding {

    // Constructor
    SCEngine::SCEngine(): thread_counter_(0) {
        // timer
        initT_ = boost::posix_time::microsec_clock::local_time();

        /* context */
        lda::Context & context = lda::Context::get_instance();
        // input and output
        data_file_ = context.get_string("data_file");
        input_data_format_ = context.get_string("input_data_format");
        is_partitioned_ = context.get_int32("is_partitioned");
        output_path_ = context.get_string("output_path");
        output_data_format_ = context.get_string("output_data_format");
        maximum_running_time_ = context.get_double("maximum_running_time");
        load_cache_ = context.get_int32("load_cache");
        cache_path_ = context.get_string("cache_path");

        // objective function parameters
        int m = context.get_int32("m");
        int n = context.get_int32("n");
        dictionary_size_ = context.get_int32("dictionary_size");
        C_ = context.get_double("c");
        lambda_ = context.get_double("lambda");

        // petuum parameters
        client_id_ = context.get_int32("client_id");
        num_clients_ = context.get_int32("num_clients");
        num_worker_threads_ = context.get_int32("num_worker_threads");

        // optimization parameters
        num_epochs_ = context.get_int32("num_epochs");
        minibatch_size_ = context.get_int32("minibatch_size");
        num_eval_minibatch_ = context.get_int32("num_eval_minibatch");
        num_eval_samples_ = context.get_int32("num_eval_samples");
        init_step_size_B_ = context.get_double("init_step_size_B");
        step_size_offset_B_ = context.get_double("step_size_offset_B");
        step_size_pow_B_ = context.get_double("step_size_pow_B");
        num_iter_S_per_minibatch_ =
            context.get_int32("num_iter_S_per_minibatch");
        init_step_size_S_ = context.get_double("init_step_size_S");
        step_size_offset_S_ = context.get_double("step_size_offset_S");
        step_size_pow_S_ = context.get_double("step_size_pow_S");
        // default step size
        if (init_step_size_B_ < FLT_MIN) {
            init_step_size_B_ = context.get_double("init_step_size")
                / num_clients_ / num_worker_threads_;
            step_size_offset_B_ = context.get_double("step_size_offset");
            step_size_pow_B_ = context.get_double("step_size_pow");
        }
        if (init_step_size_S_ < FLT_MIN) {
            init_step_size_S_ = context.get_double("init_step_size") / m;
            step_size_offset_S_ = context.get_double("step_size_offset");
            step_size_pow_S_ = context.get_double("step_size_pow");
        }
        init_S_low_ = context.get_double("init_S_low");
        init_S_high_ = context.get_double("init_S_high");
        init_B_low_ = context.get_double("init_B_low");
        init_B_high_ = context.get_double("init_B_high");

        /* Init matrices */
        // Partition by column id mod num_clients_
        int client_n = (n - (n / num_clients_) * num_clients_ > client_id_)?
            n / num_clients_ + 1: n / num_clients_;
        // Init matrix loader of data matrix X
        if (is_partitioned_) {
            X_matrix_loader_.Init(data_file_, input_data_format_, m, client_n);
        } else {
            X_matrix_loader_.Init(data_file_, input_data_format_, m, n,
                    client_id_, num_clients_);
        }

        // Init matrix loader of coefficients S
        if (dictionary_size_ == 0)
            dictionary_size_ = n;
        S_matrix_loader_.Init(dictionary_size_, client_n,
            init_S_low_, init_S_high_);

        int max_client_n = ceil(float(n) / num_clients_);
        int iter_minibatch =
            ceil(float(max_client_n) / num_worker_threads_ / minibatch_size_);
        num_eval_per_client_ =
            (num_epochs_ * iter_minibatch - 1)
              / num_eval_minibatch_ + 1;

        // Ouput parameters
        LOG(INFO) << "Sparse Coding running on "
            << num_clients_
            << ((num_clients_ > 1)? " clients, each with ": " client, with ")
            << num_worker_threads_ << " threads";
        LOG(INFO) << "Input file: " << data_file_;
        LOG(INFO) << "Matrix size: " << n << " by " << m;
        LOG(INFO) << "Dictionary size: " << dictionary_size_;
        LOG(INFO) << "C: " << C_ << ", lambda: " << lambda_;
        LOG(INFO) << "Minibatch size: " << minibatch_size_;
        LOG(INFO) << "Epochs: " << num_epochs_
            << " (Max iter: " << iter_minibatch  * num_epochs_ << ")";
        LOG(INFO) << "Evaluate loss per " << num_eval_minibatch_ << " iter "
            << "by evaluating " << num_eval_samples_ << " samples per thread"
            << " (*" << num_clients_ * num_worker_threads_<< " = "
            << num_eval_samples_ * num_clients_ * num_worker_threads_ << ")";
        LOG(INFO) << "step size of B at ith iteration: " << init_step_size_B_
            << " * (i + " << step_size_offset_B_ << ")^(-" << step_size_pow_B_
            << ")";
        LOG(INFO) << "step size of S at ith iteration: " << init_step_size_S_
            << " * (i + " << step_size_offset_S_ << ")^(-" << step_size_pow_S_
            << ")";
        LOG(INFO) << "Iter of S in each S loop: " << num_iter_S_per_minibatch_;
    }

    // Helper function, regularize a vector vec
    // such that its l2-norm is smaller than C
    inline void RegVec(std::vector<float> & vec, float C,
            std::vector<float> & vec_result) {
        float sum = 0.0;
        int len = vec.size();
        for (int i = 0; i < len; ++i) {
            sum += vec[i] * vec[i];
        }
        float ratio = (sum > C? sqrt(C / sum): 1.0);
        for (int i = 0; i < len; ++i) {
            vec_result[i] = vec[i] * ratio;
        }
    }

    // Save results: dicitonary B, coefficients S, loss evaluated on different
    // machines, time between evaluations to disk.
    // Shall be called after calling petuum::PSTableGroup::GlobalBarrier()
    void SCEngine::SaveResults(int thread_id, petuum::Table<float> & B_table,
            petuum::Table<float> & loss_table) {
        // size of matrices
        int m = X_matrix_loader_.GetM();
        int client_n = X_matrix_loader_.GetClientN();

        // Caches
        std::vector<float> B_row_cache(m), S_cache(dictionary_size_);

        // Output files
        //std::ofstream fout_loss, fout_B, fout_S, fout_time;

        // Only thread 0 of client 0 write dictionary B, loss and time to disk
        if (client_id_ == 0 && thread_id == 0) {
            // Write loss to disk
            std::string loss_filename = output_path_ + "/loss.txt";
	    petuum::io::ofstream fout_loss(loss_filename);

            //fout_loss.open(loss_filename.c_str());
            LOG(INFO) << "Writing loss result to directory: " << output_path_;
            petuum::RowAccessor row_acc;
            std::vector<float> petuum_row_cache(m);
            for (int iter = 0; iter < num_eval_per_client_; ++iter) {
                for (int client = 0; client < num_clients_; ++client) {
                    int row_id = client * num_eval_per_client_ + iter;
                    const auto & row = loss_table.
                        Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    if (std::abs(petuum_row_cache[0]) > FLT_MIN) {
                        fout_loss << petuum_row_cache[0] << "\t";
                    } else {
                        fout_loss << "N/A" << "\t";
                    }
                }
                fout_loss << "\n";
            }
            fout_loss.close();
            // Write time to disk
            std::string time_filename = output_path_ + "/time.txt";
	    petuum::io::ofstream fout_time(time_filename);

            //fout_time.open(time_filename.c_str());
            for (int iter = 0; iter < num_eval_per_client_; ++iter) {
                for (int client = 0; client < num_clients_; ++client) {
                    int row_id =
                        (client + num_clients_) * num_eval_per_client_ + iter;
                    const auto & row = loss_table.
                        Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    if (std::abs(petuum_row_cache[0]) > FLT_MIN) {
                        fout_time << petuum_row_cache[0] << "\t";
                    } else {
                        fout_time << "N/A" << "\t";
                    }
                }
                fout_time << "\n";
            }
            fout_time.close();
            // Write dictionary B to disk
            // with filename output_path_/B.[txt|bin]
            //if (output_data_format_ == "text") {
                std::string B_filename = output_path_ + "/B.txt";
                //fout_B.open(B_filename.c_str());
		petuum::io::ofstream fout_B(B_filename);

		for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
                  const auto & row = B_table.
                    Get<petuum::DenseRow<float> >(row_id, &row_acc);
                  row.CopyToVector(&petuum_row_cache);
                  // Regularize by C_
                  RegVec(petuum_row_cache, C_, B_row_cache);
                  for (int col_id = 0; col_id < m; ++col_id) {
                    if (std::abs(B_row_cache[col_id]) < INFINITESIMAL) {
                        B_row_cache[col_id] = 0.0;
                    }
                    fout_B << B_row_cache[col_id] << "\t";

                    fout_B << "\n";
                  }
                }
                fout_B.close();

        }
        // Thread 0 of each client save that client's part of S
        // to output_path_/S.[txt|bin].client_id_
        if (thread_id == 0) {

                std::string S_filename = output_path_ + "/S.txt."
                    + std::to_string(client_id_);
                //fout_S.open(S_filename.c_str());
		petuum::io::ofstream fout_S(S_filename);

            for (int col_id_client = 0; col_id_client < client_n;
                    ++col_id_client) {
                if (S_matrix_loader_.GetCol(col_id_client, S_cache)) {
                    for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
                        if (std::abs(S_cache[row_id]) < INFINITESIMAL) {
                            S_cache[row_id] = 0.0;
                        }
                        fout_S << S_cache[row_id] << "\t";

                    }
                                                    fout_S << "\n";

                }
            }
            fout_S.close();
        }
    }

    // Init B and S from cache file
    void SCEngine::LoadCache(int thread_id, petuum::Table<float> & B_table) {
        // size of matrices
        int m = X_matrix_loader_.GetM();
        int client_n = X_matrix_loader_.GetClientN();
        if (client_id_ == 0 && thread_id == 0) {
            // Load B

            std::vector<float> B_row_cache(m);

            std::string B_filename = cache_path_ + "/B.txt";
            //fout_B.open(B_filename.c_str());
	    petuum::io::ifstream fout_B(B_filename);

            CHECK(fout_B.good())
                << "Cache file " << B_filename << " does not exist!";
            for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
                petuum::UpdateBatch<float> B_update;
                for (int col_id = 0; col_id < m; ++col_id) {
                    if (input_data_format_ == "text") {
                        fout_B >> B_row_cache[col_id];
                    } else if (input_data_format_ == "binary") {
                        fout_B.read(reinterpret_cast<char*> (
                                &(B_row_cache[col_id])), 4);
                    }
                    B_update.Update(col_id, B_row_cache[col_id]);
                }
                B_table.BatchInc(row_id, B_update);
            }
            fout_B.close();
        }
        // Load S
        if (thread_id == 0) {

            std::vector<float> S_cache(dictionary_size_),
                S_inc_cache(dictionary_size_);

            std::string S_filename;
                S_filename = cache_path_ + "/S.txt." +
                    std::to_string(client_id_);
                   //fout_S.open(S_filename.c_str());
	    petuum::io::ifstream fout_S(S_filename);

            CHECK(fout_S.good())
                << "Cache file " << S_filename << " does not exist!";
               for (int col_id_client = 0; col_id_client < client_n;
                    ++col_id_client) {
                   if (S_matrix_loader_.GetCol(col_id_client, S_cache)) {
                       for (int row_id = 0; row_id < dictionary_size_;
                            ++row_id) {
                            fout_S >> S_inc_cache[row_id];
                                                S_inc_cache[row_id] =
                            S_inc_cache[row_id] - S_cache[row_id];
                       }
                    S_matrix_loader_.IncCol(col_id_client, S_inc_cache);
                   }
                S_matrix_loader_.GetCol(col_id_client, S_cache);
               }
               fout_S.close();
        }
    }

    // Init B table
    void SCEngine::InitRand(int thread_id, petuum::Table<float> & B_table) {
        if (thread_id != 0 || client_id_ != 0)
            return;
        // size of matrices
        int m = X_matrix_loader_.GetM();
        srand((unsigned)time(NULL));
        std::vector<float> B_row_cache(m);
        // petuum row accessor
        petuum::RowAccessor row_acc;
        for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
            petuum::UpdateBatch<float> B_update;
            for (int col_id = 0; col_id < m; ++col_id) {
                B_row_cache[col_id] = double(rand()) / RAND_MAX *
                    (init_B_high_ - init_B_low_) + init_B_low_;
            }
            for (int col_id = 0; col_id < m; ++col_id) {
                B_update.Update(col_id, B_row_cache[col_id]);
            }
            B_table.BatchInc(row_id, B_update);
        }
    }

    // Stochastic Gradient Descent Optimization
    void SCEngine::Start() {
        // thread id on a client
        int thread_id = thread_counter_++;
        petuum::PSTableGroup::RegisterThread();
        LOG(INFO) << "client " << client_id_ << ", thread "
            << thread_id << " registers!";

        // Get dictionary table and loss table
        petuum::Table<float> B_table =
            petuum::PSTableGroup::GetTableOrDie<float>(0);
        petuum::Table<float> loss_table =
            petuum::PSTableGroup::GetTableOrDie<float>(1);

        // size of matrices
        int m = X_matrix_loader_.GetM();
        int client_n = X_matrix_loader_.GetClientN();

        // Cache dictionary table
        Eigen::MatrixXf petuum_table_cache(m, dictionary_size_);
        // Accumulate update of dictionary table in minibatch
        Eigen::MatrixXf petuum_update_cache(m, dictionary_size_);
        // Cache a column of coefficients S
        Eigen::VectorXf Sj(dictionary_size_);
        // Cache a column of update of S_j
        Eigen::VectorXf Sj_inc(dictionary_size_);
        // Cache a column of data X
        Eigen::VectorXf Xj(m);
        Eigen::VectorXf Xj_inc(m);
        // Cache a row of dictionary table
        std::vector<float> petuum_row_cache(m);

        // Register rows
        if (thread_id == 0) {
            for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
                B_table.GetAsyncForced(row_id);
            }
            int loss_table_size = num_eval_per_client_ * num_clients_ * 2;
            for (int row_id = 0; row_id < loss_table_size; ++row_id) {
                loss_table.GetAsyncForced(row_id);
            }
        }
        petuum::PSTableGroup::GlobalBarrier();

        // initialize B
        STATS_APP_INIT_BEGIN();
        if (client_id_ == 0 && thread_id == 0) {
            LOG(INFO) << "starting to initialize B";
        }
        if (load_cache_) {// load B and S from cache
            LoadCache(thread_id, B_table);
        } else { // randomly init B
            InitRand(thread_id, B_table);
        }
        if (thread_id == 0 && client_id_ == 0) {
            LOG(INFO) << "matrix B initialization finished!";
        }
        petuum::PSTableGroup::GlobalBarrier();
        STATS_APP_INIT_END();

        // Optimization Loop
        // Timer
        boost::posix_time::ptime beginT =
            boost::posix_time::microsec_clock::local_time();
        // Step size for optimization
        float step_size_B = init_step_size_B_, step_size_S = init_step_size_S_;

        int num_minibatch = 0;
        for (int iter = 0; iter < num_epochs_; ++iter) {
            // how many minibatches per epoch
            int minibatch_per_epoch = (client_n / num_worker_threads_ > 0)?
                client_n / num_worker_threads_: 1;
            for (int iter_per_epoch = 0; iter_per_epoch * minibatch_size_
                    < minibatch_per_epoch; ++iter_per_epoch) {
                boost::posix_time::time_duration runTime =
                    boost::posix_time::microsec_clock::local_time() - initT_;
                // Terminate and save states to disk if running time exceeds
                // limit
                if (maximum_running_time_ > 0.0 &&
                        (float) runTime.total_milliseconds() >
                        maximum_running_time_*3600*1000) {
                    LOG(INFO) << "Maximum runtime limit activates, "
                        "terminating now!";
                    petuum::PSTableGroup::GlobalBarrier();
                    SaveResults(thread_id, B_table, loss_table);
                    petuum::PSTableGroup::DeregisterThread();
                    return;
                }
                // Update petuum table cache
                petuum::RowAccessor row_acc;
                for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
                    const auto & row =
                        B_table.Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    for (int col_id = 0; col_id < m; ++col_id) {
                        petuum_table_cache(col_id, row_id) =
                            petuum_row_cache[col_id];
                    }
                }
                // evaluate obj
                if (num_minibatch % num_eval_minibatch_ == 0) {
                    boost::posix_time::time_duration elapTime =
                        boost::posix_time::microsec_clock::local_time() - beginT;
                    // evaluate partial obj
                    double obj = 0.0;
                    double obj1 = 0.0;
                    double obj2 = 0.0;
                    int num_samples = num_eval_samples_;
                    for (int i = 0; i < dictionary_size_; ++i) {
                        float regularizer = petuum_table_cache.col(i).norm();
                        regularizer =
                            (regularizer > sqrt(C_))? sqrt(C_) / regularizer: 1.0;
                        petuum_table_cache.col(i) *= regularizer;
                    }
                    for (int i = 0; i < num_samples; ++i) {
                        int col_id_client = 0;
                        if (S_matrix_loader_.GetRandCol(col_id_client, Sj)
                                && X_matrix_loader_.GetCol(col_id_client, Xj)) {
                            Xj_inc = Xj - petuum_table_cache * Sj;
                            obj1 += Xj_inc.squaredNorm() / num_samples;
                            obj2 += lambda_ * Sj.lpNorm<1>() / num_samples;
                        }
                    }
                    obj = obj1 + obj2;
                    LOG(INFO) << "iter: " << num_minibatch << ", client "
                        << client_id_ << ", thread " << thread_id <<
                        " mean loss: " << obj
                        << ", mean reconstruction error: " << obj1
                        << ", mean regularization: " << obj2;
                    // update loss table
                    loss_table.Inc(client_id_ * num_eval_per_client_ +
                            num_minibatch / num_eval_minibatch_,  0,
                            obj/num_worker_threads_);
                    loss_table.Inc((num_clients_+client_id_) * num_eval_per_client_
                            + num_minibatch / num_eval_minibatch_, 0,
                            ((float) elapTime.total_milliseconds()) / 1000
                            / num_worker_threads_);
                    beginT = boost::posix_time::microsec_clock::local_time();
                }
                step_size_B = init_step_size_B_ *
                    pow(step_size_offset_B_ + num_minibatch,
                            -1.0*step_size_pow_B_);
                step_size_S = init_step_size_S_ *
                    pow(step_size_offset_S_ + num_minibatch,
                            -1.0*step_size_pow_S_);
                num_minibatch++;
                // clear update table
                petuum_update_cache.fill(0.0);
                // minibatch
                for (int k = 0; k < minibatch_size_; ++k) {
                    int col_id_client = 0;
                    if (S_matrix_loader_.GetRandCol(col_id_client, Sj)
                            && X_matrix_loader_.GetCol(col_id_client, Xj)) {
                        // update S_j
                        for (int iter_S = 0;
                                iter_S < num_iter_S_per_minibatch_; ++iter_S) {
                            // compute gradient of Sj
                            Sj_inc = step_size_S * 2.0 *
                                ( petuum_table_cache.transpose()
                                 * (Xj - petuum_table_cache * Sj) );
                            Sj.noalias() += Sj_inc;
                            // proximal gradient descent to deal with l1 subgradient
                            Sj = ((Sj.array() > step_size_S*lambda_).cast<float>()
                                * (Sj.array() - step_size_S*lambda_)
                                + (Sj.array() < -1.0*step_size_S*lambda_).cast<float>()
                                * (Sj.array() + step_size_S*lambda_)).matrix();
                            S_matrix_loader_.SetCol(col_id_client, Sj);

                            // get updated S_j
                            S_matrix_loader_.GetCol(col_id_client, Sj);
                        }
                        // update B
                        Xj_inc = Xj - petuum_table_cache * Sj;
                        petuum_update_cache.noalias() +=
                            step_size_B * 2.0 * Xj_inc * Sj.transpose();
                    }
                }
                // Update B_table
                for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
                    petuum::UpdateBatch<float> B_update;
                    for (int col_id = 0; col_id < m; ++col_id) {
                        B_update.Update(col_id,
                                petuum_update_cache(col_id, row_id)
                                / minibatch_size_);
                    }
                    B_table.BatchInc(row_id, B_update);
                }
                petuum::PSTableGroup::Clock();
                // Update B_table to normalize l2-norm to C_
                std::vector<float> B_row_cache(m);
                for (int row_id = 0; row_id < dictionary_size_; ++row_id) {
                    const auto & row =
                        B_table.Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    RegVec(petuum_row_cache, C_, B_row_cache);
                    petuum::UpdateBatch<float> B_update;
                    for (int col_id = 0; col_id < m; ++col_id) {
                        B_update.Update(col_id,
                                (-1.0 * petuum_row_cache[col_id] +
                                B_row_cache[col_id]) / num_clients_ /
                                num_worker_threads_);
                    }
                    B_table.BatchInc(row_id, B_update);
                }
                petuum::PSTableGroup::Clock();
            }
        }
        // Save results to disk
        petuum::PSTableGroup::GlobalBarrier();
        SaveResults(thread_id, B_table, loss_table);
        petuum::PSTableGroup::DeregisterThread();
    }

    SCEngine::~SCEngine() {
    }
} // namespace sparsecoding