Esempio n. 1
0
    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()
Esempio n. 2
0
    def test_analyze_unconnected(self):
        """
        Checks analysis results for an unconnected graph.
        """
        graph = GolfGraph(3, 2)

        with self.assertRaises(GraphPartitionedError):
            graph.analyze()
Esempio n. 3
0
 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
Esempio n. 4
0
    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
Esempio n. 5
0
    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
Esempio n. 6
0
 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))
Esempio n. 7
0
    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)
Esempio n. 8
0
 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)
Esempio n. 9
0
 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)
Esempio n. 10
0
 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)
Esempio n. 11
0
 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)
Esempio n. 12
0
 def unconnected_graph():
     """
     Returns a unconnected graph (that, according to its configuration,
     could have edges, however).
     """
     return GolfGraph(32, 4)
Esempio n. 13
0
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)],
                )