Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add parameter immutable to transitive closure methods in sage/graphs/generic_graph.py #39287

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/sage/combinat/posets/lattices.py
Original file line number Diff line number Diff line change
Expand Up @@ -4853,9 +4853,9 @@ def quotient(self, congruence, labels='tuple'):
parts_H = [sorted([self._element_to_vertex(e) for e in part]) for
part in congruence]
minimal_vertices = [part[0] for part in parts_H]
H = self._hasse_diagram.transitive_closure().subgraph(minimal_vertices).transitive_reduction()
H = self._hasse_diagram.transitive_closure().subgraph(minimal_vertices).transitive_reduction(immutable=False)
if labels == 'integer':
H.relabel(list(range(len(minimal_vertices))))
H.relabel()
return LatticePoset(H)
part_dict = {m[0]: [self._vertex_to_element(x) for x in m] for m
in parts_H}
Expand Down
132 changes: 109 additions & 23 deletions src/sage/graphs/generic_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -20149,7 +20149,7 @@ def disjunctive_product(self, other):
G.add_edge((u, v), (w, x))
return G

def transitive_closure(self, loops=True):
def transitive_closure(self, loops=None, immutable=None):
r"""
Return the transitive closure of the (di)graph.

Expand All @@ -20161,11 +20161,18 @@ def transitive_closure(self, loops=True):
acyclic graph is a directed acyclic graph representing the full partial
order.

.. NOTE::
INPUT:

If the (di)graph allows loops, its transitive closure will by
default have one loop edge per vertex. This can be prevented by
disallowing loops in the (di)graph (``self.allow_loops(False)``).
- ``loops`` -- boolean (default: ``None``); whether to allow loops in
the returned (di)graph. By default (``None``), if the (di)graph allows
loops, its transitive closure will have one loop edge per vertex. This
can be prevented by disallowing loops in the (di)graph
(``self.allow_loops(False)``).

- ``immutable`` -- boolean (default: ``None``); whether to create a
mutable/immutable transitive closure. ``immutable=None`` (default)
means that the (di)graph and its transitive closure will behave the
same way.

EXAMPLES::

Expand Down Expand Up @@ -20193,21 +20200,49 @@ def transitive_closure(self, loops=True):
sage: G.transitive_closure().loop_edges(labels=False)
[(0, 0), (1, 1), (2, 2)]

::
Check the behavior of parameter ``loops``::

sage: G = graphs.CycleGraph(3)
sage: G.transitive_closure().loop_edges(labels=False)
[]
sage: G.transitive_closure(loops=True).loop_edges(labels=False)
[(0, 0), (1, 1), (2, 2)]
sage: G.allow_loops(True)
sage: G.transitive_closure().loop_edges(labels=False)
[(0, 0), (1, 1), (2, 2)]
sage: G.transitive_closure(loops=False).loop_edges(labels=False)
[]

Check the behavior of parameter `ìmmutable``::

sage: G = Graph([(0, 1)])
sage: G.transitive_closure().is_immutable()
False
sage: G.transitive_closure(immutable=True).is_immutable()
True
sage: G = Graph([(0, 1)], immutable=True)
sage: G.transitive_closure().is_immutable()
True
sage: G.transitive_closure(immutable=False).is_immutable()
False
"""
G = copy(self)
G.name('Transitive closure of ' + self.name())
G.add_edges(((u, v) for u in G for v in G.breadth_first_search(u)), loops=None)
return G
name = f"Transitive closure of {self.name()}"
if immutable is None:
immutable = self.is_immutable()
if loops is None:
loops = self.allows_loops()
if loops:
edges = ((u, v) for u in self for v in self.depth_first_search(u))
else:
edges = ((u, v) for u in self for v in self.depth_first_search(u) if u != v)
if self.is_directed():
from sage.graphs.digraph import DiGraph as GT
else:
from sage.graphs.graph import Graph as GT
return GT([self, edges], format='vertices_and_edges', loops=loops,
immutable=immutable, name=name)

