Skip to content
This repository has been archived by the owner on Jan 30, 2023. It is now read-only.

Commit

Permalink
trac #15572: Exceptions when multiedges/loops are not supported
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanncohen committed Dec 23, 2013
1 parent 13c6ffc commit 843473a
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 34 deletions.
1 change: 0 additions & 1 deletion src/sage/graphs/chrompoly.pyx
Expand Up @@ -81,7 +81,6 @@ def chromatic_polynomial(G, return_tree_basis = False):
sage: min((i for i in xrange(11) if P(i) > 0)) == G.chromatic_number()
True
"""

if not G.is_connected():
return prod([chromatic_polynomial(g) for g in G.connected_components_subgraphs()])
R = ZZ['x']
Expand Down
1 change: 1 addition & 0 deletions src/sage/graphs/comparability.pyx
Expand Up @@ -528,6 +528,7 @@ def is_comparability(g, algorithm = "greedy", certificate = False, check = True)
sage: [len([g for g in graphs(i) if is_comparability(g, certificate = True)[0]]) for i in range(7)]
[1, 1, 2, 4, 11, 33, 144]
"""
g._scream_if_not_simple()
if g.size() == 0:
if certificate:
from sage.graphs.digraph import DiGraph
Expand Down
1 change: 1 addition & 0 deletions src/sage/graphs/digraph.py
Expand Up @@ -1028,6 +1028,7 @@ def is_directed_acyclic(self, certificate = False):
... for i in range(50)) # long time
True
"""
self._scream_if_not_simple(allow_multiple_edges=True)
return self._backend.is_directed_acyclic(certificate = certificate)

def to_directed(self):
Expand Down
109 changes: 95 additions & 14 deletions src/sage/graphs/generic_graph.py
Expand Up @@ -315,6 +315,8 @@
class GenericGraph(GenericGraph_pyx):
"""
Base class for graphs and digraphs.

