Skip to content

Commit 82a7adf

Browse files
committed
Reinstate EdegeDelete somewhat...
Use that in AcycleGraphQ test. More pytests-like tests moved out of doctests
1 parent a75ce74 commit 82a7adf

File tree

7 files changed

+131
-75
lines changed

7 files changed

+131
-75
lines changed

pymathics/graph/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
AdjacencyList,
4242
DirectedEdge,
4343
EdgeConnectivity,
44+
EdgeDelete,
4445
EdgeIndex,
4546
EdgeList,
4647
EdgeRules,
@@ -136,6 +137,7 @@
136137
"DirectedGraphQ",
137138
"EdgeConnectivity",
138139
"EdgeCount",
140+
"EdgeDelete",
139141
"EdgeIndex",
140142
"EdgeList",
141143
"EdgeRules",

pymathics/graph/base.py

Lines changed: 54 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
from collections import defaultdict
1010
from inspect import isgenerator
11-
from typing import Callable, Optional
11+
from typing import Callable, Optional, Union
1212

1313
from mathics.builtin.base import AtomBuiltin, Builtin
1414
from mathics.core.atoms import Atom, Integer, Integer0, Integer1, Integer2, String
15-
from mathics.core.convert.expression import ListExpression, from_python
15+
from mathics.core.convert.expression import ListExpression, from_python, to_mathics_list
1616
from mathics.core.element import BaseElement
1717
from mathics.core.expression import Expression
1818
from mathics.core.symbols import Symbol, SymbolList, SymbolTrue
@@ -283,7 +283,14 @@ def _not_a_vertex(self, expression, pos, evaluation):
283283
def _not_an_edge(self, expression, pos, evaluation):
284284
evaluation.message(self.get_name(), "inv", "edge", pos, expression)
285285

286-
def _build_graph(self, graph, evaluation, options, expr, quiet=False):
286+
def _build_graph(
287+
self,
288+
graph: Union["Graph", Expression],
289+
evaluation,
290+
options: dict,
291+
expr,
292+
quiet=False,
293+
) -> Optional["Graph"]:
287294
head = graph.get_head()
288295
if head is SymbolGraph and isinstance(graph, Atom) and hasattr(graph, "G"):
289296
return graph
@@ -384,9 +391,6 @@ def __init__(self, Gr, **kwargs):
384391
self.G = Gr
385392
self.mixed = kwargs.get("mixed", False)
386393

387-
# Number of nodes
388-
self.n: Optional[int] = None
389-
390394
# Here we define types that appear on some, but not all
391395
# graphs. So these are optional, which we given an initial
392396
# value of None
@@ -441,14 +445,13 @@ def coalesced_graph(self, evaluation):
441445

442446
return new_graph, "WEIGHT"
443447

444-
def delete_edges(self, edges_to_delete):
448+
def delete_edges(self, edges_to_delete) -> "Graph":
445449
G = self.G.copy()
446450
directed = G.is_directed()
447451

448-
edges_to_delete = list(_normalize_edges(edges_to_delete))
449-
edges_to_delete = self.edges.filter(edges_to_delete)
452+
normalized_edges_to_delete = list(_normalize_edges(edges_to_delete))
450453

451-
for edge in edges_to_delete:
454+
for edge in normalized_edges_to_delete:
452455
if edge.has_form("DirectedEdge", 2):
453456
if directed:
454457
u, v = edge.elements
@@ -461,12 +464,7 @@ def delete_edges(self, edges_to_delete):
461464
else:
462465
G.remove_edge(u, v)
463466

464-
edges = self.edges.clone()
465-
edges.delete(edges_to_delete)
466-
467-
return Graph(
468-
self.vertices, edges, G, self.layout, self.options, self.highlights
469-
)
467+
return Graph(G)
470468