def transitive_reduction(self):
def transitive_reduction(self, immutable=None):
r"""
Return a transitive reduction of a graph.

Expand All @@ -20220,6 +20255,13 @@ def transitive_reduction(self):
A transitive reduction of a complete graph is a tree. A transitive
reduction of a tree is itself.

INPUT:

- ``immutable`` -- boolean (default: ``None``); whether to create a
mutable/immutable transitive closure. ``immutable=None`` (default)
means that the (di)graph and its transitive closure will behave the
same way.

EXAMPLES::

sage: g = graphs.PathGraph(4)
Expand All @@ -20231,35 +20273,65 @@ def transitive_reduction(self):
sage: g = DiGraph({0: [1, 2], 1: [2, 3, 4, 5], 2: [4, 5]})
sage: g.transitive_reduction().size()
5

TESTS:

Check the behavior of parameter `ìmmutable``::

sage: G = Graph([(0, 1)])
sage: G.transitive_reduction().is_immutable()
False
sage: G.transitive_reduction(immutable=True).is_immutable()
True
sage: G = Graph([(0, 1)], immutable=True)
sage: G.transitive_reduction().is_immutable()
True
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)])
sage: G.transitive_reduction().is_immutable()
False
sage: G.transitive_reduction(immutable=True).is_immutable()
True
sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
sage: G.transitive_reduction().is_immutable()
True
"""
if immutable is None:
immutable = self.is_immutable()

if self.is_directed():
if self.is_directed_acyclic():
from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
return transitive_reduction_acyclic(self)
return transitive_reduction_acyclic(self, immutable=immutable)

G = copy(self)
G = self.copy(immutable=False)
G.allow_multiple_edges(False)
n = G.order()
for e in G.edges(sort=False):
for e in list(G.edges(sort=False)):
# Try deleting the edge, see if we still have a path between
# the vertices.
G.delete_edge(e)
if G.distance(e[0], e[1]) > n:
# oops, we shouldn't have deleted it
G.add_edge(e)
if immutable:
return G.copy(immutable=True)
return G

# The transitive reduction of each connected component of an
# undirected graph is a spanning tree
from sage.graphs.graph import Graph
if self.is_connected():
return Graph(self.min_spanning_tree(weight_function=lambda e: 1))
G = Graph(list(self))
for cc in self.connected_components(sort=False):
if len(cc) > 1:
edges = self.subgraph(cc).min_spanning_tree(weight_function=lambda e: 1)
G.add_edges(edges)
return G
CC = [self]
else:
CC = (self.subgraph(c)
for c in self.connected_components() if len(c) > 1)

def edges():
for g in CC:
yield from g.min_spanning_tree(weight_function=lambda e: 1)

from sage.graphs.graph import Graph
return Graph([self, edges()], format='vertices_and_edges',
immutable=immutable)

def is_transitively_reduced(self):
r"""
Expand All @@ -20281,13 +20353,27 @@ def is_transitively_reduced(self):
sage: d = DiGraph({0: [1, 2], 1: [2], 2: []})
sage: d.is_transitively_reduced()
False

TESTS:

Check the behavior of the method for immutable (di)graphs::

sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
sage: G.is_transitively_reduced()
True
sage: G = DiGraph(graphs.CompleteGraph(4), immutable=True)
sage: G.is_transitively_reduced()
False
sage: G = Graph([(0, 1), (2, 3)], immutable=True)
sage: G.is_transitively_reduced()
True
"""
if self.is_directed():
if self.is_directed_acyclic():
return self == self.transitive_reduction()

from sage.rings.infinity import Infinity
G = copy(self)
G = self.copy(immutable=False)
for e in self.edge_iterator():
G.delete_edge(e)
if G.distance(e[0], e[1]) == Infinity:
Expand Down
28 changes: 24 additions & 4 deletions src/sage/graphs/generic_graph_pyx.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1558,20 +1558,37 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
return (True, output)


