Skip to content

Commit f53e808

Browse files
author
Release Manager
committed
gh-36571: Add method to check whether a (di)graph is geodetic A (di)graph is geodetic if there exists only one shortest path between any pair of its vertices. We add a method to check this property on unweighted (di)graphs. ### 📝 Checklist - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - #12345: short description why this is a dependency - #34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> URL: #36571 Reported by: David Coudert Reviewer(s): David Coudert, Kwankyu Lee
2 parents bb7d146 + 2a623b5 commit f53e808

File tree

2 files changed

+158
-6
lines changed

2 files changed

+158
-6
lines changed

src/sage/graphs/convexity_properties.pyx

Lines changed: 156 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@ the following methods:
1313
:meth:`ConvexityProperties.hull` | Return the convex hull of a set of vertices
1414
:meth:`ConvexityProperties.hull_number` | Compute the hull number of a graph and a corresponding generating set
1515
:meth:`geodetic_closure`| Return the geodetic closure of a set of vertices
16+
:meth:`is_geodetic` | Check whether the input (di)graph is geodetic
1617
17-
These methods can be used through the :class:`ConvexityProperties` object
18-
returned by :meth:`Graph.convexity_properties`.
19-
20-
AUTHORS:
21-
22-
- Nathann Cohen
18+
Some of these methods can be used through the :class:`ConvexityProperties`
19+
object returned by :meth:`Graph.convexity_properties`.
2320
2421
Methods
2522
-------
@@ -674,3 +671,156 @@ def geodetic_closure(G, S):
674671
free_short_digraph(sd)
675672

