PQ-Trees - Graph Theory

Skip to content
Back to top
View this page
Edit this page

# PQ-Trees

This module implements PQ-Trees, a data structure use to represent all permutations of the columns of a matrix which satisfy the consecutive ones property:

> A binary matrix satisfies the consecutive ones property if the 1s are contiguous in each of its rows (or equivalently, if no row contains the regexp pattern \(10^+1\)).

> Alternatively, one can say that a sequence of sets \(S_1,...,S_n\) satisfies the consecutive ones property if for any \(x\) the indices of the sets containing \(x\) is an interval of \([1,n]\).

This module is used for the recognition of Interval Graphs (see `is_interval()`).

P-tree and Q-tree

- A \(P\)-tree with children \(c_1,...,c_k\) (which can be \(P\)-trees, \(Q\)-trees, or actual sets of points) indicates that all \(k!\) permutations of the children are allowed.

  Example: \(\{1,2\},\{3,4\},\{5,6\}\) (disjoint sets can be permuted in any way)

- A \(Q\)-tree with children \(c_1,...,c_k\) (which can be \(P\)-trees, \(Q\)-trees, or actual sets of points) indicates that only two permutations of its children are allowed: \(c_1,...,c_k\) or \(c_k,...,c_1\).

  Example: \(\{1,2\},\{2,3\},\{3,4\},\{4,5\},\{5,6\}\) (only two permutations of these sets have the consecutive ones property).

Computation of all possible orderings

1. In order to compute all permutations of a sequence of sets \(S_1,...,S_k\) satisfying the consecutive ones property, we initialize \(T\) as a \(P\)-tree whose children are all the \(S_1,...,S_k\), thus representing the set of all \(k!\) permutations of them.

