    // Copyright (C) 2007  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_GRAPH_UTILs_
    #define DLIB_GRAPH_UTILs_

    #include "../algs.h"
    #include <vector>
    #include "graph_utils_abstract.h"
    #include "../is_kind.h"
    #include "../enable_if.h"
    #include <algorithm>
    #include "../set.h"
    #include "../memory_manager.h"
    #include "../set_utils.h"

    namespace dlib
    {

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

        template <typename T>
        typename enable_if<is_graph<T>,typename T::edge_type>::type& edge(
            T& g,
            unsigned long idx_i,
            unsigned long idx_j
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(g.has_edge(idx_i,idx_j) == true,
                "\tT::edge_type& edge(g, idx_i, idx_j)"
                << "\n\t you have requested an invalid edge"
                << "\n\t idx_i: " << idx_i
                << "\n\t idx_j: " << idx_j
                );

            for (unsigned long i = 0; i < g.node(idx_i).number_of_neighbors(); ++i)
            {
                if (g.node(idx_i).neighbor(i).index() == idx_j)
                    return g.node(idx_i).edge(i);
            }

            // put this here just so compilers don't complain about a lack of
            // a return here
            DLIB_CASSERT(false,
                "\tT::edge_type& edge(g, idx_i, idx_j)"
                << "\n\t you have requested an invalid edge"
                << "\n\t idx_i: " << idx_i
                << "\n\t idx_j: " << idx_j
                );
        }

        template <typename T>
        const typename enable_if<is_graph<T>,typename T::edge_type>::type& edge(
            const T& g,
            unsigned long idx_i,
            unsigned long idx_j
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(g.has_edge(idx_i,idx_j) == true,
                "\tT::edge_type& edge(g, idx_i, idx_j)"
                << "\n\t you have requested an invalid edge"
                << "\n\t idx_i: " << idx_i
                << "\n\t idx_j: " << idx_j
                );

            for (unsigned long i = 0; i < g.node(idx_i).number_of_neighbors(); ++i)
            {
                if (g.node(idx_i).neighbor(i).index() == idx_j)
                    return g.node(idx_i).edge(i);
            }

            // put this here just so compilers don't complain about a lack of
            // a return here
            DLIB_CASSERT(false,
                "\tT::edge_type& edge(g, idx_i, idx_j)"
                << "\n\t you have requested an invalid edge"
                << "\n\t idx_i: " << idx_i
                << "\n\t idx_j: " << idx_j
                );
        }

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

        template <typename T>
        typename enable_if<is_directed_graph<T>,typename T::edge_type>::type& edge(
            T& g,
            unsigned long parent_idx,
            unsigned long child_idx
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(g.has_edge(parent_idx,child_idx) == true,
                "\t T::edge_type& edge(g, parent_idx, child_idx)"
                << "\n\t you have requested an invalid edge"
                << "\n\t parent_idx: " << parent_idx
                << "\n\t child_idx: " << child_idx
                );

            for (unsigned long i = 0; i < g.node(parent_idx).number_of_children(); ++i)
            {
                if (g.node(parent_idx).child(i).index() == child_idx)
                    return g.node(parent_idx).child_edge(i);
            }

            // put this here just so compilers don't complain about a lack of
            // a return here
            DLIB_CASSERT(false,
                "\t T::edge_type& edge(g, parent_idx, child_idx)"
                << "\n\t you have requested an invalid edge"
                << "\n\t parent_idx: " << parent_idx
                << "\n\t child_idx: " << child_idx
                );
        }

        template <typename T>
        const typename enable_if<is_directed_graph<T>,typename T::edge_type>::type& edge(
            const T& g,
            unsigned long parent_idx,
            unsigned long child_idx
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(g.has_edge(parent_idx,child_idx) == true,
                "\t T::edge_type& edge(g, parent_idx, child_idx)"
                << "\n\t you have requested an invalid edge"
                << "\n\t parent_idx: " << parent_idx
                << "\n\t child_idx: " << child_idx
                );

            for (unsigned long i = 0; i < g.node(parent_idx).number_of_children(); ++i)
            {
                if (g.node(parent_idx).child(i).index() == child_idx)
                    return g.node(parent_idx).child_edge(i);
            }

            // put this here just so compilers don't complain about a lack of
            // a return here
            DLIB_ASSERT(false,
                "\t T::edge_type& edge(g, parent_idx, child_idx)"
                << "\n\t you have requested an invalid edge"
                << "\n\t parent_idx: " << parent_idx
                << "\n\t child_idx: " << child_idx
                );
        }

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

        namespace graph_helpers
        {
            template <typename T, typename U>
            inline bool is_same_object (
                const T& a,
                const U& b
            )
            {
                if (is_same_type<const T,const U>::value == false)
                    return false;
                if ((void*)&a == (void*)&b)
                    return true;
                else
                    return false;
            }

            template <
                typename T
                >
            bool search_for_directed_cycles (
                const T& node,
                std::vector<bool>& visited,
                std::vector<bool>& temp
            )
            /*!
                requires
                    - visited.size() >= number of nodes in the graph that contains the given node
                    - temp.size() >= number of nodes in the graph that contains the given node
                    - for all i in temp:
                        - temp[i] == false
                ensures
                    - checks the connected subgraph containing the given node for directed cycles
                      and returns true if any are found and false otherwise.
                    - for all nodes N in the connected subgraph containing the given node:
                        - #visited[N.index()] == true
                    - for all i in temp:
                        - #temp[i] == false
            !*/
            {
                if (temp[node.index()] == true)
                    return true;

                visited[node.index()] = true;
                temp[node.index()] = true;

                for (unsigned long i = 0; i < node.number_of_children(); ++i)
                {
                    if (search_for_directed_cycles(node.child(i), visited, temp))
                        return true;
                }

                temp[node.index()] = false;

                return false;
            }

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

            template <
                typename T
                >
            typename enable_if<is_directed_graph<typename T::graph_type>,bool>::type search_for_undirected_cycles (
                const T& node,
                std::vector<bool>& visited,
                unsigned long prev = std::numeric_limits<unsigned long>::max()
            )
            /*!
                requires
                    - visited.size() >= number of nodes in the graph that contains the given node
                    - for all nodes N in the connected subgraph containing the given node:
                        - visited[N.index] == false
                ensures
                    - checks the connected subgraph containing the given node for directed cycles
                      and returns true if any are found and false otherwise.
                    - for all nodes N in the connected subgraph containing the given node:
                        - #visited[N.index()] == true
            !*/
            {
                using namespace std;
                if (visited[node.index()] == true)
                    return true;

                visited[node.index()] = true;

                for (unsigned long i = 0; i < node.number_of_children(); ++i)
                {
                    if (node.child(i).index() != prev &&
                        search_for_undirected_cycles(node.child(i), visited, node.index()))
                        return true;
                }

                for (unsigned long i = 0; i < node.number_of_parents(); ++i)
                {
                    if (node.parent(i).index() != prev &&
                        search_for_undirected_cycles(node.parent(i), visited, node.index()))
                        return true;
                }

                return false;
            }

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

            template <
                typename T
                >
            typename enable_if<is_graph<typename T::graph_type>,bool>::type search_for_undirected_cycles (
                const T& node,
                std::vector<bool>& visited,
                unsigned long prev = std::numeric_limits<unsigned long>::max()
            )
            /*!
                requires
                    - visited.size() >= number of nodes in the graph that contains the given node
                    - for all nodes N in the connected subgraph containing the given node:
                        - visited[N.index] == false
                ensures
                    - checks the connected subgraph containing the given node for directed cycles
                      and returns true if any are found and false otherwise.
                    - for all nodes N in the connected subgraph containing the given node:
                        - #visited[N.index()] == true
            !*/
            {
                using namespace std;
                if (visited[node.index()] == true)
                    return true;

                visited[node.index()] = true;

                for (unsigned long i = 0; i < node.number_of_neighbors(); ++i)
                {
                    if (node.neighbor(i).index() != prev &&
                        search_for_undirected_cycles(node.neighbor(i), visited, node.index()))
                        return true;
                }

                return false;
            }

        }

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

        template <
            typename graph_type1,
            typename graph_type2
            >
        typename enable_if<is_graph<graph_type1> >::type copy_graph_structure (
            const graph_type1& src,
            graph_type2& dest
        )
        {
            COMPILE_TIME_ASSERT(is_graph<graph_type1>::value);
            COMPILE_TIME_ASSERT(is_graph<graph_type2>::value);
            if (graph_helpers::is_same_object(src,dest))
                return;

            dest.clear();
            dest.set_number_of_nodes(src.number_of_nodes());

            // copy all the edges from src into dest
            for (unsigned long i = 0; i < src.number_of_nodes(); ++i)
            {
                for (unsigned long j = 0; j < src.node(i).number_of_neighbors(); ++j)
                {
                    const unsigned long nidx = src.node(i).neighbor(j).index();
                    if (nidx >= i)
                    {
                        dest.add_edge(i,nidx);
                    }
                }
            }
        }

        template <
            typename graph_type1,
            typename graph_type2
            >
        typename enable_if<is_directed_graph<graph_type1> >::type copy_graph_structure (
            const graph_type1& src,
            graph_type2& dest
        )
        {
            COMPILE_TIME_ASSERT(is_directed_graph<graph_type1>::value);
            COMPILE_TIME_ASSERT(is_directed_graph<graph_type2>::value || is_graph<graph_type2>::value );
            if (graph_helpers::is_same_object(src,dest))
                return;

            dest.clear();
            dest.set_number_of_nodes(src.number_of_nodes());

            // copy all the edges from src into dest
            for (unsigned long i = 0; i < src.number_of_nodes(); ++i)
            {
                for (unsigned long j = 0; j < src.node(i).number_of_children(); ++j)
                {
                    const unsigned long nidx = src.node(i).child(j).index();
                    if (dest.has_edge(i,nidx) == false)
                    {
                        dest.add_edge(i,nidx);
                    }
                }
            }
        }

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

        template <
            typename graph_type1,
            typename graph_type2
            >
        typename enable_if<is_graph<graph_type1> >::type copy_graph (
            const graph_type1& src,
            graph_type2& dest
        )
        {
            COMPILE_TIME_ASSERT(is_graph<graph_type1>::value);
            COMPILE_TIME_ASSERT(is_graph<graph_type2>::value);
            if (graph_helpers::is_same_object(src,dest))
                return;

            copy_graph_structure(src,dest);

            // copy all the node and edge content
            for (unsigned long i = 0; i < src.number_of_nodes(); ++i)
            {
                dest.node(i).data = src.node(i).data;

                for (unsigned long j = 0; j < src.node(i).number_of_neighbors(); ++j)
                {
                    const unsigned long nidx = src.node(i).neighbor(j).index();
                    if (nidx >= i)
                    {
                        dest.node(i).edge(j) = src.node(i).edge(j);
                    }
                }
            }
        }

        template <
            typename graph_type1,
            typename graph_type2
            >
        typename enable_if<is_directed_graph<graph_type1> >::type copy_graph (
            const graph_type1& src,
            graph_type2& dest
        )
        {
            COMPILE_TIME_ASSERT(is_directed_graph<graph_type1>::value);
            COMPILE_TIME_ASSERT(is_directed_graph<graph_type2>::value);
            if (graph_helpers::is_same_object(src,dest))
                return;

            copy_graph_structure(src,dest);

            // copy all the node and edge content
            for (unsigned long i = 0; i < src.number_of_nodes(); ++i)
            {
                dest.node(i).data = src.node(i).data;
                for (unsigned long j = 0; j < src.node(i).number_of_children(); ++j)
                {
                    dest.node(i).child_edge(j) = src.node(i).child_edge(j);
                }
            }
        }

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

        template <
            typename T,
            typename S
            >
        typename enable_if<is_graph<typename T::graph_type> >::type find_connected_nodes (
        const T& n,
        S& visited
        )
        {
            if (visited.is_member(n.index()) == false)
            {
                unsigned long temp = n.index();
                visited.add(temp);

                for (unsigned long i = 0; i < n.number_of_neighbors(); ++i)
                    find_connected_nodes(n.neighbor(i), visited);
            }
        }

        template <
            typename T,
            typename S
            >
        typename enable_if<is_directed_graph<typename T::graph_type> >::type find_connected_nodes (
        const T& n,
        S& visited
        )
        {
            if (visited.is_member(n.index()) == false)
            {
                unsigned long temp = n.index();
                visited.add(temp);

                for (unsigned long i = 0; i < n.number_of_parents(); ++i)
                    find_connected_nodes(n.parent(i), visited);
                for (unsigned long i = 0; i < n.number_of_children(); ++i)
                    find_connected_nodes(n.child(i), visited);
            }
        }

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

        template <
            typename T
            >
        bool graph_is_connected (
            const T& g
        )
        {
            if (g.number_of_nodes() == 0)
                return true;

            set<unsigned long>::kernel_1b_c visited;
            find_connected_nodes(g.node(0), visited);
            return (visited.size() == g.number_of_nodes());
        }

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

        template <
            typename T
            >
        bool graph_has_symmetric_edges (
            const T& graph
        )
        {
            for (unsigned long i = 0; i < graph.number_of_nodes(); ++i)
            {
                for (unsigned long j = 0; j < graph.node(i).number_of_children(); ++j)
                {
                    const unsigned long jj = graph.node(i).child(j).index();
                    // make sure every edge from a parent to a child has an edge linking back
                    if (graph.has_edge(jj,i) == false)
                        return false;
                }

                for (unsigned long j = 0; j < graph.node(i).number_of_parents(); ++j)
                {
                    const unsigned long jj = graph.node(i).parent(j).index();
                    // make sure every edge from a child to a parent has an edge linking back
                    if (graph.has_edge(i,jj) == false)
                        return false;
                }
            }

            return true;
        }

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

        template <
            typename T
            >
        bool graph_contains_directed_cycle (
            const T& graph
        )
        {
            using namespace std;
            using namespace graph_helpers;
            std::vector<bool> visited(graph.number_of_nodes(), false);
            std::vector<bool> temp(graph.number_of_nodes(), false);

            while (true)
            {
                // find the first node that hasn't been visited yet
                unsigned long i;
                for (i = 0; i < visited.size(); ++i)
                {
                    if (visited[i] == false)
                        break;
                }

                // if we didn't find any non-visited nodes then we are done
                if (i == visited.size())
                    return false;

                if (search_for_directed_cycles(graph.node(i), visited, temp))
                    return true;
            }
        }

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

        template <
            typename T
            >
        bool graph_contains_undirected_cycle (
            const T& graph
        )
        {
            using namespace std;
            using namespace graph_helpers;
            std::vector<bool> visited(graph.number_of_nodes(), false);

            while (true)
            {
                // find the first node that hasn't been visited yet
                unsigned long i;
                for (i = 0; i < visited.size(); ++i)
                {
                    if (visited[i] == false)
                        break;
                }

                // if we didn't find any non-visited nodes then we are done
                if (i == visited.size())
                    return false;

                if (search_for_undirected_cycles(graph.node(i), visited))
                    return true;
            }
        }

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

        template <
            typename directed_graph_type,
            typename graph_type
            >
        void create_moral_graph (
            const directed_graph_type& g,
            graph_type& moral_graph
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(graph_contains_directed_cycle(g) == false,
                "\tvoid create_moral_graph(g, moral_graph)"
                << "\n\tYou can only make moral graphs if g doesn't have directed cycles"
                );
            COMPILE_TIME_ASSERT(is_graph<graph_type>::value);
            COMPILE_TIME_ASSERT(is_directed_graph<directed_graph_type>::value);

            copy_graph_structure(g, moral_graph);

            // now marry all the parents (i.e. add edges between parent nodes)
            for (unsigned long i = 0; i < g.number_of_nodes(); ++i)
            {
                // loop over all combinations of parents of g.node(i)
                for (unsigned long j = 0; j < g.node(i).number_of_parents(); ++j)
                {
                    for (unsigned long k = 0; k < g.node(i).number_of_parents(); ++k)
                    {
                        const unsigned long p1 = g.node(i).parent(j).index();
                        const unsigned long p2 = g.node(i).parent(k).index();
                        if (p1 == p2)
                            continue;

                        if (moral_graph.has_edge(p1,p2) == false)
                            moral_graph.add_edge(p1,p2);
                    }
                }
            }
        }

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

        template <
            typename graph_type,
            typename sets_of_int
            >
        bool is_clique (
            const graph_type& g,
            const sets_of_int& clique
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(graph_contains_length_one_cycle(g) == false,
                "\tvoid is_clique(g, clique)"
                << "\n\tinvalid graph"
                );
    #ifdef ENABLE_ASSERTS
            clique.reset();
            while (clique.move_next())
            {
                const unsigned long x = clique.element();
                DLIB_ASSERT( x < g.number_of_nodes(),
                    "\tvoid is_clique(g, clique)"
                    << "\n\tthe clique set contained an invalid node index"
                    << "\n\tx:                   " << x
                    << "\n\tg.number_of_nodes(): " << g.number_of_nodes()
                    );
            }
    #endif

            COMPILE_TIME_ASSERT(is_graph<graph_type>::value);

            std::vector<unsigned long> v;
            v.reserve(clique.size());
            clique.reset();
            while (clique.move_next())
            {
                v.push_back(clique.element());
            }

            for (unsigned long i = 0; i < v.size(); ++i)
            {
                for (unsigned long j = 0; j < v.size(); ++j)
                {
                    if (v[i] == v[j])
                        continue;
                    if (g.has_edge(v[i], v[j]) == false)
                        return false;
                }
            }

            return true;
        }

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

        template <
            typename graph_type,
            typename sets_of_int
            >
        bool is_maximal_clique (
            const graph_type& g,
            const sets_of_int& clique
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(graph_contains_length_one_cycle(g) == false,
                "\tvoid is_maximal_clique(g, clique)"
                << "\n\tinvalid graph"
                );
            DLIB_ASSERT(is_clique(g,clique) == true,
                "\tvoid is_maximal_clique(g, clique)"
                << "\n\tinvalid graph"
                );
    #ifdef ENABLE_ASSERTS
            clique.reset();
            while (clique.move_next())
            {
                const unsigned long x = clique.element();
                DLIB_ASSERT( x < g.number_of_nodes(),
                    "\tvoid is_maximal_clique(g, clique)"
                    << "\n\tthe clique set contained an invalid node index"
                    << "\n\tx:                   " << x
                    << "\n\tg.number_of_nodes(): " << g.number_of_nodes()
                    );
            }
    #endif

            COMPILE_TIME_ASSERT(is_graph<graph_type>::value);

            if (clique.size() == 0)
                return true;

            // get an element in the clique and make sure that
            // none of its neighbors that aren't in the clique are connected
            // to all the elements of the clique.
            clique.reset();
            clique.move_next();
            const unsigned long idx = clique.element();

            for (unsigned long i = 0; i < g.node(idx).number_of_neighbors(); ++i)
            {
                const unsigned long n = g.node(idx).neighbor(i).index();
                if (clique.is_member(n))
                    continue;

                // now loop over all the clique members and make sure they don't all
                // share an edge with node n
                bool all_share_edge = true;
                clique.reset();
                while (clique.move_next())
                {
                    if (g.has_edge(clique.element(), n) == false)
                    {
                        all_share_edge = false;
                        break;
                    }
                }

                if (all_share_edge == true)
                    return false;
            }

            return true;
        }

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

        template <
            typename T
            >
        typename enable_if<is_directed_graph<T>,bool>::type graph_contains_length_one_cycle (
            const T& graph
        )
        {
            for (unsigned long i = 0; i < graph.number_of_nodes(); ++i)
            {
                // make sure none of this guys children are actually itself
                for (unsigned long n = 0; n < graph.node(i).number_of_children(); ++n)
                {
                    if (graph.node(i).child(n).index() == i)
                        return true;
                }
            }

            return false;
        }

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

        template <
            typename T
            >
        typename enable_if<is_graph<T>,bool>::type graph_contains_length_one_cycle (
            const T& graph
        )
        {
            for (unsigned long i = 0; i < graph.number_of_nodes(); ++i)
            {
                // make sure none of this guys neighbors are actually itself
                for (unsigned long n = 0; n < graph.node(i).number_of_neighbors(); ++n)
                {
                    if (graph.node(i).neighbor(n).index() == i)
                        return true;
                }
            }

            return false;
        }

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

        namespace graph_helpers
        {
            struct pair
            {
                unsigned long index;
                unsigned long num_neighbors;

                bool operator< (const pair& p) const { return num_neighbors < p.num_neighbors; }
            };

            template <
                typename T,
                typename S,
                typename V
                >
            void search_graph_for_triangulate (
                const T& n,
                S& visited,
                V& order_visited
            )
            {
                // base case of recursion.  stop when we hit a node we have
                // already visited.
                if (visited.is_member(n.index()))
                    return;

                // record that we have visited this node
                order_visited.push_back(n.index());
                unsigned long temp = n.index();
                visited.add(temp);

                // we want to visit all the neighbors of this node but do
                // so by visiting the nodes with the most neighbors first.  So
                // lets make a vector that lists the nodes in the order we
                // want to visit them
                std::vector<pair> neighbors;
                for (unsigned long i = 0; i < n.number_of_neighbors(); ++i)
                {
                    pair p;
                    p.index = i;
                    p.num_neighbors = n.neighbor(i).number_of_neighbors();
                    neighbors.push_back(p);
                }

                // now sort the neighbors array so that the neighbors with the
                // most neighbors come first.
                std::sort(neighbors.rbegin(), neighbors.rend());

                // now visit all the nodes
                for (unsigned long i = 0; i < neighbors.size(); ++i)
                {
                    search_graph_for_triangulate(n.neighbor(neighbors[i].index), visited, order_visited);
                }
            }
        } // end namespace graph_helpers

        template <
            typename graph_type,
            typename set_of_sets_of_int
            >
        void triangulate_graph_and_find_cliques (
            graph_type& g,
            set_of_sets_of_int& cliques
        )
        {

            // make sure requires clause is not broken
            DLIB_ASSERT(graph_contains_length_one_cycle(g) == false,
                "\tvoid triangulate_graph_and_find_cliques(g, cliques)"
                << "\n\tInvalid graph"
                );
            DLIB_ASSERT(graph_is_connected(g) == true,
                "\tvoid triangulate_graph_and_find_cliques(g, cliques)"
                << "\n\tInvalid graph"
                );

            COMPILE_TIME_ASSERT(is_graph<graph_type>::value);

            using namespace graph_helpers;
            using namespace std;
            typedef typename set_of_sets_of_int::type set_of_int;

            cliques.clear();

            // first we find the node with the most neighbors
            unsigned long max_index = 0;
            unsigned long num_neighbors = 0;
            for (unsigned long i = 0; i < g.number_of_nodes(); ++i)
            {
                if (g.node(i).number_of_neighbors() > num_neighbors)
                {
                    max_index = i;
                    num_neighbors = g.node(i).number_of_neighbors();
                }
            }

            // now we do a depth first search of the entire graph starting
            // with the node we just found.  We record the order in which
            // we visit each node in the vector order_visited.
            std::vector<unsigned long> order_visited;
            set_of_int visited;
            search_graph_for_triangulate(g.node(max_index), visited, order_visited);

            set_of_int clique;

            // now add edges to the graph to make it triangulated
            while (visited.size() > 0)
            {
                // we are going to enumerate over the nodes in the reverse of the
                // order in which they were visited.  So get the last node out.
                const unsigned long idx = order_visited.back();
                order_visited.pop_back();
                visited.destroy(idx);

                // as a start add this node to our current clique
                unsigned long temp = idx;
                clique.clear();
                clique.add(temp);

                // now we want to make a clique that contains node g.node(idx) and
                // all of its neighbors that are still recorded in the visited set
                // (except for neighbors that have only one edge).
                for (unsigned long i = 0; i < g.node(idx).number_of_neighbors(); ++i)
                {
                    // get the index of the i'th neighbor
                    unsigned long nidx = g.node(idx).neighbor(i).index();

                    // add it to the clique if it is still in visited and it isn't
                    // a node with only one neighbor
                    if (visited.is_member(nidx) == true &&
                        g.node(nidx).number_of_neighbors() != 1)
                    {
                        // add edges between this new node and all the nodes
                        // that are already in the clique
                        clique.reset();
                        while (clique.move_next())
                        {
                            if (g.has_edge(nidx, clique.element()) == false)
                                g.add_edge(nidx, clique.element());
                        }

                        // now also record that we added this node to the clique
                        clique.add(nidx);
                    }
                }

                if (cliques.is_member(clique) == false && is_maximal_clique(g,clique) )
                {
                    cliques.add(clique);
                }

                // now it is possible that we are missing some cliques of size 2 since
                // above we didn't add nodes with only one edge to any of our cliques.
                // Now lets make sure all these nodes are accounted for
                for (unsigned long i = 0; i < g.number_of_nodes(); ++i)
                {
                    clique.clear();
                    if (g.node(i).number_of_neighbors() == 1)
                    {
                        unsigned long temp = i;
                        clique.add(temp);
                        temp = g.node(i).neighbor(0).index();
                        clique.add(temp);

                        if (cliques.is_member(clique) == false)
                            cliques.add(clique);
                    }
                }
            }

        }

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

        template <
            typename graph_type,
            typename join_tree_type
            >
        void create_join_tree (
            const graph_type& g,
            join_tree_type& join_tree
        )
        {
            // make sure requires clause is not broken
            DLIB_ASSERT(graph_contains_length_one_cycle(g) == false,
                "\tvoid create_join_tree(g, join_tree)"
                << "\n\tInvalid graph"
                );
            DLIB_ASSERT(graph_is_connected(g) == true,
                "\tvoid create_join_tree(g, join_tree)"
                << "\n\tInvalid graph"
                );

            COMPILE_TIME_ASSERT(is_graph<graph_type>::value);
            COMPILE_TIME_ASSERT(is_graph<join_tree_type>::value);

            typedef typename join_tree_type::type set_of_int;
            typedef typename join_tree_type::edge_type set_of_int_edge;
            typedef typename set<set_of_int>::kernel_1b_c set_of_sets_of_int;

            copy_graph_structure(g, join_tree);

            // don't even bother in this case
            if (g.number_of_nodes() == 0)
                return;

            set_of_sets_of_int cliques;
            set_of_int s;

            triangulate_graph_and_find_cliques(join_tree, cliques);

            join_tree.set_number_of_nodes(cliques.size());

            // copy the cliques into each of the nodes of tree
            for (unsigned long i = 0; i < join_tree.number_of_nodes(); ++i)
            {
                cliques.remove_any(s);
                s.swap(join_tree.node(i).data);
            }

            set_of_int_edge e;

            // add all possible edges to the join_tree
            for (unsigned long i = 0; i < join_tree.number_of_nodes(); ++i)
            {
                for (unsigned long j = i+1; j < join_tree.number_of_nodes(); ++j)
                {
                    set_intersection(
                        join_tree.node(i).data,
                        join_tree.node(j).data,
                        e);

                    if (e.size() > 0)
                    {
                        join_tree.add_edge(i,j);
                        edge(join_tree,i,j).swap(e);
                    }
                }
            }

            // now we just need to remove the unnecessary edges so that we get a
            // proper join tree
            s.clear();
            set_of_int& good = s; // rename s to something slightly more meaningful
            // good will contain nodes that have been "approved"
            unsigned long n = 0;
            good.add(n);

            std::vector<unsigned long> vtemp;

            while (good.size() < join_tree.number_of_nodes())
            {
                // figure out which of the neighbors of nodes in good has the best edge
                unsigned long best_bad_idx = 0;
                unsigned long best_good_idx = 0;
                unsigned long best_overlap = 0;
                good.reset();
                while (good.move_next())
                {
                    // loop over all the neighbors of the current node in good
                    for (unsigned long i = 0; i < join_tree.node(good.element()).number_of_neighbors(); ++i)
                    {
                        const unsigned long idx = join_tree.node(good.element()).neighbor(i).index();
                        if (!good.is_member(idx))
                        {
                            const unsigned long overlap = join_tree.node(good.element()).edge(i).size();

                            if (overlap > best_overlap)
                            {
                                best_overlap = overlap;
                                best_bad_idx = idx;
                                best_good_idx = good.element();
                            }
                        }
                    }
                }

                // now remove all the edges from best_bad_idx to the nodes in good except for the
                // edge to best_good_idx.
                for (unsigned long i = 0; i < join_tree.node(best_bad_idx).number_of_neighbors(); ++i)
                {
                    const unsigned long idx = join_tree.node(best_bad_idx).neighbor(i).index();
                    if (idx != best_good_idx && good.is_member(idx))
                    {
                        vtemp.push_back(idx);
                    }
                }

                for (unsigned long i = 0; i < vtemp.size(); ++i)
                    join_tree.remove_edge(vtemp[i], best_bad_idx);

                vtemp.clear();

                // and finally add this bad index into the good set
                good.add(best_bad_idx);
            }
        }

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

        namespace graph_helpers
        {
            template <
                typename T,
                typename U
                >
            bool validate_join_tree (
                const T& n,
                U& deads,
                unsigned long parent = 0xffffffff
            )
            /*!
                this function makes sure that a join tree satisfies the following criterion for paths starting at the given node:
                    - for all valid i and j such that i and j are both < #join_tree.number_of_nodes()
                        - let X be the set of numbers that is contained in both #join_tree.node(i).data
                          and #join_tree.node(j).data
                        - It is the case that all nodes on the unique path between #join_tree.node(i)
                          and #join_tree.node(j) contain the numbers from X in their sets.

                returns true if validation passed and false if there is a problem with the tree
            !*/
            {
                n.data.reset();
                while (n.data.move_next())
                {
                    if (deads.is_member(n.data.element()))
                        return false;
                }

                for (unsigned long i = 0; i < n.number_of_neighbors(); ++i)
                {
                    if (n.neighbor(i).index() == parent)
                        continue;

                    // add anything to dead stuff
                    n.data.reset();
                    while (n.data.move_next())
                    {
                        if (n.neighbor(i).data.is_member(n.data.element()) == false)
                        {
                            unsigned long temp = n.data.element();
                            deads.add(temp);
                        }
                    }

                    if (validate_join_tree(n.neighbor(i), deads, n.index()) == false)
                        return false;

                    // remove this nodes stuff from dead stuff
                    n.data.reset();
                    while (n.data.move_next())
                    {
                        if (n.neighbor(i).data.is_member(n.data.element()) == false)
                        {
                            unsigned long temp = n.data.element();
                            deads.destroy(temp);
                        }
                    }
                }

                return true;
            }
        }

        template <
            typename graph_type,
            typename join_tree_type
            >
        bool is_join_tree (
            const graph_type& g,
            const join_tree_type& join_tree
        )
        {

            // make sure requires clause is not broken
            DLIB_ASSERT(graph_contains_length_one_cycle(g) == false,
                "\tvoid create_join_tree(g, join_tree)"
                << "\n\tInvalid graph"
                );
            DLIB_ASSERT(graph_is_connected(g) == true,
                "\tvoid create_join_tree(g, join_tree)"
                << "\n\tInvalid graph"
                );

            COMPILE_TIME_ASSERT(is_graph<graph_type>::value || is_directed_graph<graph_type>::value);
            COMPILE_TIME_ASSERT(is_graph<join_tree_type>::value);

            if (graph_contains_undirected_cycle(join_tree))
                return false;

            if (graph_is_connected(join_tree) == false)
                return false;

            // verify that the path condition of the join tree is valid
            for (unsigned long i = 0; i < join_tree.number_of_nodes(); ++i)
            {
                typename join_tree_type::type deads;
                if (graph_helpers::validate_join_tree(join_tree.node(i), deads) == false)
                    return false;
            }

            typename join_tree_type::edge_type e;
            typename join_tree_type::edge_type all;
            // now make sure that the edges contain correct intersections
            for (unsigned long i = 0; i < join_tree.number_of_nodes(); ++i)
            {
                set_union(all,join_tree.node(i).data, all);
                for (unsigned long j = 0; j < join_tree.node(i).number_of_neighbors(); ++j)
                {
                    set_intersection(join_tree.node(i).data,
                                     join_tree.node(i).neighbor(j).data,
                                     e);

                    if (!(e == join_tree.node(i).edge(j)))
                        return false;
                }
            }

            // and finally check that all the nodes in g show up in the join tree
            if (all.size() != g.number_of_nodes())
                return false;
            all.reset();
            while (all.move_next())
            {
                if (all.element() >= g.number_of_nodes())
                    return false;
            }

            return true;
        }

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

    }

    #endif // DLIB_GRAPH_UTILs_