Skip to content

Commit 40bfc79

Browse files
committed
add parameter immutable to transitive closure methods
1 parent 1be0a58 commit 40bfc79

File tree

2 files changed

+133
-27
lines changed

2 files changed

+133
-27
lines changed

src/sage/graphs/generic_graph.py

Lines changed: 109 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20094,7 +20094,7 @@ def disjunctive_product(self, other):
2009420094
G.add_edge((u, v), (w, x))
2009520095
return G
2009620096

20097-
def transitive_closure(self, loops=True):
20097+
def transitive_closure(self, loops=None, immutable=None):
2009820098
r"""
2009920099
Return the transitive closure of the (di)graph.
2010020100

@@ -20106,11 +20106,18 @@ def transitive_closure(self, loops=True):
2010620106
acyclic graph is a directed acyclic graph representing the full partial
2010720107
order.
2010820108

20109-
.. NOTE::
20109+
INPUT:
2011020110

20111-
If the (di)graph allows loops, its transitive closure will by
20112-
default have one loop edge per vertex. This can be prevented by
20113-
disallowing loops in the (di)graph (``self.allow_loops(False)``).
20111+
- ``loops`` -- boolean (default: ``None``); whether to allow loops in
20112+
the returned (di)graph. By default (``None``), if the (di)graph allows
20113+
loops, its transitive closure will have one loop edge per vertex. This
20114+
can be prevented by disallowing loops in the (di)graph
20115+
(``self.allow_loops(False)``).
20116+
20117+
- ``immutable`` -- boolean (default: ``None``); whether to create a
20118+
mutable/immutable transitive closure. ``immutable=None`` (default)
20119+
means that the (di)graph and its transitive closure will behave the
20120+
same way.
2011420121

2011520122
EXAMPLES::
2011620123

@@ -20138,21 +20145,49 @@ def transitive_closure(self, loops=True):
2013820145
sage: G.transitive_closure().loop_edges(labels=False)
2013920146
[(0, 0), (1, 1), (2, 2)]
2014020147

20141-
::
20148+
Check the behavior of parameter ``loops``::
2014220149

2014320150
sage: G = graphs.CycleGraph(3)
2014420151
sage: G.transitive_closure().loop_edges(labels=False)
2014520152
[]
20153+
sage: G.transitive_closure(loops=True).loop_edges(labels=False)
20154+
[(0, 0), (1, 1), (2, 2)]
2014620155
sage: G.allow_loops(True)
2014720156
sage: G.transitive_closure().loop_edges(labels=False)
2014820157
[(0, 0), (1, 1), (2, 2)]
20158+
sage: G.transitive_closure(loops=False).loop_edges(labels=False)
20159+
[]
20160+
20161+
Check the behavior of parameter `ìmmutable``::
20162+
20163+
sage: G = Graph([(0, 1)])
20164+
sage: G.transitive_closure().is_immutable()
20165+
False
20166+
sage: G.transitive_closure(immutable=True).is_immutable()
20167+
True
20168+
sage: G = Graph([(0, 1)], immutable=True)
20169+
sage: G.transitive_closure().is_immutable()
20170+
True
20171+
sage: G.transitive_closure(immutable=False).is_immutable()
20172+
False
2014920173
"""
20150-
G = copy(self)
20151-
G.name('Transitive closure of ' + self.name())
20152-
G.add_edges(((u, v) for u in G for v in G.breadth_first_search(u)), loops=None)
20153-
return G
20174+
name = f"Transitive closure of {self.name()}"
20175+
if immutable is None:
20176+
immutable = self.is_immutable()
20177+
if loops is None:
20178+
loops = self.allows_loops()
20179+
if loops:
20180+
edges = ((u, v) for u in self for v in self.depth_first_search(u))
20181+
else:
20182+
edges = ((u, v) for u in self for v in self.depth_first_search(u) if u != v)
20183+
if self.is_directed():
20184+
from sage.graphs.digraph import DiGraph as GT
20185+
else:
20186+
from sage.graphs.graph import Graph as GT
20187+
return GT([self, edges], format='vertices_and_edges', loops=loops,
20188+
immutable=immutable, name=name)
2015420189

20155-
def transitive_reduction(self):
20190+
def transitive_reduction(self, immutable=None):
2015620191
r"""
2015720192
Return a transitive reduction of a graph.
2015820193

@@ -20165,6 +20200,13 @@ def transitive_reduction(self):
2016520200
A transitive reduction of a complete graph is a tree. A transitive
2016620201
reduction of a tree is itself.
2016720202

20203+
INPUT:
20204+
20205+
- ``immutable`` -- boolean (default: ``None``); whether to create a
20206+
mutable/immutable transitive closure. ``immutable=None`` (default)
20207+
means that the (di)graph and its transitive closure will behave the
20208+
same way.
20209+
2016820210
EXAMPLES::
2016920211

2017020212
sage: g = graphs.PathGraph(4)
@@ -20176,35 +20218,65 @@ def transitive_reduction(self):
2017620218
sage: g = DiGraph({0: [1, 2], 1: [2, 3, 4, 5], 2: [4, 5]})
2017720219
sage: g.transitive_reduction().size()
2017820220
5
20221+
20222+
TESTS:
20223+
20224+
Check the behavior of parameter `ìmmutable``::
20225+
20226+
sage: G = Graph([(0, 1)])
20227+
sage: G.transitive_reduction().is_immutable()
20228+
False
20229+
sage: G.transitive_reduction(immutable=True).is_immutable()
20230+
True
20231+
sage: G = Graph([(0, 1)], immutable=True)
20232+
sage: G.transitive_reduction().is_immutable()
20233+
True
20234+
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)])
20235+
sage: G.transitive_reduction().is_immutable()
20236+
False
20237+
sage: G.transitive_reduction(immutable=True).is_immutable()
20238+
True
20239+
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20240+
sage: G.transitive_reduction().is_immutable()
20241+
True
2017920242
"""
20243+
if immutable is None:
20244+
immutable = self.is_immutable()
20245+
2018020246
if self.is_directed():
2018120247
if self.is_directed_acyclic():
2018220248
from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
20183-
return transitive_reduction_acyclic(self)
20249+
return transitive_reduction_acyclic(self, immutable=immutable)
2018420250