2. We select some element \(x\) and update the data structure \(T\) to restrict the permutations it describes to those that keep the occurrences of \(x\) on an interval of \([1,...,k]\). This will result in a new \(P\)-tree whose children are:

   - all \(\bar c_x\) sets \(S_i\) which do not contain \(x\).

   - a new \(P\)-tree whose children are the \(c_x\) sets \(S_i\) containing \(x\).

   This describes the set of all \(c_x!\times \bar c'_x!\) permutations of \(S_1,...,S_k\) that keep the sets containing \(x\) on an interval.

3. We take a second element \(x'\) and update the data structure \(T\) to restrict the permutations it describes to those that keep \(x'\) on an interval of \([1,...,k]\). The sets \(S_1,...,S_k\) belong to 4 categories:

   - The family \(S_{00}\) of sets which do not contain any of \(x,x'\).

   - The family \(S_{01}\) of sets which contain \(x'\) but do not contain \(x\).

   - The family \(S_{10}\) of sets which contain \(x\) but do not contain \(x'\).

   - The family \(S_{11}\) of sets which contain \(x'\) and \(x'\).

   With these notations, the permutations of \(S_1,...,S_k\) which keep the occurrences of \(x\) and \(x'\) on an interval are of two forms:

   - <some sets \(S_{00}\)>, <sets from \(S_{10}\)>, <sets from \(S_{11}\)>, <sets from \(S_{01}\)>, <other sets from \(S_{00}\)>

   - <some sets \(S_{00}\)>, <sets from \(S_{01}\)>, <sets from \(S_{11}\)>, <sets from \(S_{10}\)>, <other sets from \(S_{00}\)>

   These permutations can be modeled with the following \(PQ\)-tree:

   - A \(P\)-tree whose children are:

     - All sets from \(S_{00}\)

     - A \(Q\)-tree whose children are:

       - A \(P\)-tree with whose children are the sets from \(S_{10}\)

       - A \(P\)-tree with whose children are the sets from \(S_{11}\)

       - A \(P\)-tree with whose children are the sets from \(S_{01}\)

4. One at a time, we update the data structure with each element until they are all exhausted, or until we reach a proof that no permutation satisfying the consecutive ones property exists.

   Using these two types of tree, and exploring the different cases of intersection, it is possible to represent all the possible permutations of our sets satisfying our constraints, or to prove that no such ordering exists. This is the whole purpose of this module, and is explained with more details in many places, for example in the following document from Hajiaghayi [Haj2000].

Authors:

Nathann Cohen (initial implementation)

## Methods and functions

class sage.graphs.pq_trees.P(seq)

  Bases: `PQ`

  A P-Tree is a PQ-Tree whose children can be permuted in any way.

  For more information, see the documentation of `sage.graphs.pq_trees`.

  cardinality()

    Return the number of orderings allowed by the structure.

    See also

    `orderings()` – iterate over all admissible orderings

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = P([[0,3], [1,2], [2,3], [2,4], [4,0],[2,8], [2,9]])
        sage: p.cardinality()
        5040
        sage: p.set_contiguous(3)
        (1, True)
        sage: p.cardinality()
        1440

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = P([[Integer(0),Integer(3)], [Integer(1),Integer(2)], [Integer(2),Integer(3)], [Integer(2),Integer(4)], [Integer(4),Integer(0)],[Integer(2),Integer(8)], [Integer(2),Integer(9)]])
        >>> p.cardinality()
        5040
        >>> p.set_contiguous(Integer(3))
        (1, True)
        >>> p.cardinality()
        1440

  orderings()

    Iterate over all orderings of the sets allowed by the structure.

    See also

    `cardinality()` – return the number of orderings

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = P([[2,4], [1,2], [0,8], [0,5]])
        sage: for o in p.orderings():
        ....:    print(o)
        ({2, 4}, {1, 2}, {0, 8}, {0, 5})
        ({2, 4}, {1, 2}, {0, 5}, {0, 8})
        ({2, 4}, {0, 8}, {1, 2}, {0, 5})
        ({2, 4}, {0, 8}, {0, 5}, {1, 2})
        ...

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = P([[Integer(2),Integer(4)], [Integer(1),Integer(2)], [Integer(0),Integer(8)], [Integer(0),Integer(5)]])
        >>> for o in p.orderings():
        ...    print(o)
        ({2, 4}, {1, 2}, {0, 8}, {0, 5})
        ({2, 4}, {1, 2}, {0, 5}, {0, 8})
        ({2, 4}, {0, 8}, {1, 2}, {0, 5})
        ({2, 4}, {0, 8}, {0, 5}, {1, 2})
        ...

  set_contiguous(v)

    Update `self` so that the sets containing `v` are contiguous for any admissible permutation of its subtrees.

    INPUT:

    - `v` – an element of the ground set

    OUTPUT:

    According to the cases:

    > - `(EMPTY, ALIGNED)` if no set of the tree contains an occurrence of `v`

    > - `(FULL, ALIGNED)` if all the sets of the tree contain `v`

    > - `(PARTIAL, ALIGNED)` if some (but not all) of the sets contain `v`, all of which are aligned to the right of the ordering at the end when the function ends

    > - `(PARTIAL, UNALIGNED)` if some (but not all) of the sets contain `v`, though it is impossible to align them all to the right

    In any case, the sets containing `v` are contiguous when this function ends. If there is no possibility of doing so, the function raises a `ValueError` exception.

    EXAMPLES:

    Ensuring the sets containing `0` are continuous:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = P([[0,3], [1,2], [2,3], [2,4], [4,0],[2,8], [2,9]])
        sage: p.set_contiguous(0)
        (1, True)
        sage: print(p)
        ('P', [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}, ('P', [{0, 3}, {0, 4}])])

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = P([[Integer(0),Integer(3)], [Integer(1),Integer(2)], [Integer(2),Integer(3)], [Integer(2),Integer(4)], [Integer(4),Integer(0)],[Integer(2),Integer(8)], [Integer(2),Integer(9)]])
        >>> p.set_contiguous(Integer(0))
        (1, True)
        >>> print(p)
        ('P', [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}, ('P', [{0, 3}, {0, 4}])])

    Impossible situation:

    Sage

        sage: p = P([[0,1], [1,2], [2,3], [3,0]])
        sage: p.set_contiguous(0)
        (1, True)
        sage: p.set_contiguous(1)
        (1, True)
        sage: p.set_contiguous(2)
        (1, True)
        sage: p.set_contiguous(3)
        Traceback (most recent call last):
        ...
        ValueError: Impossible

    [Python]

        >>> from sage.all import *
        >>> p = P([[Integer(0),Integer(1)], [Integer(1),Integer(2)], [Integer(2),Integer(3)], [Integer(3),Integer(0)]])
        >>> p.set_contiguous(Integer(0))
        (1, True)
        >>> p.set_contiguous(Integer(1))
        (1, True)
        >>> p.set_contiguous(Integer(2))
        (1, True)
        >>> p.set_contiguous(Integer(3))
        Traceback (most recent call last):
        ...
        ValueError: Impossible

class sage.graphs.pq_trees.PQ(seq)

  Bases: `object`

  PQ-Trees

  This class should not be instantiated by itself: it is extended by `P` and `Q`. See the documentation of `sage.graphs.pq_trees` for more information.

  AUTHOR : Nathann Cohen

  flatten()

    Return a flattened copy of `self`.

    If `self` has only one child, we may as well consider its child’s children, as `self` encodes no information. This method recursively “flattens” trees having only on PQ-tree child, and returns it.

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = Q([P([[2,4], [2,8], [2,9]])])
        sage: p.flatten()
        ('P', [{2, 4}, {8, 2}, {9, 2}])

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = Q([P([[Integer(2),Integer(4)], [Integer(2),Integer(8)], [Integer(2),Integer(9)]])])
        >>> p.flatten()
        ('P', [{2, 4}, {8, 2}, {9, 2}])

  number_of_children()

    Return the number of children of `self`.

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
        sage: p.number_of_children()
        3

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = Q([[Integer(1),Integer(2)], [Integer(2),Integer(3)], P([[Integer(2),Integer(4)], [Integer(2),Integer(8)], [Integer(2),Integer(9)]])])
        >>> p.number_of_children()
        3

  ordering()

    Return the current ordering given by listing the leaves from left to right.

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
        sage: p.ordering()
        [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}]

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = Q([[Integer(1),Integer(2)], [Integer(2),Integer(3)], P([[Integer(2),Integer(4)], [Integer(2),Integer(8)], [Integer(2),Integer(9)]])])
        >>> p.ordering()
        [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}]

  reverse()

    Recursively reverse `self` and its children.

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = Q([[1,2], [2,3], P([[2,4], [2,8], [2,9]])])
        sage: p.ordering()
        [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}]
        sage: p.reverse()
        sage: p.ordering()
        [{9, 2}, {8, 2}, {2, 4}, {2, 3}, {1, 2}]

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = Q([[Integer(1),Integer(2)], [Integer(2),Integer(3)], P([[Integer(2),Integer(4)], [Integer(2),Integer(8)], [Integer(2),Integer(9)]])])
        >>> p.ordering()
        [{1, 2}, {2, 3}, {2, 4}, {8, 2}, {9, 2}]
        >>> p.reverse()
        >>> p.ordering()
        [{9, 2}, {8, 2}, {2, 4}, {2, 3}, {1, 2}]

  simplify(v, left=False, right=False)

    Return a simplified copy of `self` according to the element `v`.

    If `self` is a partial P-tree for `v`, we would like to restrict the permutations of its children to permutations keeping the children containing `v` contiguous. This function also “locks” all the elements not containing `v` inside a \(P\)-tree, which is useful when one want to keep the elements containing `v` on one side (which is the case when this method is called).

    INPUT:

    - `left`, `right` – booleans; whether `v` is aligned to the right or to the left

    - `v` – an element of the ground set

    OUTPUT:

    If `self` is a \(Q\)-Tree, the sequence of its children is returned. If `self` is a \(P\)-tree, 2 \(P\)-tree are returned, namely the two \(P\)-tree defined above and restricting the permutations, in the order implied by `left, right` (if `right =True`, the second \(P\)-tree will be the one gathering the elements containing `v`, if `left=True`, the opposite).

    Note

    This method is assumes that `self` is partial for `v`, and aligned to the side indicated by `left, right`.

    EXAMPLES:

    A \(P\)-Tree

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: p = P([[2,4], [1,2], [0,8], [0,5]])
        sage: p.simplify(0, right = True)
        [('P', [{2, 4}, {1, 2}]), ('P', [{0, 8}, {0, 5}])]

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> p = P([[Integer(2),Integer(4)], [Integer(1),Integer(2)], [Integer(0),Integer(8)], [Integer(0),Integer(5)]])
        >>> p.simplify(Integer(0), right = True)
        [('P', [{2, 4}, {1, 2}]), ('P', [{0, 8}, {0, 5}])]

    A \(Q\)-Tree

    Sage

        sage: q = Q([[2,4], [1,2], [0,8], [0,5]])
        sage: q.simplify(0, right = True)
        [{2, 4}, {1, 2}, {0, 8}, {0, 5}]

    [Python]

        >>> from sage.all import *
        >>> q = Q([[Integer(2),Integer(4)], [Integer(1),Integer(2)], [Integer(0),Integer(8)], [Integer(0),Integer(5)]])
        >>> q.simplify(Integer(0), right = True)
        [{2, 4}, {1, 2}, {0, 8}, {0, 5}]

