From 53b0b8761830d802d1ce4058a76a4115e14b628c Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 22 Jul 2023 12:14:46 +0200 Subject: [PATCH 1/3] PR #35969: try to clean the branch --- src/sage/combinat/posets/hasse_diagram.py | 2 +- src/sage/graphs/digraph.py | 105 +++++++++++++++++----- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index 3822d94f2a4..d94e11dd0a0 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -574,7 +574,7 @@ def dual(self): sage: H.is_isomorphic( H.dual() ) # optional - sage.combinat False """ - H = self.reverse() + H = self.reverse(immutable=False) H.relabel(perm=list(range(H.num_verts() - 1, -1, -1)), inplace=True) return HasseDiagram(H) diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index b1d046ee25d..5dc9dd135d9 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -923,8 +923,8 @@ def is_directed(self): # Properties def is_directed_acyclic(self, certificate=False): - """ - Return whether the digraph is acyclic or not. + r""" + Check whether the digraph is acyclic or not. A directed graph is acyclic if for any vertex `v`, there is no directed path that starts and ends at `v`. Every directed acyclic graph (DAG) @@ -943,8 +943,8 @@ def is_directed_acyclic(self, certificate=False): * When ``certificate=True``: * If the graph is acyclic, returns a pair ``(True, ordering)`` where - ``ordering`` is a list of the vertices such that ``u`` appears - before ``v`` in ``ordering`` if ``u, v`` is an edge. + ``ordering`` is a list of the vertices such that `u` appears + before `v` in ``ordering`` if `uv` is an edge. * Else, returns a pair ``(False, cycle)`` where ``cycle`` is a list of vertices representing a circuit in the graph. @@ -1272,8 +1272,7 @@ def in_degree(self, vertices=None, labels=False): return self._backend.in_degree(vertices) elif labels: return {v: d for v, d in self.in_degree_iterator(vertices, labels=labels)} - else: - return list(self.in_degree_iterator(vertices, labels=labels)) + return list(self.in_degree_iterator(vertices, labels=labels)) def in_degree_iterator(self, vertices=None, labels=False): """ @@ -1343,8 +1342,7 @@ def out_degree(self, vertices=None, labels=False): return self._backend.out_degree(vertices) elif labels: return {v: d for v, d in self.out_degree_iterator(vertices, labels=labels)} - else: - return list(self.out_degree_iterator(vertices, labels=labels)) + return list(self.out_degree_iterator(vertices, labels=labels)) def out_degree_iterator(self, vertices=None, labels=False): """ @@ -1631,14 +1629,16 @@ def feedback_edge_set(self, constraint_generation=True, value_only=False, if self.has_loops(): # We solve the problem on a copy without loops of the digraph D = DiGraph(self.edges(sort=False), multiedges=self.allows_multiple_edges(), loops=True) - D.allow_loops(False) + loops = D.loops(labels=None) + D.delete_edges(loops) + D.allow_loops(False, check=False) FAS = D.feedback_edge_set(constraint_generation=constraint_generation, value_only=value_only, solver=solver, verbose=verbose, integrality_tolerance=integrality_tolerance) if value_only: - return FAS + self.number_of_loops() + return FAS + len(loops) else: - return FAS + self.loops(labels=None) + return FAS + loops if not self.is_strongly_connected(): # If the digraph is not strongly connected, we solve the problem on @@ -1647,6 +1647,8 @@ def feedback_edge_set(self, constraint_generation=True, value_only=False, FAS = 0 if value_only else [] for h in self.strongly_connected_components_subgraphs(): + if not h.size(): + continue if value_only: FAS += h.feedback_edge_set(constraint_generation=constraint_generation, value_only=True, solver=solver, verbose=verbose, @@ -1691,9 +1693,8 @@ def feedback_edge_set(self, constraint_generation=True, value_only=False, if isok: if value_only: return sum(1 for e in self.edge_iterator(labels=False) if val[e]) - else: - # listing the edges contained in the MFAS - return [e for e in self.edge_iterator(labels=False) if val[e]] + # listing the edges contained in the MFAS + return [e for e in self.edge_iterator(labels=False) if val[e]] # There is a circuit left. Let's add the corresponding # constraint ! @@ -1736,28 +1737,92 @@ def feedback_edge_set(self, constraint_generation=True, value_only=False, if value_only: return sum(1 for e in self.edge_iterator(labels=False) if b_sol[e]) - else: - return [e for e in self.edge_iterator(labels=False) if b_sol[e]] + return [e for e in self.edge_iterator(labels=False) if b_sol[e]] # Construction - def reverse(self): + def reverse(self, immutable=None): """ Return a copy of digraph with edges reversed in direction. + INPUT: + + - ``immutable`` -- boolean (default: ``None``); whether to return an + immutable digraph or not. By default (``None``), the returned digraph + has the same setting than ``self``. That is, if ``self`` is immutable, + the returned digraph also is. + EXAMPLES:: - sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]}) - sage: D.reverse() + sage: adj = {0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]} + sage: D = DiGraph(adj) + sage: R = D.reverse(); R Reverse of (): Digraph on 6 vertices + sage: H = R.reverse() + sage: adj == H.to_dictionary() + True + + TESTS:: + + sage: adj = {0: [1, 1], 1: [1]} + sage: D = DiGraph(adj, immutable=True, multiedges=True, loops=True) + sage: R = D.reverse() + sage: R.is_immutable() and R.allows_loops() and R.allows_multiple_edges() + True + sage: adj == R.reverse().to_dictionary(multiple_edges=True) + True + + Check the behavior of parameter ``immutable``:: + + sage: D = DiGraph([(0, 1)], immutable=False) + sage: R = D.reverse() + sage: R.is_immutable() + False + sage: R = D.reverse(immutable=True) + sage: R.is_immutable() + True + sage: H = R.reverse() + sage: H.is_immutable() + True + sage: H = R.reverse(immutable=False) + sage: H.is_immutable() + False """ - H = DiGraph(multiedges=self.allows_multiple_edges(), loops=self.allows_loops()) + from sage.graphs.base.dense_graph import DenseGraphBackend + if isinstance(self._backend, DenseGraphBackend): + data_structure = "dense" + else: + data_structure = "sparse" + + H = DiGraph(data_structure=data_structure, + multiedges=self.allows_multiple_edges(), loops=self.allows_loops(), + pos=copy(self._pos), weighted=self.weighted(), + hash_labels=self._hash_labels) H.add_vertices(self) H.add_edges((v, u, d) for u, v, d in self.edge_iterator()) name = self.name() if name is None: name = '' H.name("Reverse of (%s)" % name) + + attributes_to_copy = ('_assoc', '_embedding') + for attr in attributes_to_copy: + if hasattr(self, attr): + copy_attr = {} + old_attr = getattr(self, attr) + if isinstance(old_attr, dict): + for v, value in old_attr.items(): + try: + copy_attr[v] = value.copy() + except AttributeError: + copy_attr[v] = copy(value) + setattr(H, attr, copy_attr) + else: + setattr(H, attr, copy(old_attr)) + + if immutable or (immutable is None and self.is_immutable()): + return H.copy(immutable=True) + return H def reverse_edge(self, u, v=None, label=None, inplace=True, multiedges=None): From 4277b93d2bd3f117d3c3a10cd6a66d5d81c24801 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 1 Aug 2023 20:36:35 +0100 Subject: [PATCH 2/3] use "needs ..." feature --- src/sage/combinat/posets/hasse_diagram.py | 131 +++++++++++----------- 1 file changed, 68 insertions(+), 63 deletions(-) diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index d94e11dd0a0..5e2edff0baf 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -1067,17 +1067,15 @@ def moebius_function_matrix(self, algorithm='cython'): [ 0 0 0 0 0 0 0 1] TESTS:: - - sage: H.moebius_function_matrix().is_immutable() # optional - sage.libs.flint sage.modules + sage: # needs sage.modules + sage: H.moebius_function_matrix().is_immutable() # optional - sage.libs.flint True - sage: hasattr(H,'_moebius_function_matrix') # optional - sage.libs.flint sage.modules + sage: hasattr(H,'_moebius_function_matrix') # optional - sage.libs.flint True - - sage: H.moebius_function == H._moebius_function_from_matrix # optional - sage.libs.flint sage.modules + sage: H.moebius_function == H._moebius_function_from_matrix # optional - sage.libs.flint True - sage: H = posets.TamariLattice(3)._hasse_diagram - sage: M = H.moebius_function_matrix('matrix'); M # optional - sage.modules + sage: M = H.moebius_function_matrix('matrix'); M [ 1 -1 -1 0 1] [ 0 1 0 0 -1] [ 0 0 1 -1 0] @@ -1160,25 +1158,29 @@ def coxeter_transformation(self, algorithm='cython'): EXAMPLES:: - sage: P = posets.PentagonPoset()._hasse_diagram # optional - sage.modules - sage: M = P.coxeter_transformation(); M # optional - sage.libs.flint sage.modules + sage: # needs sage.modules + sage: # needs sage.libs.flint sage.modules + sage: P = posets.PentagonPoset()._hasse_diagram + sage: M = P.coxeter_transformation(); M [ 0 0 0 0 -1] [ 0 0 0 1 -1] [ 0 1 0 0 -1] [-1 1 1 0 -1] [-1 1 0 1 -1] - sage: P.__dict__['coxeter_transformation'].clear_cache() # optional - sage.libs.flint sage.modules - sage: P.coxeter_transformation(algorithm="matrix") == M # optional - sage.libs.flint sage.modules + sage: P.__dict__['coxeter_transformation'].clear_cache() + sage: P.coxeter_transformation(algorithm="matrix") == M True TESTS:: - sage: P = posets.PentagonPoset()._hasse_diagram # optional - sage.modules - sage: M = P.coxeter_transformation() # optional - sage.libs.flint sage.modules - sage: M**8 == 1 # optional - sage.libs.flint sage.modules + sage: # needs sage.modules + sage: # needs sage.libs.flint sage.modules + sage: P = posets.PentagonPoset()._hasse_diagram + sage: M = P.coxeter_transformation() + sage: M**8 == 1 True - sage: P.__dict__['coxeter_transformation'].clear_cache() # optional - sage.libs.flint sage.modules - sage: P.coxeter_transformation(algorithm="banana") # optional - sage.libs.flint sage.modules + sage: P.__dict__['coxeter_transformation'].clear_cache() + sage: P.coxeter_transformation(algorithm="banana") Traceback (most recent call last): ... ValueError: unknown algorithm @@ -2222,24 +2224,22 @@ def antichains_iterator(self): EXAMPLES:: - sage: P = posets.PentagonPoset() # optional - sage.modules - sage: H = P._hasse_diagram # optional - sage.modules - sage: H.antichains_iterator() # optional - sage.modules + sage: # needs sage.modules + sage: P = posets.PentagonPoset() + sage: H = P._hasse_diagram + sage: H.antichains_iterator() - sage: list(H.antichains_iterator()) # optional - sage.modules + sage: list(H.antichains_iterator()) [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]] - sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[4],2:[3],3:[4]}) - sage: list(H.antichains_iterator()) # optional - sage.modules + sage: list(H.antichains_iterator()) [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]] - sage: H = HasseDiagram({0:[],1:[],2:[]}) - sage: list(H.antichains_iterator()) # optional - sage.modules + sage: list(H.antichains_iterator()) [[], [2], [1], [1, 2], [0], [0, 2], [0, 1], [0, 1, 2]] - sage: H = HasseDiagram({0:[1],1:[2],2:[3],3:[4]}) - sage: list(H.antichains_iterator()) # optional - sage.modules + sage: list(H.antichains_iterator()) [[], [4], [3], [2], [1], [0]] TESTS:: @@ -2279,12 +2279,13 @@ def are_incomparable(self, i, j): EXAMPLES:: - sage: P = posets.PentagonPoset() # optional - sage.modules - sage: H = P._hasse_diagram # optional - sage.modules - sage: H.are_incomparable(1,2) # optional - sage.modules + sage: # needs sage.modules + sage: P = posets.PentagonPoset() + sage: H = P._hasse_diagram + sage: H.are_incomparable(1,2) True - sage: V = H.vertices(sort=True) # optional - sage.modules - sage: [ (i,j) for i in V for j in V if H.are_incomparable(i,j)] # optional - sage.modules + sage: V = H.vertices(sort=True) + sage: [ (i,j) for i in V for j in V if H.are_incomparable(i,j)] [(1, 2), (1, 3), (2, 1), (3, 1)] """ if i == j: @@ -2304,12 +2305,13 @@ def are_comparable(self, i, j): EXAMPLES:: - sage: P = posets.PentagonPoset() # optional - sage.modules - sage: H = P._hasse_diagram # optional - sage.modules - sage: H.are_comparable(1,2) # optional - sage.modules + sage: # needs sage.modules + sage: P = posets.PentagonPoset() + sage: H = P._hasse_diagram + sage: H.are_comparable(1,2) False - sage: V = H.vertices(sort=True) # optional - sage.modules - sage: [ (i,j) for i in V for j in V if H.are_comparable(i,j)] # optional - sage.modules + sage: V = H.vertices(sort=True) + sage: [ (i,j) for i in V for j in V if H.are_comparable(i,j)] [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 4), (2, 0), (2, 2), (2, 3), (2, 4), (3, 0), (3, 2), (3, 3), (3, 4), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)] @@ -2331,26 +2333,27 @@ def antichains(self, element_class=list): EXAMPLES:: - sage: P = posets.PentagonPoset() # optional - sage.modules - sage: H = P._hasse_diagram # optional - sage.modules - sage: A = H.antichains() # optional - sage.modules - sage: list(A) # optional - sage.modules + sage: # needs sage.modules + sage: P = posets.PentagonPoset() + sage: H = P._hasse_diagram + sage: A = H.antichains() + sage: list(A) [[], [0], [1], [1, 2], [1, 3], [2], [3], [4]] - sage: A.cardinality() # optional - sage.modules + sage: A.cardinality() 8 - sage: [1,3] in A # optional - sage.modules + sage: [1,3] in A True - sage: [1,4] in A # optional - sage.modules + sage: [1,4] in A False TESTS:: - sage: TestSuite(A).run() # optional - sage.modules - - sage: A = Poset()._hasse_diagram.antichains() # optional - sage.modules - sage: list(A) # optional - sage.modules + sage: # needs sage.modules + sage: TestSuite(A).run() + sage: A = Poset()._hasse_diagram.antichains() + sage: list(A) [[]] - sage: TestSuite(A).run() # optional - sage.modules + sage: TestSuite(A).run() """ from sage.combinat.subsets_pairwise import PairwiseCompatibleSubsets return PairwiseCompatibleSubsets(self.vertices(sort=True), @@ -2382,23 +2385,25 @@ def chains(self, element_class=list, exclude=None, conversion=None): EXAMPLES:: - sage: P = posets.PentagonPoset() # optional - sage.modules - sage: H = P._hasse_diagram # optional - sage.modules - sage: A = H.chains() # optional - sage.modules - sage: list(A) # optional - sage.modules + sage: # needs sage.modules + sage: P = posets.PentagonPoset() + sage: H = P._hasse_diagram + sage: A = H.chains() + sage: list(A) [[], [0], [0, 1], [0, 1, 4], [0, 2], [0, 2, 3], [0, 2, 3, 4], [0, 2, 4], [0, 3], [0, 3, 4], [0, 4], [1], [1, 4], [2], [2, 3], [2, 3, 4], [2, 4], [3], [3, 4], [4]] - sage: A.cardinality() # optional - sage.modules + sage: A.cardinality() 20 - sage: [1,3] in A # optional - sage.modules + sage: [1,3] in A False - sage: [1,4] in A # optional - sage.modules + sage: [1,4] in A True One can exclude some vertices:: - sage: list(H.chains(exclude=[4, 3])) # optional - sage.modules + sage: # needs sage.modules + sage: list(H.chains(exclude=[4, 3])) [[], [0], [0, 1], [0, 2], [1], [2]] The ``element_class`` keyword determines how the chains are @@ -2426,16 +2431,16 @@ def is_linear_interval(self, t_min, t_max) -> bool: This means that this interval is a total order. EXAMPLES:: - - sage: P = posets.PentagonPoset() # optional - sage.modules - sage: H = P._hasse_diagram # optional - sage.modules - sage: H.is_linear_interval(0, 4) # optional - sage.modules + sage: # needs sage.modules + sage: P = posets.PentagonPoset() + sage: H = P._hasse_diagram + sage: H.is_linear_interval(0, 4) False - sage: H.is_linear_interval(0, 3) # optional - sage.modules + sage: H.is_linear_interval(0, 3) True - sage: H.is_linear_interval(1, 3) # optional - sage.modules + sage: H.is_linear_interval(1, 3) False - sage: H.is_linear_interval(1, 1) # optional - sage.modules + sage: H.is_linear_interval(1, 1) True TESTS:: From 05e3ba645d205f32f3cd2651670d3ec9efcfe395 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sun, 6 Aug 2023 23:02:47 +0200 Subject: [PATCH 3/3] PR #35969: fix review comments --- src/sage/combinat/posets/hasse_diagram.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/sage/combinat/posets/hasse_diagram.py b/src/sage/combinat/posets/hasse_diagram.py index 5e2edff0baf..969aedb770c 100644 --- a/src/sage/combinat/posets/hasse_diagram.py +++ b/src/sage/combinat/posets/hasse_diagram.py @@ -1067,12 +1067,13 @@ def moebius_function_matrix(self, algorithm='cython'): [ 0 0 0 0 0 0 0 1] TESTS:: - sage: # needs sage.modules - sage: H.moebius_function_matrix().is_immutable() # optional - sage.libs.flint + + sage: # needs sage.modules sage.libs.flint + sage: H.moebius_function_matrix().is_immutable() True - sage: hasattr(H,'_moebius_function_matrix') # optional - sage.libs.flint + sage: hasattr(H,'_moebius_function_matrix') True - sage: H.moebius_function == H._moebius_function_from_matrix # optional - sage.libs.flint + sage: H.moebius_function == H._moebius_function_from_matrix True sage: H = posets.TamariLattice(3)._hasse_diagram sage: M = H.moebius_function_matrix('matrix'); M @@ -1081,13 +1082,13 @@ def moebius_function_matrix(self, algorithm='cython'): [ 0 0 1 -1 0] [ 0 0 0 1 -1] [ 0 0 0 0 1] - sage: _ = H.__dict__.pop('_moebius_function_matrix') # optional - sage.modules - sage: H.moebius_function_matrix('cython') == M # optional - sage.libs.flint sage.modules + sage: _ = H.__dict__.pop('_moebius_function_matrix') + sage: H.moebius_function_matrix('cython') == M True - sage: _ = H.__dict__.pop('_moebius_function_matrix') # optional - sage.libs.flint sage.modules - sage: H.moebius_function_matrix('recursive') == M # optional - sage.modules + sage: _ = H.__dict__.pop('_moebius_function_matrix') + sage: H.moebius_function_matrix('recursive') == M True - sage: _ = H.__dict__.pop('_moebius_function_matrix') # optional - sage.modules + sage: _ = H.__dict__.pop('_moebius_function_matrix') sage: H.moebius_function_matrix('banana') Traceback (most recent call last): ... @@ -1158,7 +1159,6 @@ def coxeter_transformation(self, algorithm='cython'): EXAMPLES:: - sage: # needs sage.modules sage: # needs sage.libs.flint sage.modules sage: P = posets.PentagonPoset()._hasse_diagram sage: M = P.coxeter_transformation(); M @@ -1173,7 +1173,6 @@ def coxeter_transformation(self, algorithm='cython'): TESTS:: - sage: # needs sage.modules sage: # needs sage.libs.flint sage.modules sage: P = posets.PentagonPoset()._hasse_diagram sage: M = P.coxeter_transformation()