20185-
G = copy(self)
20251+
G = self.copy(immutable=False)
2018620252
G.allow_multiple_edges(False)
2018720253
n = G.order()
20188-
for e in G.edges(sort=False):
20254+
for e in list(G.edges(sort=False)):
2018920255
# Try deleting the edge, see if we still have a path between
2019020256
# the vertices.
2019120257
G.delete_edge(e)
2019220258
if G.distance(e[0], e[1]) > n:
2019320259
# oops, we shouldn't have deleted it
2019420260
G.add_edge(e)
20261+
if immutable:
20262+
return G.copy(immutable=True)
2019520263
return G
2019620264

2019720265
# The transitive reduction of each connected component of an
2019820266
# undirected graph is a spanning tree
20199-
from sage.graphs.graph import Graph
2020020267
if self.is_connected():
20201-
return Graph(self.min_spanning_tree(weight_function=lambda e: 1))
20202-
G = Graph(list(self))
20203-
for cc in self.connected_components(sort=False):
20204-
if len(cc) > 1:
20205-
edges = self.subgraph(cc).min_spanning_tree(weight_function=lambda e: 1)
20206-
G.add_edges(edges)
20207-
return G
20268+
CC = [self]
20269+
else:
20270+
CC = (self.subgraph(c)
20271+
for c in self.connected_components() if len(c) > 1)
20272+
20273+
def edges():
20274+
for g in CC:
20275+
yield from g.min_spanning_tree(weight_function=lambda e: 1)
20276+
20277+
from sage.graphs.graph import Graph
20278+
return Graph([self, edges()], format='vertices_and_edges',
20279+
immutable=immutable)
2020820280

2020920281
def is_transitively_reduced(self):
2021020282
r"""
@@ -20226,13 +20298,27 @@ def is_transitively_reduced(self):
2022620298
sage: d = DiGraph({0: [1, 2], 1: [2], 2: []})
2022720299
sage: d.is_transitively_reduced()
2022820300
False
20301+
20302+
TESTS:
20303+
20304+
Check the behavior of the method for immutable (di)graphs::
20305+
20306+
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20307+
sage: G.is_transitively_reduced()
20308+
True
20309+
sage: G = DiGraph(graphs.CompleteGraph(4), immutable=True)
20310+
sage: G.is_transitively_reduced()
20311+
False
20312+
sage: G = Graph([(0, 1), (2, 3)], immutable=True)
20313+
sage: G.is_transitively_reduced()
20314+
True
2022920315
"""
2023020316
if self.is_directed():
2023120317
if self.is_directed_acyclic():
2023220318
return self == self.transitive_reduction()
2023320319

2023420320
from sage.rings.infinity import Infinity
20235-
G = copy(self)
20321+
G = self.copy(immutable=False)
2023620322
for e in self.edge_iterator():
2023720323
G.delete_edge(e)
2023820324
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
@@ -1558,20 +1558,37 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
15581558
return (True, output)
15591559

15601560

1561-
def transitive_reduction_acyclic(G):
1561+
def transitive_reduction_acyclic(G, immutable=None):
15621562
r"""
15631563
Return the transitive reduction of an acyclic digraph.
15641564
15651565
INPUT:
15661566
15671567
- ``G`` -- an acyclic digraph
15681568
1569+
- ``immutable`` -- boolean (default: ``None``); whether to create a
1570+
mutable/immutable transitive closure. ``immutable=None`` (default) means
1571+
that the (di)graph and its transitive closure will behave the same way.
1572+
15691573
EXAMPLES::
15701574
15711575
sage: from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
15721576
sage: G = posets.BooleanLattice(4).hasse_diagram()
15731577
sage: G == transitive_reduction_acyclic(G.transitive_closure())
15741578
True
1579+
1580+
TESTS:
1581+
1582+
Check the behavior of parameter `ìmmutable``::
1583+
1584+
sage: G = DiGraph([(0, 1)])
1585+
sage: transitive_reduction_acyclic(G).is_immutable()
1586+
False
1587+
sage: transitive_reduction_acyclic(G, immutable=True).is_immutable()
1588+
True
1589+
sage: G = DiGraph([(0, 1)], immutable=True)
1590+
sage: transitive_reduction_acyclic(G).is_immutable()
1591+
True
15751592
"""
15761593
cdef int n = G.order()
15771594
cdef dict v_to_int = {vv: i for i, vv in enumerate(G)}
@@ -1615,10 +1632,13 @@ def transitive_reduction_acyclic(G):
16151632
if binary_matrix_get(closure, u, v):
16161633
useful_edges.append((uu, vv))
16171634

1635+
if immutable is None:
1636+
immutable = G.is_immutable()
1637+
16181638
from sage.graphs.digraph import DiGraph
1619-
reduced = DiGraph()
1620-
reduced.add_edges(useful_edges)
1621-
reduced.add_vertices(linear_extension)
1639+
reduced = DiGraph([linear_extension, useful_edges],
1640+
format='vertices_and_edges',
1641+
immutable=immutable)
16221642

16231643
binary_matrix_free(closure)
16241644

0 commit comments

Comments
 (0)