class sage.graphs.pq_trees.Q(seq)

  Bases: `PQ`

  A Q-Tree is a PQ-Tree whose children are ordered up to reversal.

  For more information, see the documentation of `sage.graphs.pq_trees`.

  cardinality()

    Return the number of orderings allowed by the structure.

    See also

    `orderings()` – iterate over all admissible orderings

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: q = Q([[0,3], [1,2], [2,3], [2,4], [4,0],[2,8], [2,9]])
        sage: q.cardinality()
        2

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> q = Q([[Integer(0),Integer(3)], [Integer(1),Integer(2)], [Integer(2),Integer(3)], [Integer(2),Integer(4)], [Integer(4),Integer(0)],[Integer(2),Integer(8)], [Integer(2),Integer(9)]])
        >>> q.cardinality()
        2

  orderings()

    Iterate over all orderings of the sets allowed by the structure.

    See also

    `cardinality()` – return the number of orderings

    EXAMPLES:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: q = Q([[2,4], [1,2], [0,8], [0,5]])
        sage: for o in q.orderings():
        ....:    print(o)
        ({2, 4}, {1, 2}, {0, 8}, {0, 5})
        ({0, 5}, {0, 8}, {1, 2}, {2, 4})

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> q = Q([[Integer(2),Integer(4)], [Integer(1),Integer(2)], [Integer(0),Integer(8)], [Integer(0),Integer(5)]])
        >>> for o in q.orderings():
        ...    print(o)
        ({2, 4}, {1, 2}, {0, 8}, {0, 5})
        ({0, 5}, {0, 8}, {1, 2}, {2, 4})

  set_contiguous(v)

    Update `self` so that the sets containing `v` are contiguous for any admissible permutation of its subtrees.

    INPUT:

    - `v` – an element of the ground set

    OUTPUT:

    According to the cases:

    > - `(EMPTY, ALIGNED)` if no set of the tree contains an occurrence of `v`

    > - `(FULL, ALIGNED)` if all the sets of the tree contain `v`

    > - `(PARTIAL, ALIGNED)` if some (but not all) of the sets contain `v`, all of which are aligned to the right of the ordering at the end when the function ends

    > - `(PARTIAL, UNALIGNED)` if some (but not all) of the sets contain `v`, though it is impossible to align them all to the right

    In any case, the sets containing `v` are contiguous when this function ends. If there is no possibility of doing so, the function raises a `ValueError` exception.

    EXAMPLES:

    Ensuring the sets containing `0` are continuous:

    Sage

        sage: from sage.graphs.pq_trees import P, Q
        sage: q = Q([[2,3], Q([[3,0],[3,1]]), Q([[4,0],[4,5]])])
        sage: q.set_contiguous(0)
        (1, False)
        sage: print(q)
        ('Q', [{2, 3}, {1, 3}, {0, 3}, {0, 4}, {4, 5}])

    Python

        >>> from sage.all import *
        >>> from sage.graphs.pq_trees import P, Q
        >>> q = Q([[Integer(2),Integer(3)], Q([[Integer(3),Integer(0)],[Integer(3),Integer(1)]]), Q([[Integer(4),Integer(0)],[Integer(4),Integer(5)]])])
        >>> q.set_contiguous(Integer(0))
        (1, False)
        >>> print(q)
        ('Q', [{2, 3}, {1, 3}, {0, 3}, {0, 4}, {4, 5}])

    Impossible situation:

    Sage

        sage: p = Q([[0,1], [1,2], [2,0]])
        sage: p.set_contiguous(0)
        Traceback (most recent call last):
        ...
        ValueError: Impossible

    [Python]

        >>> from sage.all import *
        >>> p = Q([[Integer(0),Integer(1)], [Integer(1),Integer(2)], [Integer(2),Integer(0)]])
        >>> p.set_contiguous(Integer(0))
        Traceback (most recent call last):
        ...
        ValueError: Impossible

