122 | | Methods |
| 132 | |
| 133 | MILP formulation for the vertex separation |
| 134 | ------------------------------------------ |
| 135 | |
| 136 | We describe bellow a mixed integer linear program (MILP) for determining an |
| 137 | optimal layout for the vertex separation of `G`. This MILP is an improved |
| 138 | version of the formulation proposed in [SP10]_. |
| 139 | |
| 140 | VARIABLES: |
| 141 | |
| 142 | - ``x_v^t`` -- Variable set to 1 if either `v` has an in-neighbor `u` such that |
| 143 | `u\in S_{t'}=\{v_1, v_2,\cdots, v_{t'}\}` and `v\in N_G^+(S_{t'})\backslash |
| 144 | S_{t'}` with `t'\leq t`, or `v\in S_t`. It is set to 0 otherwise. |
| 145 | |
| 146 | - ``y_v^t`` -- Variable set to 1 if `v\in S_t`, and 0 otherwise. The order of |
| 147 | `v` in the layout is the smallest `t` such that `y_v^t==1`. |
| 148 | |
| 149 | - ``u_v^t`` -- Variable set to 1 if `v\in N_G^+(S_t)\backslash S_t` has an |
| 150 | in-neighbor in `S_t`, and set to 0 otherwise. |
| 151 | |
| 152 | - ``z`` -- Objective value to minimize. It is equal to the maximum over all step |
| 153 | `t` of the number of vertices such that `u_v^t==1`. |
| 154 | |
| 155 | MILP formulation: |
| 156 | |
| 157 | .. MATH:: |
| 158 | :nowrap: |
| 159 | |
| 160 | \begin{alignat}{2} |
| 161 | \intertext{Minimize:} |
| 162 | &z&\\ |
| 163 | \intertext{Such that:} |
| 164 | x_v^t &\leq x_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\ |
| 165 | y_v^t &\leq y_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\ |
| 166 | y_v^t &\leq x_w^t& \forall v\in V,\ \forall w\in N^+(v),\ 0\leq t\leq n-1\\ |
| 167 | \sum_{v \in V} y_v^0 &\leq 1&\\ |
| 168 | \sum_{v \in V} y_v^{t+1} &\leq 1+\sum_{v \in V} y_v^t& 0\leq t\leq n-2\\ |
| 169 | \sum_{v \in V} y_v^{N-1} &= n&\\ |
| 170 | x_v^t-y_v^t&\leq u_v^t & \forall v \in V,\ 0\leq t\leq n-1\\ |
| 171 | \sum_{v \in V} u_v^t &\leq z& 0\leq t\leq n-1\\ |
| 172 | 0 \leq x_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\ |
| 173 | 0 \leq u_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\ |
| 174 | y_v^t &\in \{0,1\}& \forall v\in V,\ 0\leq t\leq n-1\\ |
| 175 | 0 \leq z &\leq n& |
| 176 | \end{alignat} |
| 177 | |
| 178 | The vertex separation of `G` is given by the value of `z`, and the order of |
| 179 | vertex `v` in the optimal layout is given by the smallest `t` for which |
| 180 | `y_v^t==1`. |
| 181 | |
| 182 | |
| 183 | REFERENCES |
| 184 | ---------- |
| 185 | |
| 186 | .. [Bod98] *A partial k-arboretum of graphs with bounded treewidth*, Hans |
| 187 | L. Bodlaender, Theoretical Computer Science 209(1-2):1-45, 1998. |
| 188 | |
| 189 | .. [Kin92] *The vertex separation number of a graph equals its path-width*, |
| 190 | Nancy G. Kinnersley, Information Processing Letters 42(6):345-350, 1992. |
| 191 | |
| 192 | .. [SP10] *Lightpath Reconfiguration in WDM networks*, Fernando Solano and |
| 193 | Michal Pioro, IEEE/OSA Journal of Optical Communication and Networking |
| 194 | 2(12):1010-1021, 2010. |
| 195 | |
| 196 | |
| 197 | AUTHORS |
| 198 | ------- |
| 199 | |
| 200 | - Nathann Cohen (2011-10): Initial version and exact exponential algorithm |
| 201 | |
| 202 | - David Coudert (2012-04): MILP formulation and tests functions |
| 203 | |
| 204 | |
| 205 | |
| 206 | METHODS |
| 557 | |
| 558 | |
| 559 | ################################################################# |
| 560 | # Function for testing the validity of a linear vertex ordering # |
| 561 | ################################################################# |
| 562 | |
| 563 | def is_valid_ordering(G, L): |
| 564 | r""" |
| 565 | Test if the linear vertex ordering `L` is valid for (di)graph `G`. |
| 566 | |
| 567 | A linear ordering `L` of the vertices of a (di)graph `G` is valid if all |
| 568 | vertices of `G` are in `L`, and if `L` contains no other vertex and no |
| 569 | duplicated vertices. |
| 570 | |
| 571 | INPUT: |
| 572 | |
| 573 | - ``G`` -- a Graph or a DiGraph. |
| 574 | |
| 575 | - ``L`` -- an ordered list of the vertices of ``G``. |
| 576 | |
| 577 | |
| 578 | OUTPUT: |
| 579 | |
| 580 | Returns `True` if `L` is a valid vertex ordering for `G`, and False |
| 581 | oterwise. |
| 582 | |
| 583 | |
| 584 | EXAMPLE: |
| 585 | |
| 586 | Path decomposition of a cycle:: |
| 587 | |
| 588 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 589 | sage: G = graphs.CycleGraph(6) |
| 590 | sage: L = [u for u in G.vertices()] |
| 591 | sage: vertex_separation.is_valid_ordering(G, L) |
| 592 | True |
| 593 | sage: vertex_separation.is_valid_ordering(G, [1,2]) |
| 594 | False |
| 595 | |
| 596 | TEST: |
| 597 | |
| 598 | Giving anything else than a Graph or a DiGraph:: |
| 599 | |
| 600 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 601 | sage: vertex_separation.is_valid_ordering(2, []) |
| 602 | Traceback (most recent call last): |
| 603 | ... |
| 604 | ValueError: The input parameter must be a Graph or a DiGraph. |
| 605 | |
| 606 | Giving anything else than a list:: |
| 607 | |
| 608 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 609 | sage: G = graphs.CycleGraph(6) |
| 610 | sage: vertex_separation.is_valid_ordering(G, {}) |
| 611 | Traceback (most recent call last): |
| 612 | ... |
| 613 | ValueError: The second parameter must be of type 'list'. |
| 614 | """ |
| 615 | from sage.graphs.graph import Graph |
| 616 | from sage.graphs.digraph import DiGraph |
| 617 | if not isinstance(G, Graph) and not isinstance(G, DiGraph): |
| 618 | raise ValueError("The input parameter must be a Graph or a DiGraph.") |
| 619 | if not isinstance(L, list): |
| 620 | raise ValueError("The second parameter must be of type 'list'.") |
| 621 | |
| 622 | return len( set( G.vertices() ).symmetric_difference( set(L) ) ) == 0 |
| 623 | |
| 624 | |
| 625 | #################################################################### |
| 626 | # Measurement functions of the widths of some graph decompositions # |
| 627 | #################################################################### |
| 628 | |
| 629 | def width_of_path_decomposition(G, L): |
| 630 | r""" |
| 631 | Returns the width of the path decomposition induced by the linear ordering |
| 632 | `L` of the vertices of `G`. |
| 633 | |
| 634 | If `G` is an instance of Graph, this function returns the width `pw_L(G)` of |
| 635 | the path decomposition induced by the linear ordering `L` of the vertices of |
| 636 | `G`. If `G` is a DiGraph, it returns instead the width `vs_L(G)` of the |
| 637 | directed path decomposition induced by the linear ordering `L` of the |
| 638 | vertices of `G`, where |
| 639 | |
| 640 | .. MATH:: |
| 641 | |
| 642 | vs_L(G) & = \max_{0\leq i< |V|-1} | N^+(L[:i])\setminus L[:i] |\\ |
| 643 | pw_L(G) & = \max_{0\leq i< |V|-1} | N(L[:i])\setminus L[:i] |\\ |
| 644 | |
| 645 | INPUT: |
| 646 | |
| 647 | - ``G`` -- a Graph or a DiGraph |
| 648 | |
| 649 | - ``L`` -- a linear ordering of the vertices of ``G`` |
| 650 | |
| 651 | EXAMPLES: |
| 652 | |
| 653 | Path decomposition of a cycle:: |
| 654 | |
| 655 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 656 | sage: G = graphs.CycleGraph(6) |
| 657 | sage: L = [u for u in G.vertices()] |
| 658 | sage: vertex_separation.width_of_path_decomposition(G, L) |
| 659 | 2 |
| 660 | |
| 661 | Directed path decomposition of a circuit:: |
| 662 | |
| 663 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 664 | sage: G = digraphs.Circuit(6) |
| 665 | sage: L = [u for u in G.vertices()] |
| 666 | sage: vertex_separation.width_of_path_decomposition(G, L) |
| 667 | 1 |
| 668 | |
| 669 | TESTS: |
| 670 | |
| 671 | Path decomposition of a BalancedTree:: |
| 672 | |
| 673 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 674 | sage: G = graphs.BalancedTree(3,2) |
| 675 | sage: pw, L = vertex_separation.path_decomposition(G) |
| 676 | sage: pw == vertex_separation.width_of_path_decomposition(G, L) |
| 677 | True |
| 678 | sage: L.reverse() |
| 679 | sage: pw == vertex_separation.width_of_path_decomposition(G, L) |
| 680 | False |
| 681 | |
| 682 | Directed path decomposition of a circuit:: |
| 683 | |
| 684 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 685 | sage: G = digraphs.Circuit(8) |
| 686 | sage: vs, L = vertex_separation.vertex_separation(G) |
| 687 | sage: vs == vertex_separation.width_of_path_decomposition(G, L) |
| 688 | True |
| 689 | sage: L = [0,4,6,3,1,5,2,7] |
| 690 | sage: vs == vertex_separation.width_of_path_decomposition(G, L) |
| 691 | False |
| 692 | |
| 693 | Giving a wrong linear ordering:: |
| 694 | |
| 695 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 696 | sage: G = Graph() |
| 697 | sage: vertex_separation.width_of_path_decomposition(G, ['a','b']) |
| 698 | Traceback (most recent call last): |
| 699 | ... |
| 700 | ValueError: The input linear vertex ordering L is not valid for G. |
| 701 | """ |
| 702 | if not is_valid_ordering(G, L): |
| 703 | raise ValueError("The input linear vertex ordering L is not valid for G.") |
| 704 | |
| 705 | vsL = 0 |
| 706 | S = [] |
| 707 | neighbors_of_S_in_V_minus_S = [] |
| 708 | |
| 709 | for u in L: |
| 710 | |
| 711 | # We remove u from the list of neighbors of S |
| 712 | if u in neighbors_of_S_in_V_minus_S: |
| 713 | neighbors_of_S_in_V_minus_S.remove(u) |
| 714 | |
| 715 | # We add vertex u to the set S |
| 716 | S.append(u) |
| 717 | |
| 718 | if G._directed: |
| 719 | Nu = G.neighbors_out(u) |
| 720 | else: |
| 721 | Nu = G.neighbors(u) |
| 722 | |
| 723 | # We add the (out-)neighbors of u to the list of neighbors of S |
| 724 | for v in Nu: |
| 725 | if (not v in S) and (not v in neighbors_of_S_in_V_minus_S): |
| 726 | neighbors_of_S_in_V_minus_S.append(v) |
| 727 | |
| 728 | # We update the cost of the vertex separation |
| 729 | vsL = max( vsL, len(neighbors_of_S_in_V_minus_S) ) |
| 730 | |
| 731 | return vsL |
| 732 | |
| 733 | |
| 734 | ########################################## |
| 735 | # MILP formulation for vertex separation # |
| 736 | ########################################## |
| 737 | |
| 738 | def vertex_separation_MILP(G, integrality = False, solver = None, verbosity = 0): |
| 739 | r""" |
| 740 | Computes the vertex separation of `G` and the optimal ordering of its |
| 741 | vertices using an MILP formulation. |
| 742 | |
| 743 | This function uses a mixed integer linear program (MILP) for determining an |
| 744 | optimal layout for the vertex separation of `G`. This MILP is an improved |
| 745 | version of the formulation proposed in [SP10]_. See the module's |
| 746 | documentation for more details on this MILP formulation. |
| 747 | |
| 748 | INPUTS: |
| 749 | |
| 750 | - ``G`` -- a DiGraph |
| 751 | |
| 752 | - ``integrality`` -- (default: ``False``) Specify if all variables must be |
| 753 | integral of if some variables can be relaxed. |
| 754 | |
| 755 | - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to |
| 756 | be used. If set to ``None``, the default one is used. For more information |
| 757 | on LP solvers and which default solver is used, see the method |
| 758 | :meth:`solve<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the |
| 759 | class |
| 760 | :class:`MixedIntegerLinearProgram<sage.numerical.mip.MixedIntegerLinearProgram>`. |
| 761 | |
| 762 | - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set |
| 763 | to 0 by default, which means quiet. |
| 764 | |
| 765 | OUTPUT: |
| 766 | |
| 767 | A pair ``(cost, ordering)`` representing the optimal ordering of the |
| 768 | vertices and its cost. |
| 769 | |
| 770 | EXAMPLE: |
| 771 | |
| 772 | Vertex separation of a De Bruijn digraph:: |
| 773 | |
| 774 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 775 | sage: G = digraphs.DeBruijn(2,3) |
| 776 | sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs |
| 777 | 2 |
| 778 | sage: vs == vertex_separation.width_of_path_decomposition(G, L) |
| 779 | True |
| 780 | sage: vse, Le = vertex_separation.vertex_separation(G); vse |
| 781 | 2 |
| 782 | |
| 783 | The vertex separation of a circuit is 1:: |
| 784 | |
| 785 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 786 | sage: G = digraphs.Circuit(6) |
| 787 | sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs |
| 788 | 1 |
| 789 | |
| 790 | TESTS: |
| 791 | |
| 792 | Comparison with exponential algorithm:: |
| 793 | |
| 794 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 795 | sage: for i in range(20): |
| 796 | ... G = digraphs.RandomDirectedGNP(10,0.2) |
| 797 | ... ve,le = vertex_separation.vertex_separation(G) |
| 798 | ... vm,lm = vertex_separation.vertex_separation_MILP(G) |
| 799 | ... if ve != vm: |
| 800 | ... print "The solution is not optimal!" |
| 801 | |
| 802 | Giving anything else than a DiGraph:: |
| 803 | |
| 804 | sage: from sage.graphs.graph_decompositions import vertex_separation |
| 805 | sage: vertex_separation.vertex_separation_MILP([]) |
| 806 | Traceback (most recent call last): |
| 807 | ... |
| 808 | ValueError: The first input parameter must be a DiGraph. |
| 809 | """ |
| 810 | from sage.graphs.digraph import DiGraph |
| 811 | if not isinstance(G, DiGraph): |
| 812 | raise ValueError("The first input parameter must be a DiGraph.") |
| 813 | |
| 814 | from sage.numerical.mip import MixedIntegerLinearProgram, Sum, MIPSolverException |
| 815 | p = MixedIntegerLinearProgram( maximization = False, solver = solver ) |
| 816 | |
| 817 | # Declaration of variables. |
| 818 | x = p.new_variable( integer = integrality, dim = 2 ) |
| 819 | u = p.new_variable( integer = integrality, dim = 2 ) |
| 820 | y = p.new_variable( integer = True, dim = 2 ) |
| 821 | z = p.new_variable( integer = True, dim = 1 ) |
| 822 | |
| 823 | N = G.num_verts() |
| 824 | V = G.vertices() |
| 825 | |
| 826 | # (2) x[v][t] <= x[v][t+1] for all v in V, and for t:=0..N-2 |
| 827 | # (3) y[v][t] <= y[v][t+1] for all v in V, and for t:=0..N-2 |
| 828 | for v in V: |
| 829 | for t in xrange(N-1): |
| 830 | p.add_constraint( x[v][t] - x[v][t+1], max = 0 ) |
| 831 | p.add_constraint( y[v][t] - y[v][t+1], max = 0 ) |
| 832 | |
| 833 | # (4) y[v][t] <= x[w][t] for all v in V, for all w in N^+(v), and for all t:=0..N-1 |
| 834 | for v in V: |
| 835 | for w in G.neighbors_out(v): |
| 836 | for t in xrange(N): |
| 837 | p.add_constraint( y[v][t] - x[w][t], max = 0 ) |
| 838 | |
| 839 | # (5) sum_{v in V} y[v][0] <= 1 |
| 840 | p.add_constraint( Sum([ y[v][0] for v in V ]), max = 1 ) |
| 841 | |
| 842 | # (6) sum_{v in V} y[v][t+1] <= sum_{v in V} y[v][t] + 1 for t:=0..N-2 |
| 843 | for t in xrange(N-1): |
| 844 | p.add_constraint( Sum([ y[v][t+1] - y[v][t] for v in V ]), max = 1 ) |
| 845 | |
| 846 | # (7) sum_{v in V} y[v][N-1] = N |
| 847 | p.add_constraint( Sum([ y[v][N-1] for v in V ]), min = N ) |
| 848 | p.add_constraint( Sum([ y[v][N-1] for v in V ]), max = N ) |
| 849 | |
| 850 | # (8) u[v][t] >= x[v][t]-y[v][t] for all v in V, and for all t:=0..N-1 |
| 851 | for v in V: |
| 852 | for t in xrange(N): |
| 853 | p.add_constraint( x[v][t] - y[v][t] - u[v][t], max = 0 ) |
| 854 | |
| 855 | # (9) z >= sum_{v in V} u[v][t] for all t:=0..N-1 |
| 856 | for t in xrange(N): |
| 857 | p.add_constraint( Sum([ u[v][t] for v in V ]) - z['z'], max = 0 ) |
| 858 | |
| 859 | # (10) 0 <= x[v][t] and u[v][t] <= 1 |
| 860 | # (11) y[v][t] in {0,1} |
| 861 | for v in V: |
| 862 | for t in xrange(N): |
| 863 | p.add_constraint( x[v][t], min = 0 ) |
| 864 | p.add_constraint( x[v][t], max = 1 ) |
| 865 | p.add_constraint( u[v][t], min = 0 ) |
| 866 | p.add_constraint( u[v][t], max = 1 ) |
| 867 | p.set_binary( y[v][t] ) |
| 868 | |
| 869 | # (12) 0 <= z <= |V| |
| 870 | p.add_constraint( z['z'], min = 0 ) |
| 871 | p.add_constraint( z['z'], max = N ) |
| 872 | |
| 873 | # (1) Minimize z |
| 874 | p.set_objective( z['z'] ) |
| 875 | |
| 876 | try: |
| 877 | obj = p.solve( log=verbosity ) |
| 878 | |
| 879 | taby = p.get_values( y ) |
| 880 | tabz = p.get_values( z ) |
| 881 | # since exactly one vertex is processed per step, we can reconstruct the sequence |
| 882 | seq = [] |
| 883 | for t in xrange(N): |
| 884 | for v in V: |
| 885 | if (taby[v][t] > 0) and (not v in seq): |
| 886 | seq.append(v) |
| 887 | break |
| 888 | vs = int(round( tabz['z'] )) |
| 889 | |
| 890 | except MIPSolverException: |
| 891 | if integrality: |
| 892 | raise ValueError("Unbounded or unexpected error") |
| 893 | else: |
| 894 | raise ValueError("Unbounded or unexpected error. Try with 'integrality = True'.") |
| 895 | |
| 896 | del p |
| 897 | return vs, seq |
| 898 | |