| | 656 | def linear_arboricity(g, hex_colors=False, value_only=False, k=1, **kwds): |
| | 657 | r""" |
| | 658 | Computes the linear arboricity of the given graph. |
| | 659 | |
| | 660 | The linear arboricity of a graph `G` is the least |
| | 661 | number `la(G)` such that the edges of `G` can be |
| | 662 | partitioned into linear forests (i.e. into forests |
| | 663 | of paths). |
| | 664 | |
| | 665 | Obviously, `la(G)\geq \lceil \frac {\Delta(G)} 2 \rceil`. |
| | 666 | |
| | 667 | It is conjectured in [Aki80]_ that |
| | 668 | `la(G)\leq \lceil \frac {\Delta(G)+1} 2 \rceil`. |
| | 669 | |
| | 670 | INPUT: |
| | 671 | |
| | 672 | - ``hex_colors`` (boolean) |
| | 673 | |
| | 674 | - If ``hex_colors = True``, the function returns a |
| | 675 | dictionary associating to each color a list |
| | 676 | of edges (meant as an argument to the ``edge_colors`` |
| | 677 | keyword of the ``plot`` method). |
| | 678 | |
| | 679 | - If ``hex_colors = False`` (default value), returns |
| | 680 | a list of graphs corresponding to each color class. |
| | 681 | |
| | 682 | - ``value_only`` (boolean) |
| | 683 | |
| | 684 | - If ``value_only = True``, only returns the linear |
| | 685 | arboricity as an integer value. |
| | 686 | |
| | 687 | - If ``value_only = False``, returns the color classes |
| | 688 | according to the value of ``hex_colors`` |
| | 689 | |
| | 690 | - ``k`` (integer) -- the number of colors to use. |
| | 691 | |
| | 692 | - If ``0``, computes a decomposition of `G` into |
| | 693 | `\lceil \frac {\Delta(G)} 2 \rceil` |
| | 694 | forests of paths |
| | 695 | |
| | 696 | - If ``1`` (default), computes a decomposition of `G` into |
| | 697 | `\lceil \frac {\Delta(G)+1} 2 \rceil` colors, |
| | 698 | which is the conjectured general bound. |
| | 699 | |
| | 700 | - If ``k=None``, computes a decomposition using the |
| | 701 | least possible number of colors. |
| | 702 | |
| | 703 | - ``**kwds`` -- arguments to be passed down to the ``solve`` |
| | 704 | function of ``MixedIntegerLinearProgram``. See the documentation |
| | 705 | of ``MixedIntegerLinearProgram.solve`` for more informations. |
| | 706 | |
| | 707 | |
| | 708 | ALGORITHM: |
| | 709 | |
| | 710 | Linear Programming |
| | 711 | |
| | 712 | COMPLEXITY: |
| | 713 | |
| | 714 | NP-Hard |
| | 715 | |
| | 716 | EXAMPLE: |
| | 717 | |
| | 718 | Obviously, a square grid has a linear arboricity of 2, as |
| | 719 | the set of horizontal lines and the set of vertical lines |
| | 720 | are an admissible partition:: |
| | 721 | |
| | 722 | sage: from sage.graphs.graph_coloring import linear_arboricity |
| | 723 | sage: g = graphs.GridGraph([4,4]) # optional - requires GLPK CPLEX or CBC |
| | 724 | sage: g1,g2 = linear_arboricity(g, k=0) # optional - requires GLPK CPLEX or CBC |
| | 725 | |
| | 726 | Each graph is of course a forest:: |
| | 727 | |
| | 728 | sage: g1.is_forest() and g2.is_forest() # optional - requires GLPK CPLEX or CBC |
| | 729 | True |
| | 730 | |
| | 731 | Of maximum degree 2:: |
| | 732 | |
| | 733 | sage: max(g1.degree()) <= 2 and max(g2.degree()) <= 2 # optional - requires GLPK CPLEX or CBC |
| | 734 | True |
| | 735 | |
| | 736 | Which constitutes a partition of the whole edge set:: |
| | 737 | |
| | 738 | sage: all([g1.has_edge(e) or g2.has_edge(e) for e in g.edges()]) # optional - requires GLPK CPLEX or CBC |
| | 739 | True |
| | 740 | |
| | 741 | REFERENCES: |
| | 742 | |
| | 743 | .. [Aki80] Akiyama, J. and Exoo, G. and Harary, F. |
| | 744 | Covering and packing in graphs. III: Cyclic and acyclic invariants |
| | 745 | Mathematical Institute of the Slovak Academy of Sciences |
| | 746 | Mathematica Slovaca vol30, n4, pages 405--417, 1980 |
| | 747 | """ |
| | 748 | |
| | 749 | from sage.rings.integer import Integer |
| | 750 | |
| | 751 | if k is None: |
| | 752 | try: |
| | 753 | return linear_arboricity(g, value_only = value_only,hex_colors = hex_colors, k = (Integer(max(g.degree()))/2).ceil() ) |
| | 754 | except ValueError: |
| | 755 | return linear_arboricity(g, value_only = value_only,hex_colors = hex_colors, k = 0) |
| | 756 | elif k==1: |
| | 757 | k = (Integer(1+max(g.degree()))/2).ceil() |
| | 758 | elif k==0: |
| | 759 | k = (Integer(max(g.degree()))/2).ceil() |
| | 760 | |
| | 761 | from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException |
| | 762 | from sage.plot.colors import rainbow |
| | 763 | |
| | 764 | p = MixedIntegerLinearProgram() |
| | 765 | |
| | 766 | |
| | 767 | # c is a boolean value such that c[i][(u,v)] = 1 if and only if (u,v) is colored with i |
| | 768 | c = p.new_variable(dim=2) |
| | 769 | |
| | 770 | # relaxed value |
| | 771 | r = p.new_variable(dim=2) |
| | 772 | |
| | 773 | E = lambda x,y : (x,y) if x<y else (y,x) |
| | 774 | |
| | 775 | MAD = 1-1/(Integer(g.order())*2) |
| | 776 | |
| | 777 | # Partition of the edges |
| | 778 | for u,v in g.edges(labels=None): |
| | 779 | p.add_constraint(sum([c[i][E(u,v)] for i in range(k)]), max=1, min=1) |
| | 780 | |
| | 781 | for i in range(k): |
| | 782 | |
| | 783 | # r greater than c |
| | 784 | for u,v in g.edges(labels=None): |
| | 785 | p.add_constraint(r[i][(u,v)] + r[i][(v,u)] - c[i][E(u,v)], max=0, min=0) |
| | 786 | |
| | 787 | |
| | 788 | # Maximum degree 2 |
| | 789 | for u in g.vertices(): |
| | 790 | p.add_constraint(sum([c[i][E(u,v)] for v in g.neighbors(u)]),max = 2) |
| | 791 | |
| | 792 | # no cycles |
| | 793 | p.add_constraint(sum([r[i][(u,v)] for v in g.neighbors(u)]),max = MAD) |
| | 794 | |
| | 795 | |
| | 796 | p.set_objective(None) |
| | 797 | p.set_binary(c) |
| | 798 | |
| | 799 | try: |
| | 800 | if value_only: |
| | 801 | return p.solve(objective_only = True) |
| | 802 | else: |
| | 803 | p.solve() |
| | 804 | |
| | 805 | except MIPSolverException: |
| | 806 | if k == (Integer(max(g.degree()))/2).ceil(): |
| | 807 | raise Exception("It looks like you have found a counterexample to a very old conjecture. Please do not loose it ! Please publish it, and send a post to sage-devel to warn us. I implore you ! Nathann Cohen ") |
| | 808 | else: |
| | 809 | raise ValueError("This graph can not be colored with the given number of colors.") |
| | 810 | |
| | 811 | c = p.get_values(c) |
| | 812 | |
| | 813 | if hex_colors: |
| | 814 | answer = [[] for i in range(k)] |
| | 815 | add = lambda (u,v),i : answer[i].append((u,v)) |
| | 816 | else: |
| | 817 | gg = g.copy() |
| | 818 | gg.delete_edges(g.edges()) |
| | 819 | answer = [gg.copy() for i in range(k)] |
| | 820 | add = lambda (u,v),i : answer[i].add_edge((u,v)) |
| | 821 | |
| | 822 | for i in range(k): |
| | 823 | for u,v in g.edges(labels=None): |
| | 824 | if c[i][E(u,v)] == 1: |
| | 825 | add((u,v),i) |
| | 826 | |
| | 827 | if hex_colors: |
| | 828 | return dict(zip(rainbow(len(classes)),classes)) |
| | 829 | else: |
| | 830 | return answer |
| | 831 | |
| | 832 | def acyclic_edge_coloring(g, hex_colors=False, value_only=False, k=0, **kwds): |
| | 833 | r""" |
| | 834 | Computes an acyclic edge coloring of the current graph. |
| | 835 | |
| | 836 | An edge coloring of a graph is a assignment of colors |
| | 837 | to the edges of a graph such that : |
| | 838 | |
| | 839 | - the coloring is proper (no adjacent edges share a |
| | 840 | color) |
| | 841 | - For any two colors `i,j`, the union of the edges |
| | 842 | colored with `i` or `j` is a forest. |
| | 843 | |
| | 844 | The least number of colors such that such a coloring |
| | 845 | exists for a graph `G` is written `\chi'_a(G)`, also |
| | 846 | called the acyclic chromatic index of `G`. |
| | 847 | |
| | 848 | It is conjectured that this parameter can not be too different |
| | 849 | from the obvious lower bound `\Delta(G)\leq \chi'_a(G)`, |
| | 850 | `\Delta(G)` being the maximum degree of `G`, which is given |
| | 851 | by the first of the two constraints. Indeed, it is conjectured |
| | 852 | that `\Delta(G)\leq \chi'_a(G) \leq \Delta(G) + 2`. |
| | 853 | |
| | 854 | INPUT: |
| | 855 | |
| | 856 | - ``hex_colors`` (boolean) |
| | 857 | |
| | 858 | - If ``hex_colors = True``, the function returns a |
| | 859 | dictionary associating to each color a list |
| | 860 | of edges (meant as an argument to the ``edge_colors`` |
| | 861 | keyword of the ``plot`` method). |
| | 862 | |
| | 863 | - If ``hex_colors = False`` (default value), returns |
| | 864 | a list of graphs corresponding to each color class. |
| | 865 | |
| | 866 | - ``value_only`` (boolean) |
| | 867 | |
| | 868 | - If ``value_only = True``, only returns the acyclic |
| | 869 | chromatic index as an integer value |
| | 870 | |
| | 871 | - If ``value_only = False``, returns the color classes |
| | 872 | according to the value of ``hex_colors`` |
| | 873 | |
| | 874 | - ``k`` (integer) -- the number of colors to use. |
| | 875 | |
| | 876 | - If ``k>0``, computes an acyclic edge coloring using |
| | 877 | `k` colors. |
| | 878 | |
| | 879 | - If ``k=0`` (default), computes a coloring of `G` into |
| | 880 | `\Delta(G) + 2` colors, |
| | 881 | which is the conjectured general bound. |
| | 882 | |
| | 883 | - If ``k=None``, computes a decomposition using the |
| | 884 | least possible number of colors. |
| | 885 | |
| | 886 | - ``**kwds`` -- arguments to be passed down to the ``solve`` |
| | 887 | function of ``MixedIntegerLinearProgram``. See the documentation |
| | 888 | of ``MixedIntegerLinearProgram.solve`` for more informations. |
| | 889 | |
| | 890 | ALGORITHM: |
| | 891 | |
| | 892 | Linear Programming |
| | 893 | |
| | 894 | EXAMPLE: |
| | 895 | |
| | 896 | The complete graph on 8 vertices can not be acyclically |
| | 897 | edge-colored with less `\Delta+1` colors, but it can be |
| | 898 | colored with `\Delta+2=9`:: |
| | 899 | |
| | 900 | sage: from sage.graphs.graph_coloring import acyclic_edge_coloring |
| | 901 | sage: g = graphs.CompleteGraph(8) |
| | 902 | sage: colors = acyclic_edge_coloring(g) # optional - requires GLPK CPLEX or CBC |
| | 903 | |
| | 904 | Each color class is of course a matching :: |
| | 905 | |
| | 906 | sage: all([max(gg.degree())<=1 for gg in colors]) # optional - requires GLPK CPLEX or CBC |
| | 907 | True |
| | 908 | |
| | 909 | These matchings being a partition of the edge set:: |
| | 910 | |
| | 911 | sage: all([ any([gg.has_edge(e) for gg in colors]) for e in g.edges()]) # optional - requires GLPK CPLEX or CBC |
| | 912 | True |
| | 913 | |
| | 914 | Besides, the union of any two of them is a forest :: |
| | 915 | |
| | 916 | sage: all([g1.union(g2).is_forest() for g1 in colors for g2 in colors]) # optional - requires GLPK CPLEX or CBC |
| | 917 | True |
| | 918 | |
| | 919 | If one wants to acyclically color a cycle on `4` vertices, |
| | 920 | at least 3 colors will be necessary. The function raises |
| | 921 | an exception when asked to color it with only 2:: |
| | 922 | |
| | 923 | sage: g = graphs.CycleGraph(4) |
| | 924 | sage: acyclic_edge_coloring(g, k=2) # optional - requires GLPK CPLEX or CBC |
| | 925 | Traceback (most recent call last): |
| | 926 | ... |
| | 927 | ValueError: This graph can not be colored with the given number of colors. |
| | 928 | |
| | 929 | The optimal coloring give us `3` classes:: |
| | 930 | |
| | 931 | sage: colors = acyclic_edge_coloring(g, k=None) # optional - requires GLPK CPLEX or CBC |
| | 932 | sage: len(colors) # optional - requires GLPK CPLEX or CBC |
| | 933 | 3 |
| | 934 | |
| | 935 | """ |
| | 936 | |
| | 937 | from sage.rings.integer import Integer |
| | 938 | from sage.combinat.subset import Subsets |
| | 939 | |
| | 940 | if k is None: |
| | 941 | k = max(g.degree()) |
| | 942 | |
| | 943 | while True: |
| | 944 | try: |
| | 945 | return acyclic_edge_coloring(g, value_only = value_only,hex_colors = hex_colors, k = k) |
| | 946 | except ValueError: |
| | 947 | k = k+1 |
| | 948 | |
| | 949 | raise Exception("This should not happen. Please report a bug !") |
| | 950 | |
| | 951 | elif k==0: |
| | 952 | k = max(g.degree())+2 |
| | 953 | |
| | 954 | from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException |
| | 955 | from sage.plot.colors import rainbow |
| | 956 | |
| | 957 | p = MixedIntegerLinearProgram() |
| | 958 | |
| | 959 | # c is a boolean value such that c[i][(u,v)] = 1 if and only if (u,v) is colored with i |
| | 960 | c = p.new_variable(dim=2) |
| | 961 | |
| | 962 | # relaxed value |
| | 963 | r = p.new_variable(dim=2) |
| | 964 | |
| | 965 | E = lambda x,y : (x,y) if x<y else (y,x) |
| | 966 | |
| | 967 | MAD = 1-1/(Integer(g.order())*2) |
| | 968 | |
| | 969 | # Partition of the edges |
| | 970 | for u,v in g.edges(labels=None): |
| | 971 | p.add_constraint(sum([c[i][E(u,v)] for i in range(k)]), max=1, min=1) |
| | 972 | |
| | 973 | |
| | 974 | for i in range(k): |
| | 975 | |
| | 976 | # Maximum degree 1 |
| | 977 | for u in g.vertices(): |
| | 978 | p.add_constraint(sum([c[i][E(u,v)] for v in g.neighbors(u)]),max = 1) |
| | 979 | |
| | 980 | for i,j in Subsets(range(k),2): |
| | 981 | # r is greater than c |
| | 982 | for u in g.vertices(): |
| | 983 | p.add_constraint(sum([r[(i,j)][(u,v)] for v in g.neighbors(u)]),max = MAD) |
| | 984 | |
| | 985 | # r greater than c |
| | 986 | for u,v in g.edges(labels=None): |
| | 987 | p.add_constraint(r[(i,j)][(u,v)] + r[(i,j)][(v,u)] - c[i][E(u,v)] - c[j][E(u,v)], max=0, min=0) |
| | 988 | |
| | 989 | p.set_objective(None) |
| | 990 | p.set_binary(c) |
| | 991 | |
| | 992 | try: |
| | 993 | if value_only: |
| | 994 | return p.solve(objective_only = True, **kwds) |
| | 995 | else: |
| | 996 | p.solve(**kwds) |
| | 997 | |
| | 998 | except MIPSolverException: |
| | 999 | if k == max(g.degree()) + 2: |
| | 1000 | raise Exception("It looks like you have found a counterexample to a very old conjecture. Please do not loose it ! Please publish it, and send a post to sage-devel to warn us. I implore you ! Nathann Cohen ") |
| | 1001 | else: |
| | 1002 | raise ValueError("This graph can not be colored with the given number of colors.") |
| | 1003 | |
| | 1004 | c = p.get_values(c) |
| | 1005 | |
| | 1006 | if hex_colors: |
| | 1007 | answer = [[] for i in range(k)] |
| | 1008 | add = lambda (u,v),i : answer[i].append((u,v)) |
| | 1009 | else: |
| | 1010 | gg = g.copy() |
| | 1011 | gg.delete_edges(g.edges()) |
| | 1012 | answer = [gg.copy() for i in range(k)] |
| | 1013 | add = lambda (u,v),i : answer[i].add_edge((u,v)) |
| | 1014 | |
| | 1015 | for i in range(k): |
| | 1016 | for u,v in g.edges(labels=None): |
| | 1017 | if c[i][E(u,v)] == 1: |
| | 1018 | add((u,v),i) |
| | 1019 | |
| | 1020 | if hex_colors: |
| | 1021 | return dict(zip(rainbow(len(classes)),classes)) |
| | 1022 | else: |
| | 1023 | return answer |
| | 1024 | |
| | 1025 | |