sage.graphs.pq_trees.reorder_sets(sets)

  Reorders a collection of sets such that each element appears on an interval.

  Given a collection of sets \(C = S_1,...,S_k\) on a ground set \(X\), this function attempts to reorder them in such a way that \(\forall x \in X\) and \(i<j\) with \(x\in S_i, S_j\), then \(x\in S_l\) for every \(i<l<j\) if it exists.

  INPUT:

  - `sets` – list of instances of `list, Set` or `set`

  ALGORITHM: PQ-Trees

  EXAMPLES:

  There is only one way (up to reversal) to represent contiguously the sequence of sets \(\{i-1, i, i+1\}\):

  Sage

      sage: from sage.graphs.pq_trees import reorder_sets
      sage: seq = [Set([i-1,i,i+1]) for i in range(1,15)]

  Python

      >>> from sage.all import *
      >>> from sage.graphs.pq_trees import reorder_sets
      >>> seq = [Set([i-Integer(1),i,i+Integer(1)]) for i in range(Integer(1),Integer(15))]

  We apply a random permutation:

  Sage

      sage: p = Permutations(len(seq)).random_element()
      sage: seq = [ seq[p(i+1)-1] for i in range(len(seq)) ]
      sage: ordered = reorder_sets(seq)
      sage: if not 0 in ordered[0]:
      ....:    ordered = ordered.reverse()
      sage: print(ordered)
      [{0, 1, 2}, {1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}, {5, 6, 7},
       {8, 6, 7}, {8, 9, 7}, {8, 9, 10}, {9, 10, 11}, {10, 11, 12},
       {11, 12, 13}, {12, 13, 14}, {13, 14, 15}]

  [Python]

      >>> from sage.all import *
      >>> p = Permutations(len(seq)).random_element()
      >>> seq = [ seq[p(i+Integer(1))-Integer(1)] for i in range(len(seq)) ]
      >>> ordered = reorder_sets(seq)
      >>> if not Integer(0) in ordered[Integer(0)]:
      ...    ordered = ordered.reverse()
      >>> print(ordered)
      [{0, 1, 2}, {1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}, {5, 6, 7},
       {8, 6, 7}, {8, 9, 7}, {8, 9, 10}, {9, 10, 11}, {10, 11, 12},
       {11, 12, 13}, {12, 13, 14}, {13, 14, 15}]