471469
def delete_vertices(self, vertices_to_delete):
472470
G = self.G.copy()
@@ -817,8 +815,7 @@ class AdjacencyList(_NetworkXBuiltin):
817815
:Adjacency list:
818816
https://en.wikipedia.org/wiki/Adjacency_list</url> (<url>
819817
:NetworkX:
820-
https://networkx.org/documentation/networkx-2.8.8/reference/readwrite/adjlist.html</url>,
821-
<url>
818+
https://networkx.org/documentation/networkx-2.8.8/reference/readwrite/adjlist.html</url>, <url>
822819
:WMA:
823820
https://reference.wolfram.com/language/ref/AdjacencyList.html</url>)
824821
@@ -1418,8 +1415,8 @@ class VertexDelete(_NetworkXBuiltin):
14181415

14191416
summary_text = "remove a vertex"
14201417

1421-
def eval(self, graph, what, expression, evaluation, options):
1422-
"%(name)s[graph_, what_, OptionsPattern[%(name)s]]"
1418+
def eval(self, graph, what, expression, evaluation, options) -> Optional[Graph]:
1419+
"VertexDelete[graph_, what_, OptionsPattern[VertexDelete]]"
14231420
graph = self._build_graph(graph, evaluation, options, expression)
14241421
if graph:
14251422
from mathics.builtin import pattern_objects
@@ -1533,41 +1530,46 @@ class UndirectedEdge(Builtin):
15331530
# return mathics_graph.add_edges(*zip(*[_parse_item(what)]))
15341531

15351532

