Skip to content

Commit 847b9a8

Browse files
author
Release Manager
committed
sagemathgh-39287: add parameter `immutable` to transitive closure methods in `sage/graphs/generic_graph.py` Following sagemath#39280 and discussions in sagemath#39177, we add parameter `immutable` to methods related to transitive closure in `sage/graphs/generic_graph.py` and `sage/graphs/generic_graph_pyx.pyx`: - `transitive_closure`. We also fix the use of parameter `loops` that was previously ignored - `transitive_reduction` - `transitive_reduction_acyclic` - `is_transitively_reduced`. Here we ensure that the method accepts immutable digraphs. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> URL: sagemath#39287 Reported by: David Coudert Reviewer(s): Frédéric Chapoton
2 parents 882c2a8 + 8a5ec16 commit 847b9a8

File tree

6 files changed

+225
-111
lines changed

6 files changed

+225
-111
lines changed

src/sage/categories/kahler_algebras.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -142,33 +142,33 @@ def hodge_riemann_relations(self, k):
142142
sage: ch = matroids.Uniform(4, 6).chow_ring(QQ, False)
143143
sage: ch.hodge_riemann_relations(1)
144144
Quadratic form in 36 variables over Rational Field with coefficients:
145-
[ 3 -1 -1 3 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 3 ]
146-
[ * 3 -1 3 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 3 ]
147-
[ * * 3 3 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 3 ]
145+
[ 3 -1 -1 3 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 3 ]
146+
[ * 3 -1 3 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 3 ]
147+
[ * * 3 3 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 3 ]
148148
[ * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
149-
[ * * * * 3 -1 3 -1 3 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 3 ]
150-
[ * * * * * 3 3 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 3 ]
149+
[ * * * * 3 -1 3 -1 -1 3 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 ]
150+
[ * * * * * 3 3 -1 3 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 3 ]
151151
[ * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
152-
[ * * * * * * * 3 3 3 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 ]
152+
[ * * * * * * * 3 3 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 3 ]
153153
[ * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
154154
[ * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
155-
[ * * * * * * * * * * 3 -1 3 -1 -1 3 -1 -1 3 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 ]
156-
[ * * * * * * * * * * * 3 3 -1 3 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 ]
155+
[ * * * * * * * * * * 3 -1 3 -1 -1 3 -1 -1 -1 3 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 ]
156+
[ * * * * * * * * * * * 3 3 -1 3 -1 -1 -1 3 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 ]
157157
[ * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
158-
[ * * * * * * * * * * * * * 3 3 3 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 -1 -1 3 ]
158+
[ * * * * * * * * * * * * * 3 3 3 -1 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 -1 -1 3 ]
159159
[ * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
160160
[ * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
161-
[ * * * * * * * * * * * * * * * * 3 3 3 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 -1 -1 -1 3 ]
161+
[ * * * * * * * * * * * * * * * * 3 3 3 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 3 3 ]
162162
[ * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
163163
[ * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
164164
[ * * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
165-
[ * * * * * * * * * * * * * * * * * * * * 3 -1 3 -1 3 -1 -1 -1 -1 3 -1 -1 -1 3 -1 3 ]
166-
[ * * * * * * * * * * * * * * * * * * * * * 3 3 -1 -1 3 -1 -1 3 -1 -1 -1 3 -1 -1 3 ]
165+
[ * * * * * * * * * * * * * * * * * * * * 3 -1 3 -1 3 -1 -1 -1 -1 3 -1 -1 -1 -1 3 3 ]
166+
[ * * * * * * * * * * * * * * * * * * * * * 3 3 -1 -1 3 -1 -1 3 -1 -1 -1 -1 3 -1 3 ]
167167
[ * * * * * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
168-
[ * * * * * * * * * * * * * * * * * * * * * * * 3 3 3 -1 3 -1 -1 -1 3 -1 -1 -1 3 ]
168+
[ * * * * * * * * * * * * * * * * * * * * * * * 3 3 3 -1 3 -1 -1 -1 -1 3 -1 -1 3 ]
169169
[ * * * * * * * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
170170
[ * * * * * * * * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 ]
171-
[ * * * * * * * * * * * * * * * * * * * * * * * * * * 3 3 3 3 -1 -1 -1 -1 3 3 ]
171+
[ * * * * * * * * * * * * * * * * * * * * * * * * * * 3 3 3 3 -1 3 -1 -1 -1 3 ]
172172
[ * * * * * * * * * * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 -1 ]
173173
[ * * * * * * * * * * * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 -1 ]
174174
[ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 3 -1 -1 -1 -1 -1 -1 ]

src/sage/combinat/posets/lattices.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4877,9 +4877,9 @@ def quotient(self, congruence, labels='tuple'):
48774877
parts_H = [sorted([self._element_to_vertex(e) for e in part]) for
48784878
part in congruence]
48794879
minimal_vertices = [part[0] for part in parts_H]
4880-
H = self._hasse_diagram.transitive_closure().subgraph(minimal_vertices).transitive_reduction()
4880+
H = self._hasse_diagram.transitive_closure().subgraph(minimal_vertices).transitive_reduction(immutable=False)
48814881
if labels == 'integer':
4882-
H.relabel(list(range(len(minimal_vertices))))
4882+
H.relabel()
48834883
return LatticePoset(H)
48844884
part_dict = {m[0]: [self._vertex_to_element(x) for x in m] for m
48854885
in parts_H}

src/sage/graphs/generic_graph.py

Lines changed: 113 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20516,7 +20516,7 @@ def disjunctive_product(self, other, immutable=None):
2051620516
return GT([vertices, edges], format='vertices_and_edges',
2051720517
loops=loops, immutable=immutable)
2051820518

20519-
def transitive_closure(self, loops=True):
20519+
def transitive_closure(self, loops=None, immutable=None):
2052020520
r"""
2052120521
Return the transitive closure of the (di)graph.
2052220522

@@ -20528,11 +20528,18 @@ def transitive_closure(self, loops=True):
2052820528
acyclic graph is a directed acyclic graph representing the full partial
2052920529
order.
2053020530

20531-
.. NOTE::
20531+
INPUT:
2053220532

20533-
If the (di)graph allows loops, its transitive closure will by
20534-
default have one loop edge per vertex. This can be prevented by
20535-
disallowing loops in the (di)graph (``self.allow_loops(False)``).
20533+
- ``loops`` -- boolean (default: ``None``); whether to allow loops in
20534+
the returned (di)graph. By default (``None``), if the (di)graph allows
20535+
loops, its transitive closure will have one loop edge per vertex. This
20536+
can be prevented by disallowing loops in the (di)graph
20537+
(``self.allow_loops(False)``).
20538+
20539+
- ``immutable`` -- boolean (default: ``None``); whether to create a
20540+
mutable/immutable transitive closure. ``immutable=None`` (default)
20541+
means that the (di)graph and its transitive closure will behave the
20542+
same way.
2053620543

2053720544
EXAMPLES::
2053820545

@@ -20560,21 +20567,49 @@ def transitive_closure(self, loops=True):
2056020567
sage: G.transitive_closure().loop_edges(labels=False)
2056120568
[(0, 0), (1, 1), (2, 2)]
2056220569

20563-
::
20570+
Check the behavior of parameter ``loops``::
2056420571

2056520572
sage: G = graphs.CycleGraph(3)
2056620573
sage: G.transitive_closure().loop_edges(labels=False)
2056720574
[]
20575+
sage: G.transitive_closure(loops=True).loop_edges(labels=False)
20576+
[(0, 0), (1, 1), (2, 2)]
2056820577
sage: G.allow_loops(True)
2056920578
sage: G.transitive_closure().loop_edges(labels=False)
2057020579
[(0, 0), (1, 1), (2, 2)]
20580+
sage: G.transitive_closure(loops=False).loop_edges(labels=False)
20581+
[]
20582+
20583+
Check the behavior of parameter ``immutable``::
20584+
20585+
sage: G = Graph([(0, 1)])
20586+
sage: G.transitive_closure().is_immutable()
20587+
False
20588+
sage: G.transitive_closure(immutable=True).is_immutable()
20589+
True
20590+
sage: G = Graph([(0, 1)], immutable=True)
20591+
sage: G.transitive_closure().is_immutable()
20592+
True
20593+
sage: G.transitive_closure(immutable=False).is_immutable()
20594+
False
2057120595
"""
20572-
G = copy(self)
20573-
G.name('Transitive closure of ' + self.name())
20574-
G.add_edges(((u, v) for u in G for v in G.breadth_first_search(u)), loops=None)
20575-
return G
20596+
name = f"Transitive closure of {self.name()}"
20597+
if immutable is None:
20598+
immutable = self.is_immutable()
20599+
if loops is None:
20600+
loops = self.allows_loops()
20601+
if loops:
20602+
edges = ((u, v) for u in self for v in self.depth_first_search(u))
20603+
else:
20604+
edges = ((u, v) for u in self for v in self.depth_first_search(u) if u != v)
20605+
if self.is_directed():
20606+
from sage.graphs.digraph import DiGraph as GT
20607+
else:
20608+
from sage.graphs.graph import Graph as GT
20609+
return GT([self, edges], format='vertices_and_edges', loops=loops,
20610+
immutable=immutable, name=name)
2057620611

20577-
def transitive_reduction(self):
20612+
def transitive_reduction(self, immutable=None):
2057820613
r"""
2057920614
Return a transitive reduction of a graph.
2058020615

@@ -20587,6 +20622,13 @@ def transitive_reduction(self):
2058720622
A transitive reduction of a complete graph is a tree. A transitive
2058820623
reduction of a tree is itself.
2058920624

20625+
INPUT:
20626+
20627+
- ``immutable`` -- boolean (default: ``None``); whether to create a
20628+
mutable/immutable transitive closure. ``immutable=None`` (default)
20629+
means that the (di)graph and its transitive closure will behave the
20630+
same way.
20631+
2059020632
EXAMPLES::
2059120633

2059220634
sage: g = graphs.PathGraph(4)
@@ -20595,38 +20637,72 @@ def transitive_reduction(self):
2059520637
sage: g = graphs.CompleteGraph(5)
2059620638
sage: h = g.transitive_reduction(); h.size()
2059720639
4
20640+
sage: (2*g).transitive_reduction().size()
20641+
8
2059820642
sage: g = DiGraph({0: [1, 2], 1: [2, 3, 4, 5], 2: [4, 5]})
2059920643
sage: g.transitive_reduction().size()
2060020644
5
20645+
sage: (2*g).transitive_reduction().size()
20646+
10
20647+
20648+
TESTS:
20649+
20650+
Check the behavior of parameter ``immutable``::
20651+
20652+
sage: G = Graph([(0, 1)])
20653+
sage: G.transitive_reduction().is_immutable()
20654+
False
20655+
sage: G.transitive_reduction(immutable=True).is_immutable()
20656+
True
20657+
sage: G = Graph([(0, 1)], immutable=True)
20658+
sage: G.transitive_reduction().is_immutable()
20659+
True
20660+
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)])
20661+
sage: G.transitive_reduction().is_immutable()
20662+
False
20663+
sage: G.transitive_reduction(immutable=True).is_immutable()
20664+
True
20665+
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20666+
sage: G.transitive_reduction().is_immutable()
20667+
True
2060120668
"""
20669+
if immutable is None:
20670+
immutable = self.is_immutable()
20671+
2060220672
if self.is_directed():
2060320673
if self.is_directed_acyclic():
2060420674
from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
20605-
return transitive_reduction_acyclic(self)
20675+
return transitive_reduction_acyclic(self, immutable=immutable)
2060620676

20607-
G = copy(self)
20677+
G = self.copy(immutable=False)
2060820678
G.allow_multiple_edges(False)
2060920679
n = G.order()
20610-
for e in G.edges(sort=False):
20680+
for e in list(G.edges(sort=False)):
2061120681
# Try deleting the edge, see if we still have a path between
2061220682
# the vertices.
2061320683
G.delete_edge(e)
2061420684
if G.distance(e[0], e[1]) > n:
2061520685
# oops, we shouldn't have deleted it
2061620686
G.add_edge(e)
20687+
if immutable:
20688+
return G.copy(immutable=True)
2061720689
return G
2061820690

2061920691
# The transitive reduction of each connected component of an
2062020692
# undirected graph is a spanning tree
20621-
from sage.graphs.graph import Graph
2062220693
if self.is_connected():
20623-
return Graph(self.min_spanning_tree(weight_function=lambda e: 1))
20624-
G = Graph(list(self))
20625-
for cc in self.connected_components(sort=False):
20626-
if len(cc) > 1:
20627-
edges = self.subgraph(cc).min_spanning_tree(weight_function=lambda e: 1)
20628-
G.add_edges(edges)
20629-
return G
20694+
CC = [self]
20695+
else:
20696+
CC = (self.subgraph(c)
20697+
for c in self.connected_components(sort=False) if len(c) > 1)
20698+
20699+
def edges():
20700+
for g in CC:
20701+
yield from g.min_spanning_tree(weight_function=lambda e: 1)
20702+
20703+
from sage.graphs.graph import Graph
20704+
return Graph([self, edges()], format='vertices_and_edges',
20705+
immutable=immutable)
2063020706

2063120707
def is_transitively_reduced(self):
2063220708
r"""
@@ -20648,13 +20724,27 @@ def is_transitively_reduced(self):
2064820724
sage: d = DiGraph({0: [1, 2], 1: [2], 2: []})
2064920725
sage: d.is_transitively_reduced()
2065020726
False
20727+
20728+
TESTS:
20729+
20730+
Check the behavior of the method for immutable (di)graphs::
20731+
20732+
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20733+
sage: G.is_transitively_reduced()
20734+
True
20735+
sage: G = DiGraph(graphs.CompleteGraph(4), immutable=True)
20736+
sage: G.is_transitively_reduced()
20737+
False
20738+
sage: G = Graph([(0, 1), (2, 3)], immutable=True)
20739+
sage: G.is_transitively_reduced()
20740+
True
2065120741
"""
2065220742
if self.is_directed():
2065320743
if self.is_directed_acyclic():
2065420744
return self == self.transitive_reduction()
2065520745

2065620746
from sage.rings.infinity import Infinity
20657-
G = copy(self)
20747+
G = self.copy(immutable=False)
2065820748
for e in self.edge_iterator():
2065920749
G.delete_edge(e)
2066020750
if G.distance(e[0], e[1]) == Infinity:

src/sage/graphs/generic_graph_pyx.pyx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,20 +1585,37 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
15851585
return (True, output)
15861586

15871587

1588-
def transitive_reduction_acyclic(G):
1588+
def transitive_reduction_acyclic(G, immutable=None):
15891589
r"""
15901590
Return the transitive reduction of an acyclic digraph.
15911591
15921592
INPUT:
15931593
15941594
- ``G`` -- an acyclic digraph
15951595
1596+
- ``immutable`` -- boolean (default: ``None``); whether to create a
1597+
mutable/immutable transitive closure. ``immutable=None`` (default) means
1598+
that the (di)graph and its transitive closure will behave the same way.
1599+
15961600
EXAMPLES::
15971601
15981602
sage: from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
15991603
sage: G = posets.BooleanLattice(4).hasse_diagram()
16001604
sage: G == transitive_reduction_acyclic(G.transitive_closure())
16011605
True
1606+
1607+
TESTS:
1608+
1609+
Check the behavior of parameter ``immutable``::
1610+
1611+
sage: G = DiGraph([(0, 1)])
1612+
sage: transitive_reduction_acyclic(G).is_immutable()
1613+
False
1614+
sage: transitive_reduction_acyclic(G, immutable=True).is_immutable()
1615+
True
1616+
sage: G = DiGraph([(0, 1)], immutable=True)
1617+
sage: transitive_reduction_acyclic(G).is_immutable()
1618+
True
16021619
"""
16031620
cdef int n = G.order()
16041621
cdef dict v_to_int = {vv: i for i, vv in enumerate(G)}
@@ -1642,10 +1659,13 @@ def transitive_reduction_acyclic(G):
16421659
if binary_matrix_get(closure, u, v):
16431660
useful_edges.append((uu, vv))
16441661

1662+
if immutable is None:
1663+
immutable = G.is_immutable()
1664+
16451665
from sage.graphs.digraph import DiGraph
1646-
reduced = DiGraph()
1647-
reduced.add_edges(useful_edges)
1648-
reduced.add_vertices(linear_extension)
1666+
reduced = DiGraph([linear_extension, useful_edges],
1667+
format='vertices_and_edges',
1668+
immutable=immutable)
16491669

16501670
binary_matrix_free(closure)
16511671

0 commit comments

Comments
 (0)