def transitive_reduction_acyclic(G):
def transitive_reduction_acyclic(G, immutable=None):
r"""
Return the transitive reduction of an acyclic digraph.

INPUT:

- ``G`` -- an acyclic digraph

- ``immutable`` -- boolean (default: ``None``); whether to create a
mutable/immutable transitive closure. ``immutable=None`` (default) means
that the (di)graph and its transitive closure will behave the same way.

EXAMPLES::

sage: from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
sage: G = posets.BooleanLattice(4).hasse_diagram()
sage: G == transitive_reduction_acyclic(G.transitive_closure())
True

TESTS:

Check the behavior of parameter `ìmmutable``::

sage: G = DiGraph([(0, 1)])
sage: transitive_reduction_acyclic(G).is_immutable()
False
sage: transitive_reduction_acyclic(G, immutable=True).is_immutable()
True
sage: G = DiGraph([(0, 1)], immutable=True)
sage: transitive_reduction_acyclic(G).is_immutable()
True
"""
cdef int n = G.order()
cdef dict v_to_int = {vv: i for i, vv in enumerate(G)}
Expand Down Expand Up @@ -1615,10 +1632,13 @@ def transitive_reduction_acyclic(G):
if binary_matrix_get(closure, u, v):
useful_edges.append((uu, vv))

if immutable is None:
immutable = G.is_immutable()

from sage.graphs.digraph import DiGraph
reduced = DiGraph()
reduced.add_edges(useful_edges)
reduced.add_vertices(linear_extension)
reduced = DiGraph([linear_extension, useful_edges],
format='vertices_and_edges',
immutable=immutable)

binary_matrix_free(closure)

Expand Down
56 changes: 28 additions & 28 deletions src/sage/matroids/chow_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,25 +186,25 @@