676673
return ret
674+
675+
676+
def is_geodetic(G):
677+
r"""
678+
Check whether the input (di)graph is geodetic.
679+
680+
A graph `G` is *geodetic* if there exists only one shortest path between
681+
every pair of its vertices. This can be checked in time `O(nm)` in
682+
unweighted (di)graphs with `n` nodes and `m` edges. Examples of geodetic
683+
graphs are trees, cliques and odd cycles. See the
684+
:wikipedia:`Geodetic_graph` for more details.
685+
686+
(Di)graphs with multiple edges are not considered geodetic.
687+
688+
INPUT:
689+
690+
- ``G`` -- a graph or a digraph
691+
692+
EXAMPLES:
693+
694+
Trees, cliques and odd cycles are geodetic::
695+
696+
sage: T = graphs.RandomTree(20)
697+
sage: T.is_geodetic()
698+
True
699+
sage: all(graphs.CompleteGraph(n).is_geodetic() for n in range(8))
700+
True
701+
sage: all(graphs.CycleGraph(n).is_geodetic() for n in range(3, 16, 2))
702+
True
703+
704+
Even cycles of order at least 4 are not geodetic::
705+
706+
sage: all(graphs.CycleGraph(n).is_geodetic() for n in range(4, 17, 2))
707+
False
708+
709+
The Petersen graph is geodetic::
710+
711+
sage: P = graphs.PetersenGraph()
712+
sage: P.is_geodetic()
713+
True
714+
715+
Grid graphs are not geodetic::
716+
717+
sage: G = graphs.Grid2dGraph(2, 3)
718+
sage: G.is_geodetic()
719+
False
720+
721+
This method is also valid for digraphs::
722+
723+
sage: G = DiGraph(graphs.PetersenGraph())
724+
sage: G.is_geodetic()
725+
True
726+
sage: G = digraphs.Path(5)
727+
sage: G.add_path([0, 'a', 'b', 'c', 4])
728+
sage: G.is_geodetic()
729+
False
730+
731+
TESTS::
732+
733+
sage: all(g.is_geodetic() for g in graphs(3))
734+
True
735+
sage: all((2*g).is_geodetic() for g in graphs(3))
736+
True
737+
sage: G = graphs.CycleGraph(5)
738+
sage: G.allow_loops(True)
739+
sage: G.add_edges([(u, u) for u in G])
740+
sage: G.is_geodetic()
741+
True
742+
sage: G.allow_multiple_edges(True)
743+
sage: G.is_geodetic()
744+
True
745+
sage: G.add_edge(G.random_edge())
746+
sage: G.is_geodetic()
747+
False
748+
"""
749+
if G.has_multiple_edges():
750+
return False
751+
752+
if G.order() < 4:
753+
return True
754+
755+
# Copy the graph as a short digraph
756+
cdef int n = G.order()
757+
cdef short_digraph sd
758+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G))
759+
760+
# Allocate some data structures
761+
cdef MemoryAllocator mem = MemoryAllocator()
762+
cdef uint32_t * distances = <uint32_t *> mem.malloc(n * sizeof(uint32_t))
763+
cdef uint32_t * waiting_list = <uint32_t *> mem.malloc(n * sizeof(uint32_t))
764+
if not distances or not waiting_list:
765+
free_short_digraph(sd)
766+
raise MemoryError()
767+
cdef bitset_t seen
768+
bitset_init(seen, n)
769+
770+
# We now explore geodesics between vertices in S, and we avoid visiting
771+
# twice the geodesics between u and v
772+
773+
cdef uint32_t source, u, v
774+
cdef uint32_t waiting_beginning
775+
cdef uint32_t waiting_end
776+
cdef uint32_t * p_tmp
777+
cdef uint32_t * end
778+
cdef uint32_t ** p_vertices = sd.neighbors
779+
780+
for source in range(n):
781+
782+
# Compute distances from source using BFS
783+
bitset_clear(seen)
784+
bitset_add(seen, source)
785+
distances[source] = 0
786+
waiting_beginning = 0
787+
waiting_end = 0
788+
waiting_list[waiting_beginning] = source
789+
790+
# For as long as there are vertices left to explore
791+
while waiting_beginning <= waiting_end:
792+
793+
# We pick the first one
794+
v = waiting_list[waiting_beginning]
795+
p_tmp = p_vertices[v]
796+
end = p_vertices[v + 1]
797+
798+
# and we iterate over all the outneighbors u of v
799+
while p_tmp < end:
800+
u = p_tmp[0]
801+
802+
# If we notice one of these neighbors is not seen yet, we set
803+
# its parameters and add it to the queue to be explored later.
804+
# Otherwise, we check whether we have detected a second shortest
805+
# path between source and v.
806+
if not bitset_in(seen, u):
807+
distances[u] = distances[v] + 1
808+
bitset_add(seen, u)
809+
waiting_end += 1
810+
waiting_list[waiting_end] = u
811+
elif distances[u] == distances[v] + 1:
812+
# G is not geodetic
813+
bitset_free(seen)
814+
free_short_digraph(sd)
815+
return False
816+
817+
p_tmp += 1
818+
819+
# We go to the next vertex in the queue
820+
waiting_beginning += 1
821+
822+
bitset_free(seen)
823+
free_short_digraph(sd)
824+
825+
# The graph is geodetic
826+
return True

src/sage/graphs/generic_graph.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
:meth:`~GenericGraph.is_gallai_tree` | Return whether the current graph is a Gallai tree.
186186
:meth:`~GenericGraph.is_clique` | Check whether a set of vertices is a clique
187187
:meth:`~GenericGraph.is_cycle` | Check whether ``self`` is a (directed) cycle graph.
188+
:meth:`~GenericGraph.is_geodetic` | Check whether the input (di)graph is geodetic.
188189
:meth:`~GenericGraph.is_independent_set` | Check whether ``vertices`` is an independent set of ``self``
189190
:meth:`~GenericGraph.is_transitively_reduced` | Test whether the digraph is transitively reduced.
190191
:meth:`~GenericGraph.is_equitable` | Check whether the given partition is equitable with respect to self.
@@ -24415,6 +24416,7 @@ def is_self_complementary(self):
2441524416
from sage.graphs.traversals import lex_UP
2441624417
from sage.graphs.traversals import lex_DFS
2441724418
from sage.graphs.traversals import lex_DOWN
24419+
is_geodetic = LazyImport('sage.graphs.convexity_properties', 'is_geodetic')
2441824420

2441924421
def katz_matrix(self, alpha, nonedgesonly=False, vertices=None):
2442024422
r"""

0 commit comments

Comments
 (0)