@@ -2920,6 +2920,247 @@ def is_path(self):
2920
2920
return False
2921
2921
return deg_one_counter == 2 and seen_counter == order
2922
2922
2923
+ @doc_index ("Graph properties" )
2924
+ def is_chordal_bipartite (self , certificate = False ):
2925
+ r"""
2926
+ Check whether the given graph is chordal bipartite.
2927
+
2928
+ A graph `G` is chordal bipartite if it is bipartite and has no induced
2929
+ cycle of length at least 6.
2930
+
2931
+ An edge `(x, y) \in E` is bisimplical if `N(x) \cup N(y)` induces a
2932
+ complete bipartite subgraph of `G`.
2933
+
2934
+ A Perfect Edge Without Vertex Elimination Ordering of a bipartite graph
2935
+ `G = (X, Y, E)` is an ordering `e_1,...,e_m` of its edge set such that
2936
+ for all `1 \leq i \leq m`, `e_i` is a bisimplical edge of
2937
+ `G_i = (X, Y, E_i)` where `E_i` consists of the edges `e_i,e_{i+1}...,e_m`.
2938
+
2939
+ A graph `G` is chordal bipartite if and only if it has a Perfect Edge
2940
+ Without Vertex Elimination Ordering. See Lemma 4 in [KKD1995]_.
2941
+
2942
+ INPUT:
2943
+
2944
+ - ``certificate`` -- boolean (default: ``False``); whether to return a
2945
+ certificate
2946
+
2947
+ OUTPUT:
2948
+
2949
+ When ``certificate`` is set to ``False`` (default) this method only
2950
+ returns ``True`` or ``False`` answers. When ``certificate`` is set to
2951
+ ``True``, the method either returns:
2952
+
2953
+ * ``(True, pewveo)`` when the graph is chordal bipartite, where ``pewveo``
2954
+ is a Perfect Edge Without Vertex Elimination Ordering of edges.
2955
+
2956
+ * ``(False, cycle)`` when the graph is not chordal bipartite, where
2957
+ ``cycle`` is an odd cycle or a chordless cycle of length at least 6.
2958
+
2959
+ ALGORITHM:
2960
+
2961
+ This algorithm is based on these facts. The first one is trivial.
2962
+
2963
+ * A reduced adjacnecy matrix
2964
+ (:meth:`~sage.graphs.bipartite_graph.BipartiteGraph.reduced_adjacency_matrix`)
2965
+ of a bipartite graph has no cycle submatrix if and only if the graph is
2966
+ chordal bipartite, where cycle submatrix is 0-1 `n \times n` matrix `n \geq 3`
2967
+ with exactly two 1's in each row and column and no proper submatrix satsify
2968
+ this property.
2969
+
2970
+ * A doubly lexical ordering
2971
+ (:meth:`~sage.matrix.matrix_mod2_dense.Matrix_mod2_dense.doubly_lexical_ordering`)
2972
+ of a 0-1 matrix is `\Gamma`-free
2973
+ (:meth:`~sage.matrix.matrix_mod2_dense.Matrix_mod2_dense.is_Gamma_free`) if and
2974
+ only if the matrix has no cycle submatrix. See Theorem 5.4 in [Lub1987]_.
2975
+
2976
+ Hence, checking a doubly lexical ordering of a reduced adjacency matrix
2977
+ of a bipartite graph is `\Gamma`-free leads to detecting the graph
2978
+ is chordal bipartite. Also, this matrix contains a certificate. Hence,
2979
+ if `G` is chordal bipartite, we find a Perfect Edge Without Vertex
2980
+ Elimination Ordering of edges by Lemma 10 in [KKD1995]_.
2981
+ Otherwise, we can find a cycle submatrix by Theorem 5.2 in [Lub1987]_.
2982
+ The time complexity of this algorithm is `O(n^3)`.
2983
+
2984
+ EXAMPLES:
2985
+
2986
+ A non-bipartite graph is not chordal bipartite::
2987
+
2988
+ sage: g = graphs.CycleGraph(5)
2989
+ sage: g.is_chordal_bipartite()
2990
+ False
2991
+ sage: _, cycle = g.is_chordal_bipartite(certificate=True)
2992
+ sage: len(cycle) % 2 == 1
2993
+ True
2994
+
2995
+ A 6-cycle graph is not chordal bipartite::
2996
+
2997
+ sage: g = graphs.CycleGraph(6)
2998
+ sage: g.is_chordal_bipartite()
2999
+ False
3000
+ sage: _, cycle = g.is_chordal_bipartite(certificate=True)
3001
+ sage: len(cycle) == 6
3002
+ True
3003
+
3004
+ A `2 \times n` grid graph is chordal bipartite::
3005
+
3006
+ sage: g = graphs.Grid2dGraph(2, 6)
3007
+ sage: result, pewveo = g.is_chordal_bipartite(certificate=True)
3008
+ sage: result
3009
+ True
3010
+
3011
+ Let us check the certificate given by Sage is indeed a perfect
3012
+ edge without vertex elimination ordering::
3013
+
3014
+ sage: for e in pewveo:
3015
+ ....: a = g.subgraph(vertices=g.neighbors(e[0]) + g.neighbors(e[1]))
3016
+ ....: b = BipartiteGraph(a).complement_bipartite()
3017
+ ....: if b.edges():
3018
+ ....: raise ValueError("this should never happen")
3019
+ ....: g.delete_edge(e)
3020
+
3021
+ Let us check the certificate given by Sage is indeed a
3022
+ chordless cycle of length at least 6::
3023
+
3024
+ sage: g = graphs.Grid2dGraph(3, 6)
3025
+ sage: result, cycle = g.is_chordal_bipartite(certificate=True)
3026
+ sage: result
3027
+ False
3028
+ sage: l = len(cycle); l >= 6
3029
+ True
3030
+ sage: for i in range(len(cycle)):
3031
+ ....: if not g.has_edge(cycle[i], cycle[(i+1)%l]):
3032
+ ....: raise ValueError("this should never happen")
3033
+ sage: h = g.subgraph(vertices=cycle)
3034
+ sage: h.is_cycle()
3035
+ True
3036
+
3037
+ TESTS:
3038
+
3039
+ The algorithm works correctly for disconnected graphs::
3040
+
3041
+ sage: c4 = graphs.CycleGraph(4)
3042
+ sage: g = c4.disjoint_union(graphs.CycleGraph(6))
3043
+ sage: g.is_chordal_bipartite()
3044
+ False
3045
+ sage: _, cycle = g.is_chordal_bipartite(certificate=True)
3046
+ sage: len(cycle) == 6
3047
+ True
3048
+ sage: g = c4.disjoint_union(graphs.Grid2dGraph(2, 6))
3049
+ sage: g.is_chordal_bipartite()
3050
+ True
3051
+ sage: _, pewveo = g.is_chordal_bipartite(certificate=True)
3052
+ sage: for e in pewveo:
3053
+ ....: a = g.subgraph(vertices=g.neighbors(e[0]) + g.neighbors(e[1]))
3054
+ ....: b = BipartiteGraph(a).complement_bipartite()
3055
+ ....: if b.edges():
3056
+ ....: raise ValueError("this should never happen")
3057
+ ....: g.delete_edge(e)
3058
+ """
3059
+ self ._scream_if_not_simple ()
3060
+ is_bipartite , bipartite_certificate = self .is_bipartite (certificate = True )
3061
+ if not is_bipartite :
3062
+ return False if not certificate else (False , bipartite_certificate )
3063
+
3064
+ # If the graph is not connected, we are computing the result on each
3065
+ # component
3066
+ if not self .is_connected ():
3067
+ # If the user wants a certificate, we had no choice but to collect
3068
+ # the Perfect Edge Without Vertex Elimination Ordering. But we
3069
+ # return a cycle certificate immediately if we find any.
3070
+ if certificate :
3071
+ pewveo = []
3072
+ for gg in self .connected_components_subgraphs ():
3073
+ b , certif = gg .is_chordal_bipartite (certificate = True )
3074
+ if not b :
3075
+ return False , certif
3076
+ pewveo .extend (certif )
3077
+ return True , pewveo
3078
+ return all (gg .is_chordal_bipartite () for gg in
3079
+ self .connected_components_subgraphs ())
3080
+
3081
+ left = [v for v , c in bipartite_certificate .items () if c == 0 ]
3082
+ right = [v for v , c in bipartite_certificate .items () if c == 1 ]
3083
+ order_left = len (left )
3084
+ order_right = len (right )
3085
+
3086
+ # We set |left| > |right| for optimization, i.e. time complexity of
3087
+ # doubly lexical ordering algorithm is O(nm^2) for a n x m matrix.
3088
+ if order_left < order_right :
3089
+ left , right = right , left
3090
+ order_left , order_right = order_right , order_left
3091
+
3092
+ # create a reduced_adjacency_matrix
3093
+ from sage .rings .finite_rings .finite_field_prime_modn import FiniteField_prime_modn
3094
+ A = self .adjacency_matrix (vertices = left + right , base_ring = FiniteField_prime_modn (2 ))
3095
+ B = A [range (order_left ), range (order_left , order_left + order_right )]
3096
+
3097
+ # get doubly lexical ordering of reduced_adjacency_matrix
3098
+ row_ordering , col_ordering = B .doubly_lexical_ordering (inplace = True )
3099
+
3100
+ # determine if B is Gamma-free or not
3101
+ is_Gamma_free , Gamma_submatrix_indices = B .is_Gamma_free (certificate = True )
3102
+
3103
+ if not certificate :
3104
+ return is_Gamma_free
3105
+
3106
+ row_ordering_dict = {k - 1 : v - 1 for k , v in row_ordering .dict ().items ()}
3107
+ col_ordering_dict = {k - 1 : v - 1 for k , v in col_ordering .dict ().items ()}
3108
+ row_vertices = [left [row_ordering_dict [i ]] for i in range (order_left )]
3109
+ col_vertices = [right [col_ordering_dict [i ]] for i in range (order_right )]
3110
+
3111
+ if is_Gamma_free :
3112
+ pewveo = dict ()
3113
+ order = 0
3114
+ for i , vi in enumerate (row_vertices ):
3115
+ for j , vj in enumerate (col_vertices ):
3116
+ if B [i , j ] == 1 :
3117
+ pewveo [vi , vj ] = order
3118
+ pewveo [vj , vi ] = order
3119
+ order += 1
3120
+ edges = self .edges (sort = True , key = lambda x : pewveo [x [0 ], x [1 ]])
3121
+ return True , edges
3122
+
3123
+ # Find a chordless cycle of length at least 6
3124
+ r1 , c1 , r2 , c2 = Gamma_submatrix_indices
3125
+ row_indices = [r1 , r2 ]
3126
+ col_indices = [c1 , c2 ]
3127
+ while not B [row_indices [- 1 ], col_indices [- 1 ]]:
3128
+ # find the rightmost column with different value
3129
+ # in row_indices[-2] and row_indices[-1]
3130
+ col = order_right - 1
3131
+ while col > col_indices [- 1 ]:
3132
+ if not B [row_indices [- 2 ], col ] and B [row_indices [- 1 ], col ]:
3133
+ break
3134
+ col -= 1
3135
+ assert col > col_indices [- 1 ]
3136
+
3137
+ # find the bottommost row with different value
3138
+ # in col_indices[-2] and col_indices[-1]
3139
+ row = order_left - 1
3140
+ while row > row_indices [- 1 ]:
3141
+ if not B [row , col_indices [- 2 ]] and B [row , col_indices [- 1 ]]:
3142
+ break
3143
+ row -= 1
3144
+ assert row > row_indices [- 1 ]
3145
+
3146
+ col_indices .append (col )
3147
+ row_indices .append (row )
3148
+
3149
+ l = len (row_indices )
3150
+ cycle = []
3151
+ for i in range (l ):
3152
+ if i % 2 == 0 :
3153
+ cycle .append (row_vertices [row_indices [i ]])
3154
+ else :
3155
+ cycle .append (col_vertices [col_indices [i ]])
3156
+ for i in reversed (range (l )):
3157
+ if i % 2 == 0 :
3158
+ cycle .append (col_vertices [col_indices [i ]])
3159
+ else :
3160
+ cycle .append (row_vertices [row_indices [i ]])
3161
+
3162
+ return False , cycle
3163
+
2923
3164
@doc_index ("Connectivity, orientations, trees" )
2924
3165
def degree_constrained_subgraph (self , bounds , solver = None , verbose = 0 ,
2925
3166
* , integrality_tolerance = 1e-3 ):
0 commit comments