diff --git a/tests/generate/hardcard.py b/tests/generate/hardcard.py new file mode 100644 index 0000000..c7785ff --- /dev/null +++ b/tests/generate/hardcard.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +""" +Generate a graph that belongs to a class of worst-case graphs +described by Gabow. + +Reference: H. N. Gabow, "An efficient implementation of Edmonds' + algorithm for maximum matching on graphs", JACM 23 + (1976), pp. 221-234. + +Based on Fortran program "hardcard.f" by R. Bruce Mattingly, 1991. +Rewritten in Python by Joris van Rantwijk, 2023. + +For the original Fortran code, see + http://archive.dimacs.rutgers.edu/pub/netflow/generators/matching/hardcard.f + +Output to stdout in DIMACS edge format. +All edges have weight 1. + +Input parameter: K +Number of vertices: N = 6*K +Number of edges: M = 8*K*K + +The graph is constructed so that vertices 1 - 4*K form a complete subgraph. +For 1 <= I <= 2*K, vertex (2*I-1) is joined to vertex (4*K+I). +""" + +import argparse + + +def main(): + + parser = argparse.ArgumentParser() + parser.description = "Generate a difficult graph" + + parser.add_argument("k", + action="store", + type=int, + help="size parameter; N = 6*K, M = 4*K*K") + args = parser.parse_args() + + if args.k < 1: + print("ERROR: K must be at least 1", file=sys.stderr) + sys.exit(1) + + k = args.k + n = 6 * k + m = 8 * k * k + + print(f"p edge {n} {m}") + + for i in range(1, 4*k): + for j in range(i + 1, 4*k + 1): + print(f"e {i} {j} 1") + if i % 2 == 1: + j = 4 * k + (i + 1) // 2 + print(f"e {i} {j} 1") + + +if __name__ == "__main__": + main() + diff --git a/tests/generate/make_slow_graph.py b/tests/generate/make_slow_graph.py index ee4ca84..e18100c 100644 --- a/tests/generate/make_slow_graph.py +++ b/tests/generate/make_slow_graph.py @@ -70,12 +70,14 @@ def write_dimacs_graph( def make_dense_slow_graph(n: int) -> list[tuple[int, int, int]]: - """Generate a dense (not complete) graph with N vertices. + """Generate a dense graph with N vertices. + + The graph contains O(n**2) edges and triggers O(n**2) delta steps. N must be divisible by 4. - Number of edges = M = (N**2/16 + N/2). - Number of delta steps required to solve the matching = (M - 1). + Number of edges = M = N**2/16 + N/2 + Number of delta steps required to solve the matching = M - 1 """ assert n % 4 == 0 @@ -118,10 +120,12 @@ def make_dense_slow_graph(n: int) -> list[tuple[int, int, int]]: def make_sparse_slow_graph(n: int) -> list[tuple[int, int, int]]: """Generate a sparse graph with N vertices. + The graph contains just O(n) edges but still triggers O(n**2) delta steps. + N must be 4 modulo 8. - Number of edges = M = (5/4 * N - 3). - Number of delta steps required to solve the matching ~ (N**2 / 16). + Number of edges = M = 5/4 * N - 3 + Number of delta steps required to solve the matching = N**2/16 + 3/4*N - 3 """ assert n >= 12 @@ -233,20 +237,40 @@ def make_sparse_slow_graph(n: int) -> list[tuple[int, int, int]]: return edges +def make_chain_graph(n: int) -> list[tuple[int, int, int]]: + """Generate a graph with N vertices connected into a simple chain. + + The graph contains O(n) edges and triggers O(n) delta steps. + To force a large number of delta steps, N must be even. + + Number of edges = M = N - 1 + Number of delta steps required to solve the matching = N - 2 + """ + + assert n >= 2 + + edges: list[tuple[int, int, int]] = [] + + for i in range(n - 1): + w = n + i % 2 + edges.append((i, i + 1, w)) + + return edges + + def main() -> int: """Main program.""" parser = argparse.ArgumentParser() parser.description = "Generate a difficult graph." - parser.add_argument("--structure", - action="store", - choices=("sparse", "dense"), - default="sparse", - help="choose graph structure") parser.add_argument("--check", action="store_true", help="solve the matching and count delta steps") + parser.add_argument("structure", + action="store", + choices=("sparse", "dense", "chain"), + help="graph structure") parser.add_argument("n", action="store", type=int, @@ -283,6 +307,18 @@ def main() -> int: edges = make_dense_slow_graph(args.n) + elif args.structure == "chain": + + if args.n < 2: + print("ERROR: Number of vertices must be >= 2", file=sys.stderr) + return 1 + + if args.n % 2 != 0: + print("ERROR: Number of vertices must be even", file=sys.stderr) + return 1 + + edges = make_chain_graph(args.n) + else: assert False diff --git a/tests/generate/triangles.py b/tests/generate/triangles.py new file mode 100644 index 0000000..87f2c6e --- /dev/null +++ b/tests/generate/triangles.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +r""" +Generate a graph that consists of interconnected triangles. + +The graph is a chain of triangles, connected either at 1 vertex +or at 3 vertices. + +Example of triangles connected at one vertex: + + [1]-------[4] [7] [10]-------[13] + | \ | \ | \ / | | \ + | [3] | [6] | [9]---[12] | | [15] + | / | / | / \ | | / + [2] [5]-------[8] [11] [14]----- + +Example of triangles connected at 3 vertices: + + [1]-------[4]-------[7]-------[10]-------[13] + | \ | \ | \ | \ | \ + | [3]----|--[6]----|--[9]----|--[12]----|--[15] + | / | / | / | / | / + [2]-------[5]-------[8]-------[11]-------[14] + +Based on Fortran programs "t.f" and "tt.f" +by N. Ritchey and B. Mattingly, Youngstown State University, 1991. + +Rewritten in Python by Joris van Rantwijk, 2023. + +For the original Fortran code, see + http://archive.dimacs.rutgers.edu/pub/netflow/generators/matching/t.f + http://archive.dimacs.rutgers.edu/pub/netflow/generators/matching/tt.f + +Output to stdout in DIMACS edge format. +All edges have weight 1. + +Input parameter: K = number of triangles +Input parameter: C = 1 to connect triangles by 1 corner + C = 3 to connect triangles by 3 corners +Number of vertices: N = 3*K +Number of edges: M = 3*K + C*(K-1) +""" + +import argparse + + +def main(): + + parser = argparse.ArgumentParser() + parser.description = ( + "Generate a graph that consists of interconnected triangles.") + + parser.add_argument("k", + action="store", + type=int, + help="size parameter; N = 3*K, M = 3*K+C*(K-1)") + parser.add_argument("c", + action="store", + type=int, + choices=(1, 3), + help="number of corners to connect") + + args = parser.parse_args() + + if args.k < 1: + print("ERROR: K must be at least 1", file=sys.stderr) + sys.exit(1) + + k = args.k + n = 3 * k + m = 3 * k + args.c * (k - 1) + + print(f"p edge {n} {m}") + + for i in range(k): + x = 3 * i + 1 + print(f"e {x} {x+1} 1") + print(f"e {x} {x+2} 1") + print(f"e {x+1} {x+2} 1") + + if args.c == 1: + for i in range(k - 1): + x = 3 * i + i % 3 + 1 + print(f"e {x} {x+3} 1") + + elif args.c == 3: + for x in range(1, 3 * k - 2): + print(f"e {x} {x+3} 1") + + +if __name__ == "__main__": + main()