.. autofunction:: _scream_if_not_simple
"""

# Nice defaults for plotting arrays of graphs (see sage.misc.functional.show)
Expand Down Expand Up @@ -571,6 +573,7 @@ def _bit_vector(self):
sage: G.num_edges()
15
"""
self._scream_if_not_simple()
vertices = self.vertices()
n = len(vertices)
if self._directed:
Expand Down Expand Up @@ -812,6 +815,74 @@ def __copy__(self, implementation='c_graph', data_structure=None,

copy = __copy__

def _scream_if_not_simple(self, allow_loops=False, allow_multiple_edges=False):
r"""
Raises an exception if the graph is not simple.

This function is called by some functions of the Graph library when they
have not been written for simple graphs only (i.e. no loops nor multiple
edges). It raises an exception inviting the user to convert the graph to
a simple graph first, before calling the function again.

Note that this function does not checl the existence of loops or
multiple edges, which would take linear time : it merely checks that the
graph *does not allow* multiple edges nor loops, which takes a constant
time.

INPUT:

- ``allow_loops`` (boolean) -- whether to tolerate loops. Set to
``False`` by default.

- ``allow_multiple_edges`` (boolean) -- whether to tolerate multiple
edges. Set to ``False`` by default.

.. SEEALSO::

* :meth:`allow_loops`
* :meth:`allow_multiple_edges`

EXAMPLES::

sage: g = graphs.PetersenGraph()
sage: g._scream_if_not_simple()
sage: g.allow_loops(True)
sage: g.allow_multiple_edges(True)
sage: g._scream_if_not_simple()
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with multiedges/loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges/loops using allow_multiple_edges() and allow_loops().
sage: g.allow_multiple_edges(True)
sage: g._scream_if_not_simple()
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with multiedges/loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges/loops using allow_multiple_edges() and allow_loops().
sage: g._scream_if_not_simple(allow_loops=True)
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with multiedges. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow multiedges using allow_multiple_edges().
sage: g._scream_if_not_simple(allow_multiple_edges=True)
Traceback (most recent call last):
...
ValueError: This method is not known to work on graphs with loops. Perhaps this method can be updated to handle them, but in the meantime if you want to use it please disallow loops using allow_loops().
"""
if ((not allow_loops and self.allows_loops()) or
(not allow_multiple_edges and self.allows_multiple_edges())):
if allow_loops is False and allow_multiple_edges is False:
name = "multiedges/loops"
functions = "allow_multiple_edges() and allow_loops()"
elif allow_loops is False:
name = "loops"
functions = "allow_loops()"
else:
name = "multiedges"
functions = "allow_multiple_edges()"
msg = ("This method is not known to work on graphs with "+name+". "+
"Perhaps this method can be updated to handle them, but in the "+
"meantime if you want to use it please disallow "+name+" using "+
functions+".")
raise ValueError(msg)

def networkx_graph(self, copy=True):
"""
Creates a new NetworkX graph from the Sage graph.
Expand Down Expand Up @@ -3497,7 +3568,6 @@ def genus(self, set_embedding=True, on_embedding=None, minimal=True, maximal=Fal

INPUT:


- ``set_embedding (boolean)`` - whether or not to
store an embedding attribute of the computed (minimal) genus of the
graph. (Default is True).
Expand Down Expand Up @@ -4371,6 +4441,7 @@ def steiner_tree(self,vertices, weighted = False, solver = None, verbose = 0):
...
ValueError: The given vertices do not all belong to the same connected component. This problem has no solution !
"""
self._scream_if_not_simple(allow_loops=True)

if self.is_directed():
from sage.graphs.all import Graph
Expand Down Expand Up @@ -4778,7 +4849,7 @@ def edge_cut(self, s, t, value_only=True, use_edge_labels=False, vertices=False,
sage: g.edge_cut(1, 2, value_only=True, method = "LP")
3
"""

self._scream_if_not_simple(allow_loops=True)
if vertices:
value_only = False

Expand Down Expand Up @@ -5079,6 +5150,7 @@ def multiway_cut(self, vertices, value_only = False, use_edge_labels = False, so
sage: set(C) == set(g.edges())
True
"""
self._scream_if_not_simple(allow_loops=True)
from sage.numerical.mip import MixedIntegerLinearProgram
from itertools import combinations, chain

Expand Down Expand Up @@ -5208,6 +5280,7 @@ def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver
12

"""
self._scream_if_not_simple(allow_loops=True)
g=self

if vertices:
Expand All @@ -5231,7 +5304,6 @@ def max_cut(self, value_only=True, use_edge_labels=False, vertices=False, solver
in_set = p.new_variable(dim=2)
in_cut = p.new_variable(dim=1)


# A vertex has to be in some set
for v in g:
p.add_constraint(in_set[0][v]+in_set[1][v],max=1,min=1)
Expand Down Expand Up @@ -5467,6 +5539,8 @@ def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP",
sage: G.longest_path().edges()
[(0, 1, None), (2, 0, None)]
"""
self._scream_if_not_simple()

if use_edge_labels:
algorithm = "MILP"
if algorithm not in ("backtrack", "MILP"):
Expand Down Expand Up @@ -5671,7 +5745,6 @@ def longest_path(self, s=None, t=None, use_edge_labels=False, algorithm="MILP",
else:
return g


def traveling_salesman_problem(self, use_edge_labels = False, solver = None, constraint_generation = None, verbose = 0, verbose_constraints = False):
r"""
Solves the traveling salesman problem (TSP)
Expand Down Expand Up @@ -5840,6 +5913,8 @@ def traveling_salesman_problem(self, use_edge_labels = False, solver = None, con
... break

"""
self._scream_if_not_simple()

if constraint_generation is None:
if self.density() > .7:
constraint_generation = False
Expand All @@ -5864,8 +5939,6 @@ def traveling_salesman_problem(self, use_edge_labels = False, solver = None, con
if not self.strong_orientation().is_strongly_connected():
raise ValueError("The given graph is not hamiltonian")



############################
# Deal with multiple edges #
############################
Expand Down Expand Up @@ -6579,7 +6652,7 @@ def flow(self, x, y, value_only=True, integer=False, use_edge_labels=True, verte
sage: abs(flow_ff-flow_lp) < 0.01
True
"""

self._scream_if_not_simple(allow_loops=True)
if vertex_bound and method == "FF":
raise ValueError("This method does not support both vertex_bound=True and method=\"FF\".")

Expand Down Expand Up @@ -6902,7 +6975,7 @@ def multicommodity_flow(self, terminals, integer=True, use_edge_labels=False,ver
...
ValueError: The multiflow problem has no solution
"""

self._scream_if_not_simple(allow_loops=True)
from sage.numerical.mip import MixedIntegerLinearProgram
g=self
p=MixedIntegerLinearProgram(maximization=True, solver = solver)
Expand Down Expand Up @@ -7448,6 +7521,7 @@ def edge_connectivity(self, value_only=True, use_edge_labels=False, vertices=Fal
sage: g.edge_connectivity()
0.0
"""
self._scream_if_not_simple(allow_loops=True)
g=self

if vertices:
Expand Down Expand Up @@ -8454,7 +8528,6 @@ def merge_vertices(self,vertices):
add_edges.append((vertices[0],v,l))
self.add_edges(add_edges)


### Edge handlers

def add_edge(self, u, v=None, label=None):
Expand Down Expand Up @@ -9746,8 +9819,6 @@ def is_regular(self, k = None):

return True



### Substructures

def subgraph(self, vertices=None, edges=None, inplace=False,
Expand Down Expand Up @@ -10662,6 +10733,7 @@ def is_chordal(self, certificate = False, algorithm = "B"):
sage: g1.is_isomorphic(graphs.CycleGraph(g1.order()))
True
"""
self._scream_if_not_simple()

# If the graph is not connected, we are computing the result on each component
if not self.is_connected():
Expand Down Expand Up @@ -10879,6 +10951,7 @@ def is_circulant(self, certificate = False):
sage: Graph([(0,0)]).is_circulant(certificate = True)
(True, (1, [0]))
"""
self._scream_if_not_simple(allow_loops=True)
# Stupid cases
if self.order() <= 1:
if certificate:
Expand Down Expand Up @@ -10974,7 +11047,7 @@ def is_interval(self, certificate = False):
possible embedding (we can actually compute all of them
through the PQ-Tree structures)::

sage: g = Graph(':S__@_@A_@AB_@AC_@ACD_@ACDE_ACDEF_ACDEFG_ACDEGH_ACDEGHI_ACDEGHIJ_ACDEGIJK_ACDEGIJKL_ACDEGIJKLMaCEGIJKNaCEGIJKNaCGIJKNPaCIP')
sage: g = Graph(':S__@_@A_@AB_@AC_@ACD_@ACDE_ACDEF_ACDEFG_ACDEGH_ACDEGHI_ACDEGHIJ_ACDEGIJK_ACDEGIJKL_ACDEGIJKLMaCEGIJKNaCEGIJKNaCGIJKNPaCIP', loops=False, multiedges=False)
sage: d = g.is_interval(certificate = True)
sage: print d # not tested
{0: (0, 20), 1: (1, 9), 2: (2, 36), 3: (3, 5), 4: (4, 38), 5: (6, 21), 6: (7, 27), 7: (8, 12), 8: (10, 29), 9: (11, 16), 10: (13, 39), 11: (14, 31), 12: (15, 32), 13: (17, 23), 14: (18, 22), 15: (19, 33), 16: (24, 25), 17: (26, 35), 18: (28, 30), 19: (34, 37)}
Expand All @@ -10994,6 +11067,7 @@ def is_interval(self, certificate = False):
-- Implementation of PQ-Trees.

"""
self._scream_if_not_simple()

# An interval graph first is a chordal graph. Without this,
# there is no telling how we should find its maximal cliques,
Expand Down Expand Up @@ -11106,7 +11180,7 @@ def is_gallai_tree(self):
sage: g.is_gallai_tree()
True
"""

self._scream_if_not_simple()
if not self.is_connected():
return False

Expand Down Expand Up @@ -11194,7 +11268,7 @@ def is_independent_set(self, vertices=None):
sage: graphs.CycleGraph(4).is_independent_set([1,2,3])
False
"""
return self.subgraph(vertices).to_simple().size()==0
return self.subgraph(vertices).size()==0

def is_subgraph(self, other, induced=True):
"""
Expand Down Expand Up @@ -11283,6 +11357,7 @@ def is_subgraph(self, other, induced=True):
if induced:
return other.subgraph(self.vertices()) == self
else:
self._scream_if_not_simple(allow_loops=True)
return all(other.has_edge(e) for e in self.edge_iterator())

### Cluster
Expand Down Expand Up @@ -13300,6 +13375,7 @@ def complement(self):
"""
if self.has_multiple_edges():
raise TypeError('complement not well defined for (di)graphs with multiple edges')
self._scream_if_not_simple()
from copy import copy
G = copy(self)
G.delete_edges(G.edges())
Expand Down Expand Up @@ -13487,6 +13563,7 @@ def cartesian_product(self, other):
sage: B.is_isomorphic( Q.subgraph(V) )
True
"""
self._scream_if_not_simple(allow_loops=True)
if self._directed and other._directed:
from sage.graphs.all import DiGraph
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
Expand Down Expand Up @@ -13568,6 +13645,7 @@ def tensor_product(self, other):
sage: T.is_isomorphic( digraphs.DeBruijn( 2*3, 3) )
True
"""
self._scream_if_not_simple(allow_loops=True)
if self._directed and other._directed:
from sage.graphs.all import DiGraph
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
Expand Down Expand Up @@ -13635,6 +13713,7 @@ def lexicographic_product(self, other):
sage: T.is_isomorphic( J.lexicographic_product(I) )
False
"""
self._scream_if_not_simple(allow_loops=True)
if self._directed and other._directed:
from sage.graphs.all import DiGraph
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
Expand Down Expand Up @@ -13713,6 +13792,7 @@ def strong_product(self, other):
sage: if product_size != expected:
... print "Something is really wrong here...", product_size, "!=", expected
"""
self._scream_if_not_simple(allow_loops=True)
if self._directed and other._directed:
from sage.graphs.all import DiGraph
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
Expand Down Expand Up @@ -13781,6 +13861,7 @@ def disjunctive_product(self, other):
sage: T.is_isomorphic( J.disjunctive_product(I) )
True
"""
self._scream_if_not_simple(allow_loops=True)
if self._directed and other._directed:
from sage.graphs.all import DiGraph
G = DiGraph( loops = (self.has_loops() or other.has_loops()) )
Expand Down
3 changes: 3 additions & 0 deletions src/sage/graphs/generic_graph_pyx.pyx
Expand Up @@ -509,6 +509,9 @@ cdef class SubgraphSearch:
if sum([G.is_directed(), H.is_directed()]) == 1:
raise ValueError("One graph can not be directed while the other is not.")

G._scream_if_not_simple(allow_loops=True)
H._scream_if_not_simple(allow_loops=True)

self._initialization()

def __iter__(self):
Expand Down

0 comments on commit 843473a

Please sign in to comment.