1536-
# class EdgeDelete(_NetworkXBuiltin):
1537-
# """
1538-
# >> Length[EdgeList[EdgeDelete[{a -> b, b -> c, c -> d}, b -> c]]]
1539-
# = 2
1533+
class EdgeDelete(_NetworkXBuiltin):
1534+
"""
1535+
<url>
1536+
:WMA:
1537+
https://reference.wolfram.com/language/ref/EdgeDelete.html</url>
15401538
1541-
# >> Length[EdgeList[EdgeDelete[{a -> b, b -> c, c -> b, c -> d}, b <-> c]]]
1542-
# = 4
1539+
<dl>
1540+
<dt>'EdgeDelete'[$g$, $edge$]
1541+
<dd>remove the edge $edge$.
1542+
</dl>
15431543
1544-
# >> Length[EdgeList[EdgeDelete[{a -> b, b <-> c, c -> d}, b -> c]]]
1545-
# = 3
1544+
>> g = Graph[{1 -> 2, 2 -> 3, 3 -> 1}, VertexLabels->True]
1545+
= -Graph-
15461546
1547-
# >> Length[EdgeList[EdgeDelete[{a -> b, b <-> c, c -> d}, c -> b]]]
1548-
# = 3
1547+
>> EdgeList[EdgeDelete[g, 2 -> 3]]
1548+
= {{1, 2}, {3, 1}}
15491549
1550-
# >> Length[EdgeList[EdgeDelete[{a -> b, b <-> c, c -> d}, b <-> c]]]
1551-
# = 2
1550+
## >> g = Graph[{4<->5,5<->7,7<->9,9<->5,2->4,4->6,6->2}, VertexLabels->True]
1551+
## = -Graph-
15521552
1553-
# >> EdgeDelete[{4<->5,5<->7,7<->9,9<->5,2->4,4->6,6->2}, _UndirectedEdge]
1554-
# = -Graph-
1555-
# """
1553+
## >> EdgeDelete[g, _UndirectedEdge]
1554+
## = -Graph-
1555+
"""
15561556

1557-
# def eval(self, graph, what, expression, evaluation, options):
1558-
# "%(name)s[graph_, what_, OptionsPattern[%(name)s]]"
1559-
# graph = self._build_graph(graph, evaluation, options, expression)
1560-
# if graph:
1561-
# from mathics.builtin import pattern_objects
1562-
1563-
# head_name = what.get_head_name()
1564-
# if head_name in pattern_objects:
1565-
# cases = Expression(
1566-
# SymbolCases, ListExpression(*graph.edges), what
1567-
# ).evaluate(evaluation)
1568-
# if cases.get_head_name() == "System`List":
1569-
# return graph.delete_edges(cases.elements)
1570-
# elif head_name == "System`List":
1571-
# return graph.delete_edges(what.elements)
1572-
# else:
1573-
# return graph.delete_edges([what])
1557+
summary_text = "remove an edge"
1558+
1559+
def eval(self, graph, what, expression, evaluation, options) -> Optional[Graph]:
1560+
"EdgeDelete[graph_, what_, OptionsPattern[EdgeDelete]]"
1561+
graph = self._build_graph(graph, evaluation, options, expression)
1562+
if graph:
1563+
from mathics.builtin import pattern_objects
1564+
1565+
head_name = what.get_head_name()
1566+
if head_name in pattern_objects:
1567+
cases = Expression(
1568+
SymbolCases, to_mathics_list(*graph.edges), what
1569+
).evaluate(evaluation)
1570+
if cases.get_head_name() == "System`List":
1571+
return graph.delete_edges(cases.elements)
1572+
elif head_name == "System`List":
1573+
return graph.delete_edges(what.elements)
1574+
else:
1575+
return graph.delete_edges([what])

pymathics/graph/eval/parametric.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,4 @@ def eval_hkn_harary(
8080
if not g:
8181
return None
8282
g.k = py_k
83-
g.n = py_n
8483
return g

pymathics/graph/parametric.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ class CycleGraph(_NetworkXBuiltin):
306306
def eval(
307307
self, n: Integer, expression, evaluation: Evaluation, options: dict
308308
) -> Optional[Graph]:
309-
"CycleGraph[n_Integer, OptionsPattern[CycleGragh]]"
309+
"CycleGraph[n_Integer, OptionsPattern[CycleGraph]]"
310310
n_int = n.value
311311
if n_int < 3:
312312
return eval_complete_graph(self, n, expression, evaluation, options)
@@ -447,7 +447,6 @@ def eval(
447447
)
448448
if not g:
449449
return None
450-
g.n = py_n
451450
g.m = py_m
452451
return g
453452

@@ -653,5 +652,5 @@ def eval(
653652
g = graph_helper(nx.star_graph, options, False, "spring", evaluation, 0, *args)
654653
if not g:
655654
return None
656-
g.G.n = n
655+
g.G.n = py_n
657656
return g

pymathics/graph/properties.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@ class AcyclicGraphQ(_NetworkXBuiltin):
1919
"""
2020
<url>
2121
:Acyclic graph:
22-
https://en.wikipedia.org/wiki/Acyclic_graph</url> (<url>
22+
https://en.wikipedia.org/wiki/Acyclic_graph</url> test (<url>
2323
:NetworkX:
2424
https://networkx.org/documentation/stable/reference/algorithms\
2525
/generated/networkx.algorithms.cycles.find_cycle.html
26-
</url>,
27-
<url>
26+
</url>, <url>
2827
:WMA:
2928
https://reference.wolfram.com/language/ref/AcyclicGraphQ.html</url>)
3029
@@ -34,33 +33,27 @@ class AcyclicGraphQ(_NetworkXBuiltin):
3433
</dl>
3534
3635
37-
>> g = Graph[{1 -> 2, 2 -> 3}]; AcyclicGraphQ[g]
38-
= True
39-
40-
>> g = Graph[{1 -> 2, 2 -> 3, 5 -> 2, 3 -> 4, 3 -> 5}]; AcyclicGraphQ[g]
41-
= False
36+
Create a directed graph with a cycle in it:
4237
43-
#> g = Graph[{1 -> 2, 2 -> 3, 5 -> 2, 3 -> 4, 5 -> 3}]; AcyclicGraphQ[g]
44-
= True
38+
>> g = Graph[{1 -> 2, 2 -> 3, 5 -> 2, 3 -> 4, 3 -> 5}, VertexLabels->True]
39+
= -Graph-
4540
46-
#> g = Graph[{1 -> 2, 2 -> 3, 5 -> 2, 3 -> 4, 5 <-> 3}]; AcyclicGraphQ[g]
41+
>> AcyclicGraphQ[g]
4742
= False
4843
49-
#> g = Graph[{1 <-> 2, 2 <-> 3, 5 <-> 2, 3 <-> 4, 5 <-> 3}]; AcyclicGraphQ[g]
50-
= False
44+
Remove a cycle edge:
5145
52-
#> g = Graph[{}]; AcyclicGraphQ[{}]
53-
= False
46+
>> g = EdgeDelete[g, 5 -> 2]; EdgeList[g]
47+
= {{1, 2}, {2, 3}, {3, 4}, {3, 5}}
5448
55-
#> AcyclicGraphQ["abc"]
56-
= False
57-
: Expected a graph at position 1 in AcyclicGraphQ[abc].
49+
>> AcyclicGraphQ[g]
50+
= True
5851
"""
5952

6053
summary_text = "test if is an acyclic graph"
6154

6255
def eval(self, graph, expression, evaluation, options):
63-
"%(name)s[graph_, OptionsPattern[%(name)s]]"
56+
"AcyclicGraphQ[graph_, OptionsPattern[AcyclicGraphQ]]"
6457
graph = self._build_graph(graph, evaluation, options, expression, quiet=False)
6558
if not graph or graph.empty():
6659
return SymbolFalse

test/test_base.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,23 @@ def setup_module(module):
3030
)
3131
def test_degree_centrality(str_expr, str_expected, msg):
3232
check_evaluation(str_expr, str_expected, failure_message=msg)
33+
34+
35+
@pytest.mark.parametrize(
36+
("str_expr", "str_expected", "msg"),
37+
[
38+
(None, None, None),
39+
(
40+
"Length[EdgeList[EdgeDelete[{a -> b, b -> c, c -> d}, b -> c]]]",
41+
"2",
42+
None,
43+
),
44+
# ("Length[EdgeList[EdgeDelete[{a -> b, b -> c, c -> b, c -> d}, b <-> c]]]", "4", None),
45+
("Length[EdgeList[EdgeDelete[{a -> b, b <-> c, c -> d}, b -> c]]]", "3", None),
46+
("Length[EdgeList[EdgeDelete[{a -> b, b <-> c, c -> d}, c -> b]]]", "3", None),
47+
("Length[EdgeList[EdgeDelete[{a -> b, b <-> c, c -> d}, b <-> c]]]", "2", None),
48+
(None, None, None),
49+
],
50+
)
51+
def test_edge_delete(str_expr, str_expected, msg):
52+
check_evaluation(str_expr, str_expected, failure_message=msg)

test/test_properties.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Unit tests for pymathics.graph.measures_and_metrics
3+
"""
4+
5+
from test.helper import check_evaluation, evaluate_value
6+
7+
8+
def setup_module(module):
9+
"""Load pymathics.graph"""
10+
assert evaluate_value('LoadModule["pymathics.graph"]') == "pymathics.graph"
11+
12+
13+
def test_AcyclicQ():
14+
for str_expr, str_expected, mess in [
15+
(
16+
"AcyclicGraphQ[Graph[{1 -> 2, 2 -> 3, 5 -> 2, 3 -> 4, 5 -> 3}]]",
17+
"True",
18+
None,
19+
),
20+
(
21+
"AcyclicGraphQ[Graph[{1 -> 2, 2 -> 3, 5 -> 2, 3 -> 4, 5 <-> 3}]]",
22+
"False",
23+
None,
24+
),
25+
(
26+
"AcyclicGraphQ[Graph[{1 <-> 2, 2 <-> 3, 5 <-> 2, 3 <-> 4, 5 <-> 3}]]",
27+
"False",
28+
None,
29+
),
30+
# (
31+
# "AcyclicGraphQ[Graph[{}]]",
32+
# "False",
33+
# None,
34+
# ),
35+
(
36+
'AcyclicGraphQ["abc"]',
37+
"False",
38+
["Expected a graph at position 1 in AcyclicGraphQ[abc]."],
39+
),
40+
]:
41+
check_evaluation(str_expr, str_expected, expected_messages=mess)

0 commit comments

Comments
 (0)