sage: ch = matroids.Uniform(3, 6).chow_ring(QQ, True, 'fy')
sage: ch.basis()
Family (1, B1, B1*B012345, B0, B0*B012345, B01, B01^2, B2,
B2*B012345, B02, B02^2, B12, B12^2, B3, B3*B012345, B03, B03^2,
B13, B13^2, B23, B23^2, B4, B4*B012345, B04, B04^2, B14, B14^2,
B24, B24^2, B34, B34^2, B5, B5*B012345, B05, B05^2, B15, B15^2,
B25, B25^2, B35, B35^2, B45, B45^2, B012345, B012345^2, B012345^3)
Family (1, B0, B0*B012345, B1, B1*B012345, B01, B01^2, B2,
B2*B012345, B12, B12^2, B02, B02^2, B3, B3*B012345, B23, B23^2,
B13, B13^2, B03, B03^2, B4, B4*B012345, B34, B34^2, B24, B24^2,
B14, B14^2, B04, B04^2, B5, B5*B012345, B45, B45^2, B35, B35^2,
B25, B25^2, B15, B15^2, B05, B05^2, B012345, B012345^2, B012345^3)
sage: set(ch.defining_ideal().normal_basis()) == set(ch.basis())
True
sage: ch = matroids.catalog.Fano().chow_ring(QQ, False)
sage: ch.basis()
Family (1, Abcd, Aace, Aabf, Adef, Aadg, Abeg, Acfg, Aabcdefg,
Aabcdefg^2)
Family (1, Abcd, Aace, Adef, Aabf, Acfg, Abeg, Aadg, Aabcdefg,
Aabcdefg^2)
sage: set(ch.defining_ideal().normal_basis()) == set(ch.basis())
True
sage: ch = matroids.Wheel(3).chow_ring(QQ, True, 'atom-free')
sage: ch.basis()
Family (1, A0, A0*A012345, A2, A2*A012345, A3, A3*A012345, A23,
A23^2, A1, A1*A012345, A013, A013^2, A4, A4*A012345, A04, A04^2,
A124, A124^2, A5, A5*A012345, A025, A025^2, A15, A15^2, A345,
A345^2, A012345, A012345^2, A012345^3)
Family (1, A0, A0*A012345, A1, A1*A012345, A2, A2*A012345, A3,
A3*A012345, A23, A23^2, A013, A013^2, A4, A4*A012345, A124, A124^2,
A04, A04^2, A5, A5*A012345, A345, A345^2, A15, A15^2, A025, A025^2,
A012345, A012345^2, A012345^3)
sage: set(ch.defining_ideal().normal_basis()) == set(ch.basis())
True
"""
Expand Down Expand Up @@ -244,7 +244,7 @@
....: basis_deg[deg] = []
....: basis_deg[deg].append(b)
....:
sage: basis_deg

Check failure on line 247 in src/sage/matroids/chow_ring.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: {0: [1], 1: [A01, A12, A02, A012, A23, A13, A123, A03, A013, A023, A34, A24, A234, A14, A124, A134, A04, A014, A024, A034, A01234], 2: [A01*A01234, A12*A01234, A02*A01234, A012^2, A23*A01234, A13*A01234, A123^2, A03*A01234, A013^2, A023^2, A34*A01234, A24*A01234, A234^2, A14*A01234, A124^2, A134^2, A04*A01234, A014^2, A024^2, A034^2, A01234^2], 3: [A01234^3]}
{0: [1], 1: [A02, A12, A01, A012, A03, A13, A013, A23, A023,
A123, A04, A14, A014, A24, A024, A124, A34, A034, A134, A234,
A01234], 2: [A02*A01234, A12*A01234, A01*A01234, A012^2,
Expand All @@ -261,7 +261,7 @@
....: g_eq_maps[deg] = []
....: g_eq_maps[deg].extend([i*lefschetz_el for i in basis_deg[deg]])
....:
sage: g_eq_maps

Check failure on line 264 in src/sage/matroids/chow_ring.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: {0: [-2*A01 - 2*A02 - 2*A03 - 2*A04 - 2*A12 - 2*A13 - 2*A14 - 2*A23 - 2*A24 - 2*A34 - 6*A012 - 6*A013 - 6*A014 - 6*A023 - 6*A024 - 6*A034 - 6*A123 - 6*A124 - 6*A134 - 6*A234 - 20*A01234], 1: [2*A012^2 + 2*A013^2 + 2*A014^2 - 10*A01*A01234 + 2*A01234^2, 2*A012^2 + 2*A123^2 + 2*A124^2 - 10*A12*A01234 + 2*A01234^2, 2*A012^2 + 2*A023^2 + 2*A024^2 - 10*A02*A01234 + 2*A01234^2, -6*A012^2 + 2*A01*A01234 + 2*A02*A01234 + 2*A12*A01234, 2*A023^2 + 2*A123^2 + 2*A234^2 - 10*A23*A01234 + 2*A01234^2, 2*A013^2 + 2*A123^2 + 2*A134^2 - 10*A13*A01234 + 2*A01234^2, -6*A123^2 + 2*A12*A01234 + 2*A13*A01234 + 2*A23*A01234, 2*A013^2 + 2*A023^2 + 2*A034^2 - 10*A03*A01234 + 2*A01234^2, -6*A013^2 + 2*A01*A01234 + 2*A03*A01234 + 2*A13*A01234, -6*A023^2 + 2*A02*A01234 + 2*A03*A01234 + 2*A23*A01234, 2*A034^2 + 2*A134^2 + 2*A234^2 - 10*A34*A01234 + 2*A01234^2, 2*A024^2 + 2*A124^2 + 2*A234^2 - 10*A24*A01234 + 2*A01234^2, -6*A234^2 + 2*A23*A01234 + 2*A24*A01234 + 2*A34*A01234, 2*A014^2 + 2*A124^2 + 2*A134^2 - 10*A14*A01234 + 2*A01234^2, -6*A124^2 + 2*A12*A01234 + 2*A14*A01234 + 2*A24*A01234, -6*A134^2 + 2*A13*A01234 + 2*A14*A01234 + 2*A34*A01234, 2*A014^2 + 2*A024^2 + 2*A034^2 - 10*A04*A01234 + 2*A01234^2, -6*A014^2 + 2*A01*A01234 + 2*A04*A01234 + 2*A14*A01234, -6*A024^2 + 2*A02*A01234 + 2*A04*A01234 + 2*A24*A01234, -6*A034^2 + 2*A03*A01234 + 2*A04*A01234 + 2*A34*A01234, -2*A01*A01234 - 2*A02*A01234 - 2*A03*A01234 - 2*A04*A01234 - 2*A12*A01234 - 2*A13*A01234 - 2*A14*A01234 - 2*A23*A01234 - 2*A24*A01234 - 2*A34*A01234 - 20*A01234^2], 2: [2*A01234^3, 2*A01234^3, 2*A01234^3, 6*A01234^3, 2*A01234^3, 2*A01234^3, 6*A01234^3, 2*A01234^3, 6*A01234^3, 6*A01234^3, 2*A01234^3, 2*A01234^3, 6*A01234^3, 2*A01234^3, 6*A01234^3, 6*A01234^3, 2*A01234^3, 6*A01234^3, 6*A01234^3, 6*A01234^3, -20*A01234^3], 3: [0]}
{0: [-2*A01 - 2*A02 - 2*A03 - 2*A04 - 2*A12 - 2*A13 - 2*A14
- 2*A23 - 2*A24 - 2*A34 - 6*A012 - 6*A013 - 6*A014 - 6*A023
- 6*A024 - 6*A034 - 6*A123 - 6*A124 - 6*A134 - 6*A234
Expand Down Expand Up @@ -336,7 +336,7 @@
sage: v = ch.an_element(); v
-A01 - A02 - A03 - A04 - A05 - A012345
sage: v.to_vector()
(0, -1, -1, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0)
(0, -1, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, 0, -1, -1, 0)
"""
P = self.parent()
B = P.basis()
Expand Down Expand Up @@ -377,20 +377,20 @@
....: print(b, b.degree())
1 0
A01 1
A02 1
A12 1
A03 1
A13 1
A02 1
A23 1
A04 1
A14 1
A24 1
A13 1
A03 1
A34 1
A05 1
A15 1
A25 1
A35 1
A24 1
A14 1
A04 1
A45 1
A35 1
A25 1
A15 1
A05 1
A012345 1
A012345^2 2
sage: v = sum(ch.basis())
Expand Down Expand Up @@ -426,18 +426,18 @@
Bace^2 2
Bf 1
Bf*Babcdefg 2
Babf 1
Babf^2 2
Bdef 1
Bdef^2 2
Babf 1
Babf^2 2
Bg 1
Bg*Babcdefg 2
Badg 1
Badg^2 2
Bbeg 1
Bbeg^2 2
Bcfg 1
Bcfg^2 2
Bbeg 1
Bbeg^2 2
Badg 1
Badg^2 2
Babcdefg 1
Babcdefg^2 2
Babcdefg^3 3
Expand Down
7 changes: 4 additions & 3 deletions src/sage/matroids/chow_ring_ideal.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,14 +304,15 @@ def normal_basis(self, algorithm='', *args, **kwargs):
sage: ch = matroids.Z(3).chow_ring(QQ, False)
sage: I = ch.defining_ideal()
sage: I.normal_basis()
[1, Ax2x3y1, Ax1x3y2, Ax1x2y3, Ay1y2y3, Atx1y1, Atx2y2, Atx3y3, Atx1x2x3y1y2y3, Atx1x2x3y1y2y3^2]
[1, Ax2x3y1, Ax1x3y2, Ay1y2y3, Ax1x2y3, Atx3y3, Atx2y2, Atx1y1,
Atx1x2x3y1y2y3, Atx1x2x3y1y2y3^2]
sage: set(I.gens().ideal().normal_basis()) == set(I.normal_basis())
True
sage: ch = matroids.AG(2,3).chow_ring(QQ, False)
sage: I = ch.defining_ideal()
sage: I.normal_basis()
[1, A012, A236, A046, A156, A345, A247, A057, A137, A258, A678,
A038, A148, A012345678, A012345678^2]
[1, A012, A345, A236, A156, A046, A247, A137, A057, A678, A258,
A148, A038, A012345678, A012345678^2]
sage: set(I.gens().ideal().normal_basis()) == set(I.normal_basis())
True
"""
Expand Down
Loading