@@ -2920,6 +2920,247 @@ def is_path(self):
29202920 return False
29212921 return deg_one_counter == 2 and seen_counter == order
29222922
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+
29233164 @doc_index ("Connectivity, orientations, trees" )
29243165 def degree_constrained_subgraph (self , bounds , solver = None , verbose = 0 ,
29253166 * , integrality_tolerance = 1e-3 ):
0 commit comments