def run(self): """ This initially kicks off the actual operation. """ debug("starting to run") self._parse_args() self.best_graph = GolfGraph(self.args.order, self.args.degree) if self.args.edges: self.load_edges() else: self.best_graph.add_as_many_random_edges_as_possible() print("lower bound diameter:", self.best_graph.diameter_lower_bound) print("lower bound average shortest path length:", self.best_graph.aspl_lower_bound) self.best_graph.analyze() print("initial graph:", self.best_graph) try: if self.args.serial: self._run_debug() else: self._run() except KeyboardInterrupt: pass self.write_edges()
def test_analyze_unconnected(self): """ Checks analysis results for an unconnected graph. """ graph = GolfGraph(3, 2) with self.assertRaises(GraphPartitionedError): graph.analyze()
def some_valid_graphs(): """ Returns a bunch of graphs with valid combinations of order and degree. """ orders = (3, 4, 32, 33) degrees = (2, 3, 4, 32, 33) for order in orders: for degree in degrees: graph = GolfGraph(order, degree) graph.add_as_many_random_edges_as_possible() yield graph
def rectangle_graph(): """ Returns a graph with four vertices, connected as a "circle" (think: rectangle). """ graph = GolfGraph(4, 2) vertices = graph.vertices edges = ((0, 1), (1, 2), (2, 3), (3, 0)) # construct for vertex_a_i, vertex_b_i in edges: graph.add_edge_unsafe(vertices[vertex_a_i], vertices[vertex_b_i]) return graph
def line_graph(): """ Returns a graph with two vertices connected. """ graph = GolfGraph(3, 2) vertex0 = graph.vertices[0] vertex1 = graph.vertices[1] vertex2 = graph.vertices[2] # construct graph.add_edge_unsafe(vertex0, vertex1) graph.add_edge_unsafe(vertex1, vertex2) return graph
def test_hops_two_vertices(self): """ Tests shortest path computation for graph with two vertices. """ graph = GolfGraph(2, 2) graph.add_as_many_random_edges_as_possible() graph.analyze() self.assertEqual(tuple(), graph.hops(*graph.vertices))
def test_write_out_and_read_in(self): """ Test writing and reading of persisted graphs (i.e., plain text files containing a list of edges). """ orders_degrees = ((5, 4), (10, 9), (10, 11)) for order, degree in orders_degrees: self.cli.best_graph = GolfGraph(order, degree) self.cli.best_graph.add_as_many_random_edges_as_possible() self.cli.best_graph.analyze() filename = self.cli.current_edges_filename() self.cli.write_edges() cli = Cli() cli.best_graph = GolfGraph(order, degree) cli.load_edges(filename) cli.best_graph.analyze() for attr_name in ("order", "degree", "aspl", "diameter"): self.assertEqual(getattr(cli.best_graph, attr_name), getattr(self.cli.best_graph, attr_name)) remove(filename)
def test_hops_fully_connected(self): """ Tests shortest path computation for some fully connected graphs. """ orders_degrees = ((5, 4), (10, 9), (10, 11)) for order, degree in orders_degrees: graph = GolfGraph(order, degree) graph.add_as_many_random_edges_as_possible() graph.analyze() self.assertEqual(1, graph.diameter) self.assertEqual(1, graph.aspl)
def test_comparison(self): """ Tests whether our implementation of ``__lt__`` does what we want. """ orders_degrees = ((5, 4), (10, 9), (10, 11)) for order, degree in orders_degrees: graph_a = GolfGraph(order, degree) graph_a.add_as_many_random_edges_as_possible() graph_a.analyze() graph_b = graph_a.duplicate() self.assertFalse(graph_a < graph_b) self.assertFalse(graph_a > graph_b) graph_b.remove_edge_unsafe(graph_b.vertices[0], graph_b.vertices[1]) graph_b.analyze() self.assertTrue(graph_a < graph_b)
def test_all_edges(self): """ Tests whether there are as many edges as allowed (i.e. degree) after an modification. """ original = GolfGraph(10, 3) original.add_as_many_random_edges_as_possible() original.analyze() self.assert_all_edges_used(original) for Enhancer in Registry.enhancers: enhancer = Enhancer(None) while True: try: modified = enhancer.modify_graph(original.duplicate()) except GraphPartitionedError: continue else: break self.assert_all_edges_used(modified)
def test_hops_cache_reverse_lookup(self): """ Tests absence of a wrong ASPL that was returned for a specific graph after implementing reverse hops cache lookups. """ graph = GolfGraph(32, 5) edges = [ (0, [2, 31, 8, 16, 15]), (1, [4, 26, 27, 29, 11]), (2, [25, 29, 15, 7]), (3, [18, 17, 27, 8, 16]), (4, [21, 5, 28, 10]), (5, [14, 22, 24, 15]), (6, [16, 14, 30, 19, 9]), (7, [13]), (7, [20, 12, 10]), (8, [29, 15, 24]), (9, [23, 18, 28, 22]), (10, [24, 16, 11]), (11, [22, 26, 17]), (12, [31, 30, 14, 29]), (13, [17, 28, 19, 23]), (14, [21, 25]), (15, [23]), (16, [20]), (17, [22, 26]), (18, [29, 26, 24]), (19, [22, 27, 20]), (20, [25, 23]), (21, [30, 25, 23]), (24, [31]), (25, [26]), (27, [31, 28]), (28, [30]), (30, [31]), ] for vertex_a_i, vertex_b_is in edges: for vertex_b_i in vertex_b_is: graph.add_edge_unsafe(graph.vertices[vertex_a_i], graph.vertices[vertex_b_i]) graph.analyze() self.assertEqual(graph.diameter, 4) self.assertEqual(round(graph.aspl, 12), 2.20564516129)
def unconnected_graph(): """ Returns a unconnected graph (that, according to its configuration, could have edges, however). """ return GolfGraph(32, 4)
class Cli(object): """ Implements top-level coordination and interaction as CLI. After initialization, all you need is ``run()``. """ def __init__(self): """ Finds/loads/initializes everything needed for operation. """ # enabling debugging is the first thing we (might) have to do: if '-d' in argv or '--debug' in argv: getLogger().setLevel(DEBUG) debug("initializing CLI") self.enahncers = None """ All initialized enhancers. """ self._init_arg_parser() self._init_logging() self._init_enhancers() self.args = None self.best_graph = None def _init_arg_parser(self): """ Adds all options to the argument parser. """ self.arg_parser = ArgumentParser( description=("graph golf challenge experiments " "(http://research.nii.ac.jp/graphgolf/)"), formatter_class=ArgumentDefaultsHelpFormatter, ) self.arg_parser.add_argument('-e', '--edges', type=str, help="file name to load edges from") self.arg_parser.add_argument('-s', '--serial', action='store_true', default=False, help=("serial " "execution for debugging (** " "W/O MAKING ACTUAL PROGRESS**)")) self.arg_parser.add_argument('-o', '--once', action='store_true', default=False, help="run enhancers only once") self.arg_parser.add_argument('order', type=int, help="order of the graph") self.arg_parser.add_argument('degree', type=int, help="degree of the graph") def _init_enhancers(self): """ Initializes registered enhancers. """ enhancer_classes = EnhancerRegistry.enhancers debug("initializing enhancers %r", enhancer_classes) self.enhancers = [ Enhancer(self.arg_parser) for Enhancer in enhancer_classes ] def _init_logging(self): """ Configures our logger and add the arguments regarding logging to the argument parser. """ self.arg_parser.add_argument('-d', '--debug', action='store_true', default=False, help='turn on debug messages ' '(incomplete when running ' 'the interpreter optimized)') self.arg_parser.add_argument('-v', '--verbose', action='store_true', default=False, help='turn on verbose messages') formatter = Formatter("%(levelname)s:%(process)d:%(message)s") logger = getLogger() logger.name = "graphgolf" for handler in logger.handlers: handler.setFormatter(formatter) def _parse_args(self): """ Parses args and configures the logger according to them. """ debug("parsing command line arguments") # display help per default: if len(argv) == 1: argv.append("-h") self.args = self.arg_parser.parse_args() if self.args.verbose: getLogger().setLevel(INFO) if self.args.debug: getLogger().setLevel(DEBUG) def run(self): """ This initially kicks off the actual operation. """ debug("starting to run") self._parse_args() self.best_graph = GolfGraph(self.args.order, self.args.degree) if self.args.edges: self.load_edges() else: self.best_graph.add_as_many_random_edges_as_possible() print("lower bound diameter:", self.best_graph.diameter_lower_bound) print("lower bound average shortest path length:", self.best_graph.aspl_lower_bound) self.best_graph.analyze() print("initial graph:", self.best_graph) try: if self.args.serial: self._run_debug() else: self._run() except KeyboardInterrupt: pass self.write_edges() def _run(self): """ Tries to enhance the ``self.best_graph`` forever. Once an enhancer returns an enhanced graph, all enhancers are restarted therewith. """ processes = [] report_queue = Manager().Queue() while True: # check termination criteria if self.best_graph.ideal(): print("found best graph") return # create processes for enhancer in self.enhancers: if not enhancer.applicable_to(self.best_graph): debug("%s not applicable to %s", enhancer, self.best_graph) continue processes.append( Process(target=enhancer.enhance, args=(self.best_graph, report_queue))) # start processes for process in processes: debug("starting %s", process) process.start() # wait for any of them self.best_graph = report_queue.get() # kill the rest while processes: process = processes.pop() debug("terminating %s", process) process.terminate() # in case processes submitted a graph before they got killed while not report_queue.empty(): self.best_graph = min(report_queue.get(), self.best_graph) print("%s: %s" % (datetime.now(), self.best_graph)) if self.args.once: break def _run_debug(self): """ Like ``_run`` but w/o forking processes and parallelism. **MAKES NO ACTUAL PROGRESS** - solely for debugging purposes. """ report_queue = Manager().Queue() while True: for enhancer in self.enhancers: if enhancer.applicable_to(self.best_graph): enhancer.enhance(self.best_graph, report_queue) self.best_graph = report_queue.get() if self.args.once: break def current_edges_filename(self): """ Returns the file name of the current edges file. """ assert self.best_graph.diameter is not None assert self.best_graph.aspl is not None return "-".join(("edges", "order=%i" % self.best_graph.order, "degree=%i" % self.best_graph.degree, "diameter=%i" % self.best_graph.diameter, "aspl=%f" % self.best_graph.aspl)) def write_edges(self): """ Writes the best graph to a file. #refactoring: maybe this should be moved to another/separate class? """ assert self.best_graph.diameter is not None assert self.best_graph.aspl is not None info("writing out best graph found") with open(self.current_edges_filename(), mode="w") as open_file: open_file.writelines(("%i %i\n" % (v1.id, v2.id) for v1, v2 in self.best_graph.edges())) def load_edges(self, override_filename=None): """ Loads edges form the file specified in ``self.args`` into ``self.best_graph``. #refactoring: maybe this should be moved to another/separate class? """ filename = override_filename or self.args.edges with open(filename, "r") as open_file: for line in open_file.readlines(): line = line.strip() vertex_a_id, vertex_b_id = line.split(" ") self.best_graph.add_edge_unsafe( self.best_graph.vertices[int(vertex_a_id)], self.best_graph.vertices[int(vertex_b_id)], )