@@ -2920,6 +2920,251 @@ 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 x `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+ else :
3077+ pewveo .extend (certif )
3078+ return True , pewveo
3079+ else :
3080+ return all (gg .is_chordal_bipartite () for gg in
3081+ self .connected_components_subgraphs ())
3082+
3083+ left = [v for v , c in bipartite_certificate .items () if c == 0 ]
3084+ right = [v for v , c in bipartite_certificate .items () if c == 1 ]
3085+ order_left = len (left )
3086+ order_right = len (right )
3087+
3088+ # We set |left| > |right| for optimization, i.e. time complexity of
3089+ # doubly lexical ordering algorithm is O(nm^2) for a n x m matrix.
3090+ if order_left < order_right :
3091+ left , right = right , left
3092+ order_left , order_right = order_right , order_left
3093+
3094+ # create a reduced_adjacency_matrix
3095+ from sage .rings .finite_rings .finite_field_prime_modn import FiniteField_prime_modn
3096+ A = self .adjacency_matrix (vertices = left + right , base_ring = FiniteField_prime_modn (2 ))
3097+ B = A [range (order_left ), range (order_left , order_left + order_right )]
3098+
3099+ # get doubly lexical ordering of reduced_adjacency_matrix
3100+ row_ordering , col_ordering = B .doubly_lexical_ordering (inplace = True )
3101+
3102+ # determine if B is Gamma-free or not
3103+ is_Gamma_free , Gamma_submatrix_indices = B .is_Gamma_free (certificate = True )
3104+
3105+ if not certificate :
3106+ return is_Gamma_free
3107+
3108+ row_ordering_dict = {k - 1 : v - 1 for k , v in row_ordering .dict ().items ()}
3109+ col_ordering_dict = {k - 1 : v - 1 for k , v in col_ordering .dict ().items ()}
3110+ row_vertices = [left [row_ordering_dict [i ]] for i in range (order_left )]
3111+ col_vertices = [right [col_ordering_dict [i ]] for i in range (order_right )]
3112+
3113+ if is_Gamma_free :
3114+ pewveo = dict ()
3115+ order = 0
3116+ for i in range (B .nrows ()):
3117+ for j in range (B .ncols ()):
3118+ if B [i ][j ] == 1 :
3119+ v_i = row_vertices [i ]
3120+ v_j = col_vertices [j ]
3121+ pewveo [(v_i , v_j )] = order
3122+ pewveo [(v_j , v_i )] = order
3123+ order += 1
3124+ edges = self .edges (sort = True , key = lambda x : pewveo [(x [0 ], x [1 ])])
3125+ return True , edges
3126+
3127+ # Find a chordless cycle of length at least 6
3128+ r1 , c1 , r2 , c2 = Gamma_submatrix_indices
3129+ row_indices = [r1 , r2 ]
3130+ col_indices = [c1 , c2 ]
3131+ while B [row_indices [- 1 ]][col_indices [- 1 ]] == 0 :
3132+ # find the rightmost column with different value
3133+ # in row_indices[-2] and row_indices[-1]
3134+ col = order_right - 1
3135+ while col > col_indices [- 1 ]:
3136+ if B [row_indices [- 2 ]][col ] == 0 and B [row_indices [- 1 ]][col ] == 1 :
3137+ break
3138+ col -= 1
3139+ assert col > col_indices [- 1 ]
3140+
3141+ # find the bottommost row with different value
3142+ # in col_indices[-2] and col_indices[-1]
3143+ row = order_left - 1
3144+ while row > row_indices [- 1 ]:
3145+ if B [row ][col_indices [- 2 ]] == 0 and B [row ][col_indices [- 1 ]] == 1 :
3146+ break
3147+ row -= 1
3148+ assert row > row_indices [- 1 ]
3149+
3150+ col_indices .append (col )
3151+ row_indices .append (row )
3152+
3153+ l = len (row_indices )
3154+ cycle = []
3155+ for i in range (l ):
3156+ if i % 2 == 0 :
3157+ cycle .append (row_vertices [row_indices [i ]])
3158+ else :
3159+ cycle .append (col_vertices [col_indices [i ]])
3160+ for i in reversed (range (l )):
3161+ if i % 2 == 0 :
3162+ cycle .append (col_vertices [col_indices [i ]])
3163+ else :
3164+ cycle .append (row_vertices [row_indices [i ]])
3165+
3166+ return False , cycle
3167+
29233168 @doc_index ("Connectivity, orientations, trees" )
29243169 def degree_constrained_subgraph (self , bounds , solver = None , verbose = 0 ,
29253170 * , integrality_tolerance = 1e-3 ):
0 commit comments