예제 #1
0
 def __init__(self, graph, debug=False):
     self.graph = graph
     self.spanning_tree = SpanningTree(graph.vertexCount())
     self.edge_stack = []
     self.best_price = 0
     self.best = None
     self.debug = debug
    def test_edgeCandidates(self):
        self.graph.addEdge(0, 1)
        self.graph.addEdge(1, 2)
        self.graph.addEdge(1, 3)
        self.graph.addEdge(1, 4)
        self.graph.addEdge(2, 3)
        self.graph.addEdge(0, 4)

        tree = SpanningTree(5)
        tree.addEdge(Edge(0, 1))

        actual = self.graph.edgeCandidates(tree)
        expected = [Edge(1, 2), Edge(1, 3), Edge(1, 4), Edge(0, 4)]
        self.assertEqual(set(expected), set(actual))
    def test_edgeCandidates(self):
        self.graph.addEdge(0, 1)
        self.graph.addEdge(1, 2)
        self.graph.addEdge(1, 3)
        self.graph.addEdge(1, 4)
        self.graph.addEdge(2, 3)
        self.graph.addEdge(0, 4)

        tree = SpanningTree(5)
        tree.addEdge(Edge(0, 1))

        actual = self.graph.edgeCandidates(tree)
        expected = {Edge(1, 2), Edge(1, 3), Edge(1, 4), Edge(0, 4)}
        self.assertEqual(expected, actual)
예제 #4
0
    def __init__(self, graph, comm, debug=False):
        self.graph = graph
        self.spanning_tree = SpanningTree(graph.vertexCount())
        self.edge_stack = []
        self.best_price = sys.maxint
        self.best = None

        self.color = Token.BLACK
        self.initial_token_sent = False
        self.counter = 0
        self.finished = False

        self.rank = comm.Get_rank()
        self.comm_size = comm.Get_size()
        self.comm = comm
        self.logger = self.setupLogging(debug)
        self.mpi_logger = self.logger

        self.total_edges = 0
        self.total_solutions = 0
        self.all_trees = set([])
예제 #5
0
    def __init__(self, graph, comm, debug=False):
        self.graph = graph
        self.spanning_tree = SpanningTree(graph.vertexCount())
        self.edge_stack = []
        self.best_price = sys.maxint
        self.best = None

        self.color = Token.BLACK
        self.initial_token_sent = False
        self.counter = 0
        self.finished = False

        self.rank = comm.Get_rank()
        self.comm_size = comm.Get_size()
        self.comm = comm
        self.logger = self.setupLogging(debug)
        self.mpi_logger = self.logger

        self.total_edges = 0
        self.total_solutions = 0
        self.all_trees = set([])
예제 #6
0
 def setUp(self):
     self.spanning_tree = SpanningTree(7)
예제 #7
0
class TestSpanningTree(unittest.TestCase):

    def setUp(self):
        self.spanning_tree = SpanningTree(7)

    def test_manipulateEdges(self):
        self.spanning_tree.addEdge(Edge(0, 1))
        self.assertEqual(1, self.spanning_tree.maxDegree())
        self.assertEqual(1, self.spanning_tree.edgeCount())

        self.spanning_tree.addEdge(Edge(1, 2))
        self.assertEqual(2, self.spanning_tree.maxDegree())
        self.assertEqual(2, self.spanning_tree.edgeCount())

        self.spanning_tree.removeLastEdge()
        self.assertEqual(1, self.spanning_tree.maxDegree())
        self.assertEqual(1, self.spanning_tree.edgeCount())

        self.spanning_tree.removeLastEdge()
        self.assertEqual(0, self.spanning_tree.maxDegree())
        self.assertEqual(0, self.spanning_tree.edgeCount())

    def test_maxDegree(self):
        self.spanning_tree.addEdge(Edge(0, 1))
        self.spanning_tree.addEdge(Edge(1, 2))
        self.spanning_tree.addEdge(Edge(1, 3))

        self.assertEqual(3, self.spanning_tree.maxDegree())

    def test_maxDegreeWith(self):
        self.spanning_tree.addEdge(Edge(0, 1))
        self.spanning_tree.addEdge(Edge(1, 2))

        degree = self.spanning_tree.maxDegreeWith(Edge(1, 3))
        self.assertEqual(3, degree)

    def test_str(self):
        self.spanning_tree.addEdge(Edge(0, 1))
        self.spanning_tree.addEdge(Edge(1, 2))
        self.spanning_tree.addEdge(Edge(1, 3))

        expected = "(0, 1), (1, 2), (1, 3)"
        actual = self.spanning_tree.__str__()
        self.assertEqual(expected, actual)

    def test_duplicate_edges(self):
        self.spanning_tree.addEdge(Edge(1, 2))
        self.assertEqual(1, self.spanning_tree.edgeCount())
        self.spanning_tree.addEdge(Edge(1, 2))
        self.assertEqual(1, self.spanning_tree.edgeCount())
예제 #8
0
class DFSSolver:

    BEST_PRICE_POSSIBLE = 2

    WORK_REQ = 1
    WORK_SHARE = 2
    WORK_REJECT = 3
    TOKEN = 4
    TERMINATE = 5

    def __init__(self, graph, comm, debug=False):
        self.graph = graph
        self.spanning_tree = SpanningTree(graph.vertexCount())
        self.edge_stack = []
        self.best_price = sys.maxint
        self.best = None

        self.color = Token.BLACK
        self.initial_token_sent = False
        self.counter = 0
        self.finished = False

        self.rank = comm.Get_rank()
        self.comm_size = comm.Get_size()
        self.comm = comm
        self.logger = self.setupLogging(debug)
        self.mpi_logger = self.logger

        self.total_edges = 0
        self.total_solutions = 0
        self.all_trees = set([])

    def setupLogging(self, debug):
        logger = logging.getLogger(str(self.rank))
        handler = logging.FileHandler('./log/{}.txt'.format(self.rank))
        formatter = logging.Formatter(fmt='%(asctime)s: %(message)s',
                                      datefmt='%H:%M:%S')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        if debug:
            logger.setLevel(logging.DEBUG)
        return logger

    def findBestSolution(self):
        # push all edges adjacent to vertex 0 to stack
        if self.rank == 0:
            for edge in self.firstEdgeCandidates():
                self.edge_stack.append(edge)
            self.distributeInitialWork()

        self.comm.Barrier()

        if self.rank != 0:
            self.acceptInitialWork()

        # main loop
        while not self.shouldTerminate():

            # trochu prasarna
            if len(self.edge_stack) == 0:
                continue

            self.logger.debug(self.printStack(self.edge_stack))

            self.expand()

        self.logger.info("%s processed %s edges.", self.rank, self.total_edges)
        self.logger.info("%s found %s solutions.", self.rank,
                         self.total_solutions)

        self.logger.info("%s created %s unique of %s partial solutions.",
                         self.rank, len(self.all_trees), self.total_edges)

        for partial in self.all_trees:
            self.logger.info(partial)

        # DFS traversal completed
        if self.foundSolution():
            return self.best, self.best_price
        else:
            self.logger.info("%s: no solution found.", self.rank)
            return None, sys.maxint

    def expand(self):
        current_edge = self.edge_stack.pop()
        self.total_edges += 1

        if current_edge.isBacktrackMarker():
            # found backtrack marker, remove last edge from spanning tree
            self.spanning_tree.removeLastEdge()
        else:
            # add current edge to spanning tree
            self.spanning_tree.addEdge(current_edge)
            edge_set = frozenset(self.spanning_tree.edgeList())
            self.all_trees.add(edge_set)
            del edge_set

            self.logger.debug(self.spanning_tree)

            if self.isSolution():

                self.logger.debug("%s found solution!", self.rank)
                self.total_solutions += 1

                price = self.spanning_tree.maxDegree()
                if self.isBestPossible(price):
                    # current solution is the best possible, return
                    self.updateBest(price)
                    self.logger.debug("! %s found the best solution possible",
                                      self.rank)
                    self.askToTerminate()
                elif self.isBestSoFar(price):
                    # better that any solution so far, update best
                    self.updateBest(price)
                    self.logger.debug("%s found new solution with price %s:",
                                      self.rank, price)
                    self.logger.debug(self.spanning_tree)
                # since we've found the solution, current edge is a leaf of the DFS tree -> backtrack
                self.spanning_tree.removeLastEdge()
            else:
                # add backtrack marker to stack so we will know when we are moving up the DFS tree
                self.pushBacktrackMarker()
                # find new edges to add to spanning tree
                for edge in self.graph.edgeCandidates(self.spanning_tree):
                    #                    if not self.isCandidate(edge):
                    #                        continue
                    if self.possibleWinner(edge):
                        # candidate edge can lead to better solution than best solution so far
                        self.edge_stack.append(edge)
                    else:
                        self.logger.debug("Leaving out edge %s", edge)

    def isCandidate(self, edge):
        for tree_edge in self.spanning_tree.edgeList():
            if edge < tree_edge:
                self.logger.debug("Not considering edge %s", edge)
                return False
        return True

    def firstEdgeCandidates(self):
        vertex = 0
        return self.graph.adjacentEdges(vertex)

    def pushBacktrackMarker(self):
        self.edge_stack.append(Edge(-1, -1))

    def possibleWinner(self, edge):
        price = self.spanning_tree.maxDegreeWith(edge)
        return self.isBestSoFar(price)

    def isSolution(self):
        tree_edges = self.spanning_tree.edgeCount()
        graph_vertices = self.graph.vertexCount()
        return tree_edges == graph_vertices - 1

    def isBestPossible(self, price):
        return price == self.BEST_PRICE_POSSIBLE

    def isBestSoFar(self, price):
        if self.best == None:
            return True
        return price < self.best_price

    def updateBest(self, price):
        self.best_price = price
        self.best = copy.deepcopy(self.spanning_tree)

    def foundSolution(self):
        return self.best != None

    def printStack(self, stack):
        result = "\n|--------|\n"
        for edge in stack:
            if edge.isBacktrackMarker():
                result += "|   **   |\n"
            else:
                result += "| {0:6} |\n".format(edge)
        result += "^        ^"
        return result

    def distributeInitialWork(self):
        parts = self.initialWorkSplit(self.comm_size - 1)
        self.mpi_logger.debug("0 has %s initial work parts", len(parts))
        toNode = 1
        for part in parts:
            self.comm.send(part, dest=toNode, tag=DFSSolver.WORK_SHARE)
            self.mpi_logger.debug("0 has sent initial work to %s", toNode)
            toNode += 1

    def acceptInitialWork(self):
        status = MPI.Status()
        # check for shared work
        work_resp = self.comm.Iprobe(source=0,
                                     tag=DFSSolver.WORK_SHARE,
                                     status=status)
        if work_resp:
            self.mpi_logger.debug("%s has initial work", self.rank)
            self.receiveWork(0)
        else:
            self.mpi_logger.debug("%s has no initial work :(", self.rank)

    def initialWorkSplit(self, partsReq):
        parts = []
        partCnt = 0
        while partCnt < partsReq:
            edgesLeft = self.countEdgesOnStack()
            while edgesLeft < partsReq + 1 - partCnt:
                if edgesLeft == 0:
                    return parts
                self.expand()
                edgesLeft = self.countEdgesOnStack()
            new_stack, new_tree = self.splitWork()
            parts.append((new_stack, new_tree))
            partCnt += 1
        return parts

    def countEdgesOnStack(self):
        count = 0
        for edge in self.edge_stack:
            if not edge.isBacktrackMarker():
                count += 1
        return count

    def splitWork(self):
        def fromBottomElement(self):
            for index, edge in enumerate(self.edge_stack):
                if not edge.isBacktrackMarker():
                    return index
            return None

        def countBacktracks(self, to_index):
            count = 0
            for edge in self.edge_stack[to_index + 1:]:
                if edge.isBacktrackMarker():
                    count += 1
            return count

        elem_to_move = fromBottomElement(self)
        if elem_to_move is None:
            return None, None
        new_stack = self.edge_stack[:elem_to_move + 1]
        del self.edge_stack[elem_to_move]

        backtracks_to_do = countBacktracks(self, elem_to_move)
        new_spanning_tree = copy.deepcopy(self.spanning_tree)
        for i in range(backtracks_to_do):
            new_spanning_tree.removeLastEdge()

        return new_stack, new_spanning_tree

    def shouldTerminate(self):
        if self.counter > 100:
            self.counter %= 100
            return False
        else:
            self.counter += 1

        self.checkTerminationMsg()
        if self.finished:
            self.mpi_logger.debug("! %s exiting.", self.rank)
            return True

        if self.hasWorkToShare():
            self.handleWorkRequests()
            return False
        else:
            # print("{0} has no work to share.").format(self.rank)
            # not enough work to share
            self.rejectAll()
        if len(self.edge_stack) > 0:
            # but enough work to continue
            return False
        else:
            # own stack is empty

            if self.comm_size == 1:
                return True

            self.handleTokens()

            work_request_sent = False
            while True:
                self.rejectAll()

                self.handleTokens()
                if self.finished:
                    break

                if not work_request_sent:
                    self.sendWorkRequest()
                    work_request_sent = True
                else:
                    work_available, avl_from, work_req_sent_flag = self.checkWorkResponse(
                    )
                    if work_req_sent_flag is not None:
                        work_request_sent = work_req_sent_flag
                    if work_available:
                        self.receiveWork(avl_from)
                        return False

    def hasWorkToShare(self):
        edge_count = 0
        for edge in self.edge_stack:
            if not edge.isBacktrackMarker():
                edge_count += 1
        return edge_count >= 2

    def handleWorkRequests(self):
        status = MPI.Status()
        # check for work request from anyone
        has_message = self.comm.Iprobe(source=MPI.ANY_SOURCE,
                                       tag=DFSSolver.WORK_REQ,
                                       status=status)
        if not has_message:
            # print("{0} has no work requests.").format(self.rank)
            return
        else:
            source = status.Get_source()
            self.mpi_logger.debug("%s has WORK_REQ from %s", self.rank, source)
            self.comm.recv(source=source,
                           tag=DFSSolver.WORK_REQ,
                           status=status)
            self.sendWork(source)

    def sendWork(self, to):
        new_stack, new_spanning_tree = self.splitWork()
        if new_stack is None and new_spanning_tree is None:
            return

        self.comm.send((new_stack, new_spanning_tree),
                       dest=to,
                       tag=DFSSolver.WORK_SHARE)
        self.checkColorChange(to)
        self.mpi_logger.debug("%s sent work offer to %s", self.rank, to)

    def rejectAll(self):
        status = MPI.Status()
        has_work_req = self.comm.Iprobe(source=MPI.ANY_SOURCE,
                                        tag=DFSSolver.WORK_REQ,
                                        status=status)
        while has_work_req:
            source = status.Get_source()
            self.comm.recv(source=source, tag=DFSSolver.WORK_REQ)
            self.comm.send(dest=source, tag=DFSSolver.WORK_REJECT)
            self.mpi_logger.debug(
                "%s received and REJECTED work request from %s", self.rank,
                source)
            has_work_req = self.comm.Iprobe(source=MPI.ANY_SOURCE,
                                            tag=DFSSolver.WORK_REQ,
                                            status=status)

    def sendWorkRequest(self):
        destination = self.nextNode()
        self.comm.send(dest=destination, tag=DFSSolver.WORK_REQ)
        self.mpi_logger.debug("%s sent work request to %s", self.rank,
                              destination)

    def prevNode(self):
        if self.rank == 0:
            return self.comm_size - 1
        else:
            return self.rank - 1

    def nextNode(self):
        return (self.rank + 1) % self.comm_size

    def checkWorkResponse(self):
        status = MPI.Status()
        # check for shared work
        work_resp = self.comm.Iprobe(source=MPI.ANY_SOURCE,
                                     tag=DFSSolver.WORK_SHARE,
                                     status=status)
        if work_resp:
            source = status.Get_source()
            self.mpi_logger.debug("there is shared work for %s from %s",
                                  self.rank, source)
            return True, source, True
        else:
            # check for rejections
            rejection = self.comm.Iprobe(source=MPI.ANY_SOURCE,
                                         tag=DFSSolver.WORK_REJECT,
                                         status=status)
            if rejection:
                # got rejection for work request
                source = status.Get_source()
                self.mpi_logger.debug(
                    "%s received work request rejection from %s", self.rank,
                    source)
                self.comm.recv(source=source, tag=DFSSolver.WORK_REJECT)
                return False, None, False
            else:
                # no reponse
                return False, None, True

    def receiveWork(self, source):
        work_tuple = self.comm.recv(source=source, tag=DFSSolver.WORK_SHARE)
        self.mpi_logger.debug("%s RECEIVED work from %s", self.rank, source)
        new_stack = work_tuple[0]
        new_spanning_tree = work_tuple[1]

        self.mpi_logger.debug("%s received stack: %s", self.rank,
                              self.printStack(new_stack))
        self.mpi_logger.debug("%s received tree: %s", self.rank,
                              new_spanning_tree)

        self.spanning_tree = new_spanning_tree
        for edge in new_stack:
            self.edge_stack.append(edge)

    def checkColorChange(self, sentWorkTo):
        change_color = False
        if self.rank == 0 and sentWorkTo == self.comm_size - 1:
            # should not happen
            change_color = True
        elif self.rank > sentWorkTo:
            change_color = True

        if change_color:
            self.color = Token.BLACK

    def askToTerminate(self):
        for node in range(0, self.comm_size):
            self.mpi_logger.debug("%s sent TERMINATION tag to %s", self.rank,
                                  node)
            self.comm.send(dest=node, tag=DFSSolver.TERMINATE)

    def handleTokens(self):
        def initialTokenSend(self):
            token = Token()
            sendToken(self, token)
            self.token_sent = True

        def sendToken(self, token):
            self.comm.send(token, dest=self.nextNode(), tag=DFSSolver.TOKEN)
            self.color = Token.WHITE
            self.mpi_logger.debug("%s sent %s to %s", self.rank, token,
                                  self.nextNode())

        def receiveToken(self):
            token = self.comm.recv(source=self.prevNode(), tag=DFSSolver.TOKEN)
            self.mpi_logger.debug("%s received %s from %s", self.rank, token,
                                  self.prevNode())
            if self.rank == 0:
                if token.color == Token.WHITE:
                    self.askToTerminate()
                else:
                    # black token arrived, try again
                    initialTokenSend(self)
            else:
                token.color = self.color
                sendToken(self, token)

        self.checkTerminationMsg()

        has_token = self.comm.Iprobe(source=self.prevNode(),
                                     tag=DFSSolver.TOKEN)
        if has_token:
            receiveToken(self)
        else:
            #print("No token for {}").format(self.rank)
            if not self.initial_token_sent and self.rank == 0:
                initialTokenSend(self)

    def checkTerminationMsg(self):
        should_terminate = self.comm.Iprobe(source=0, tag=DFSSolver.TERMINATE)
        if should_terminate:
            self.comm.recv(source=0, tag=DFSSolver.TERMINATE)
            self.mpi_logger.debug("%s has received termination token.",
                                  self.rank)
            self.finished = True
            return
예제 #9
0
class DFSSolver:

    BEST_PRICE_POSSIBLE = 2

    WORK_REQ = 1
    WORK_SHARE = 2
    WORK_REJECT = 3
    TOKEN = 4
    TERMINATE = 5

    def __init__(self, graph, comm, debug=False):
        self.graph = graph
        self.spanning_tree = SpanningTree(graph.vertexCount())
        self.edge_stack = []
        self.best_price = sys.maxint
        self.best = None

        self.color = Token.BLACK
        self.initial_token_sent = False
        self.counter = 0
        self.finished = False

        self.rank = comm.Get_rank()
        self.comm_size = comm.Get_size()
        self.comm = comm
        self.logger = self.setupLogging(debug)
        self.mpi_logger = self.logger

        self.total_edges = 0
        self.total_solutions = 0
        self.all_trees = set([])

    def setupLogging(self, debug):
        logger = logging.getLogger(str(self.rank))
        handler = logging.FileHandler('./log/{}.txt'.format(self.rank))
        formatter = logging.Formatter(fmt='%(asctime)s: %(message)s', datefmt='%H:%M:%S')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        if debug:
            logger.setLevel(logging.DEBUG)
        return logger

    def findBestSolution(self):
        # push all edges adjacent to vertex 0 to stack
        if self.rank == 0:
            for edge in self.firstEdgeCandidates():
                self.edge_stack.append(edge)
            self.distributeInitialWork()

        self.comm.Barrier()

        if self.rank != 0:
            self.acceptInitialWork()

        # main loop
        while not self.shouldTerminate():

            # trochu prasarna
            if len(self.edge_stack) == 0:
                continue

            self.logger.debug(self.printStack(self.edge_stack))

            self.expand()

        self.logger.info("%s processed %s edges.", self.rank, self.total_edges)
        self.logger.info("%s found %s solutions.", self.rank, self.total_solutions)

        self.logger.info("%s created %s unique of %s partial solutions.", self.rank, len(self.all_trees), self.total_edges)

        for partial in self.all_trees:
                self.logger.info(partial)

        # DFS traversal completed
        if self.foundSolution():
            return self.best, self.best_price
        else:
            self.logger.info("%s: no solution found.", self.rank)
            return None, sys.maxint

    def expand(self):
        current_edge = self.edge_stack.pop()
        self.total_edges += 1

        if current_edge.isBacktrackMarker():
            # found backtrack marker, remove last edge from spanning tree
            self.spanning_tree.removeLastEdge()
        else:
            # add current edge to spanning tree
            self.spanning_tree.addEdge(current_edge)
            edge_set = frozenset(self.spanning_tree.edgeList())
            self.all_trees.add(edge_set)
            del edge_set

            self.logger.debug(self.spanning_tree)

            if self.isSolution():

                self.logger.debug("%s found solution!", self.rank)
                self.total_solutions += 1

                price = self.spanning_tree.maxDegree()
                if self.isBestPossible(price):
                    # current solution is the best possible, return
                    self.updateBest(price)
                    self.logger.debug("! %s found the best solution possible", self.rank)
                    self.askToTerminate()
                elif self.isBestSoFar(price):
                    # better that any solution so far, update best
                    self.updateBest(price)
                    self.logger.debug("%s found new solution with price %s:",self.rank, price)
                    self.logger.debug(self.spanning_tree)
                # since we've found the solution, current edge is a leaf of the DFS tree -> backtrack
                self.spanning_tree.removeLastEdge()
            else:
                # add backtrack marker to stack so we will know when we are moving up the DFS tree
                self.pushBacktrackMarker()
                # find new edges to add to spanning tree
                for edge in self.graph.edgeCandidates(self.spanning_tree):
#                    if not self.isCandidate(edge):
#                        continue
                    if self.possibleWinner(edge):
                        # candidate edge can lead to better solution than best solution so far
                        self.edge_stack.append(edge)
                    else:
                        self.logger.debug("Leaving out edge %s", edge)

    def isCandidate(self, edge):
        for tree_edge in self.spanning_tree.edgeList():
            if edge < tree_edge:
                self.logger.debug("Not considering edge %s", edge)
                return False
        return True

    def firstEdgeCandidates(self):
        vertex = 0
        return self.graph.adjacentEdges(vertex)

    def pushBacktrackMarker(self):
        self.edge_stack.append(Edge(-1, -1))

    def possibleWinner(self, edge):
        price = self.spanning_tree.maxDegreeWith(edge)
        return self.isBestSoFar(price)

    def isSolution(self):
        tree_edges = self.spanning_tree.edgeCount()
        graph_vertices = self.graph.vertexCount()
        return tree_edges == graph_vertices - 1

    def isBestPossible(self, price):
        return price == self.BEST_PRICE_POSSIBLE

    def isBestSoFar(self, price):
        if self.best == None:
            return True
        return price < self.best_price

    def updateBest(self, price):
        self.best_price = price
        self.best = copy.deepcopy(self.spanning_tree)

    def foundSolution(self):
        return self.best != None

    def printStack(self, stack):
        result = "\n|--------|\n"
        for edge in stack:
            if edge.isBacktrackMarker():
                result += "|   **   |\n"
            else:
                result += "| {0:6} |\n".format(edge)
        result += "^        ^"
        return result

    def distributeInitialWork(self):
        parts = self.initialWorkSplit(self.comm_size - 1)
        self.mpi_logger.debug("0 has %s initial work parts", len(parts))
        toNode = 1
        for part in parts:
            self.comm.send(part, dest=toNode, tag=DFSSolver.WORK_SHARE)
            self.mpi_logger.debug("0 has sent initial work to %s", toNode)
            toNode += 1

    def acceptInitialWork(self):
        status = MPI.Status()
        # check for shared work
        work_resp = self.comm.Iprobe(source=0, tag=DFSSolver.WORK_SHARE, status=status)
        if work_resp:
            self.mpi_logger.debug("%s has initial work", self.rank)
            self.receiveWork(0)
        else:
            self.mpi_logger.debug("%s has no initial work :(", self.rank)

    def initialWorkSplit(self, partsReq):
        parts = []
        partCnt = 0
        while partCnt < partsReq:
            edgesLeft = self.countEdgesOnStack()
            while edgesLeft < partsReq + 1 - partCnt:
                if edgesLeft == 0:
                    return parts
                self.expand()
                edgesLeft = self.countEdgesOnStack()
            new_stack, new_tree = self.splitWork()
            parts.append((new_stack, new_tree))
            partCnt += 1
        return parts

    def countEdgesOnStack(self):
        count = 0
        for edge in self.edge_stack:
            if not edge.isBacktrackMarker():
                count += 1
        return count

    def splitWork(self):

        def fromBottomElement(self):
            for index, edge in enumerate(self.edge_stack):
                if not edge.isBacktrackMarker():
                    return index
            return None

        def countBacktracks(self, to_index):
            count = 0
            for edge in self.edge_stack[to_index + 1:]:
                if edge.isBacktrackMarker():
                    count += 1
            return count

        elem_to_move = fromBottomElement(self)
        if elem_to_move is None:
            return None, None
        new_stack = self.edge_stack[:elem_to_move + 1]
        del self.edge_stack[elem_to_move]

        backtracks_to_do = countBacktracks(self, elem_to_move)
        new_spanning_tree = copy.deepcopy(self.spanning_tree)
        for i in range(backtracks_to_do):
            new_spanning_tree.removeLastEdge()

        return new_stack, new_spanning_tree

    def shouldTerminate(self):
        if self.counter > 100:
            self.counter %= 100
            return False
        else:
            self.counter += 1

        self.checkTerminationMsg()
        if self.finished:
            self.mpi_logger.debug("! %s exiting.", self.rank)
            return True

        if self.hasWorkToShare():
            self.handleWorkRequests()
            return False
        else:
            # print("{0} has no work to share.").format(self.rank)
            # not enough work to share
            self.rejectAll()
        if len(self.edge_stack) > 0:
            # but enough work to continue
            return False
        else:
            # own stack is empty

            if self.comm_size == 1:
                return True

            self.handleTokens()

            work_request_sent = False
            while True:
                self.rejectAll()

                self.handleTokens()
                if self.finished:
                    break

                if not work_request_sent:
                    self.sendWorkRequest()
                    work_request_sent = True
                else:
                    work_available, avl_from, work_req_sent_flag = self.checkWorkResponse()
                    if work_req_sent_flag is not None:
                        work_request_sent = work_req_sent_flag
                    if work_available:
                        self.receiveWork(avl_from)
                        return False

    def hasWorkToShare(self):
        edge_count = 0
        for edge in self.edge_stack:
            if not edge.isBacktrackMarker():
                edge_count += 1
        return edge_count >= 2

    def handleWorkRequests(self):
        status = MPI.Status()
        # check for work request from anyone
        has_message = self.comm.Iprobe(source=MPI.ANY_SOURCE, tag=DFSSolver.WORK_REQ, status=status)
        if not has_message:
            # print("{0} has no work requests.").format(self.rank)
            return
        else:
            source = status.Get_source()
            self.mpi_logger.debug("%s has WORK_REQ from %s", self.rank, source)
            self.comm.recv(source=source, tag=DFSSolver.WORK_REQ, status=status)
            self.sendWork(source)

    def sendWork(self, to):
        new_stack, new_spanning_tree = self.splitWork()
        if new_stack is None and new_spanning_tree is None:
            return

        self.comm.send((new_stack, new_spanning_tree), dest=to, tag=DFSSolver.WORK_SHARE)
        self.checkColorChange(to)
        self.mpi_logger.debug("%s sent work offer to %s", self.rank, to)

    def rejectAll(self):
        status = MPI.Status()
        has_work_req = self.comm.Iprobe(source=MPI.ANY_SOURCE, tag=DFSSolver.WORK_REQ, status=status)
        while has_work_req:
            source = status.Get_source()
            self.comm.recv(source=source, tag=DFSSolver.WORK_REQ)
            self.comm.send(dest=source, tag=DFSSolver.WORK_REJECT)
            self.mpi_logger.debug("%s received and REJECTED work request from %s", self.rank, source)
            has_work_req = self.comm.Iprobe(source=MPI.ANY_SOURCE, tag=DFSSolver.WORK_REQ, status=status)

    def sendWorkRequest(self):
        destination = self.nextNode()
        self.comm.send(dest=destination, tag=DFSSolver.WORK_REQ)
        self.mpi_logger.debug("%s sent work request to %s", self.rank, destination)

    def prevNode(self):
        if self.rank == 0:
            return self.comm_size - 1
        else:
            return self.rank - 1

    def nextNode(self):
        return (self.rank + 1) % self.comm_size

    def checkWorkResponse(self):
        status = MPI.Status()
        # check for shared work
        work_resp = self.comm.Iprobe(source=MPI.ANY_SOURCE, tag=DFSSolver.WORK_SHARE, status=status)
        if work_resp:
            source = status.Get_source()
            self.mpi_logger.debug("there is shared work for %s from %s", self.rank, source)
            return True, source, True
        else:
            # check for rejections
            rejection = self.comm.Iprobe(source=MPI.ANY_SOURCE, tag=DFSSolver.WORK_REJECT, status=status)
            if rejection:
                # got rejection for work request
                source = status.Get_source()
                self.mpi_logger.debug("%s received work request rejection from %s", self.rank, source)
                self.comm.recv(source=source, tag=DFSSolver.WORK_REJECT)
                return False, None, False
            else:
                # no reponse
                return False, None, True

    def receiveWork(self, source):
        work_tuple = self.comm.recv(source=source, tag=DFSSolver.WORK_SHARE)
        self.mpi_logger.debug("%s RECEIVED work from %s", self.rank, source)
        new_stack = work_tuple[0]
        new_spanning_tree = work_tuple[1]

        self.mpi_logger.debug("%s received stack: %s", self.rank, self.printStack(new_stack))
        self.mpi_logger.debug("%s received tree: %s", self.rank, new_spanning_tree)

        self.spanning_tree = new_spanning_tree
        for edge in new_stack:
            self.edge_stack.append(edge)

    def checkColorChange(self, sentWorkTo):
        change_color = False
        if self.rank == 0 and sentWorkTo == self.comm_size - 1:
            # should not happen
            change_color = True
        elif self.rank > sentWorkTo:
            change_color = True

        if change_color:
            self.color = Token.BLACK

    def askToTerminate(self):
        for node in range(0, self.comm_size):
            self.mpi_logger.debug("%s sent TERMINATION tag to %s", self.rank, node)
            self.comm.send(dest=node, tag=DFSSolver.TERMINATE)

    def handleTokens(self):

        def initialTokenSend(self):
            token = Token()
            sendToken(self, token)
            self.token_sent = True;

        def sendToken(self, token):
            self.comm.send(token, dest=self.nextNode(), tag=DFSSolver.TOKEN)
            self.color = Token.WHITE
            self.mpi_logger.debug("%s sent %s to %s", self.rank, token, self.nextNode())

        def receiveToken(self):
            token = self.comm.recv(source=self.prevNode(), tag=DFSSolver.TOKEN)
            self.mpi_logger.debug("%s received %s from %s", self.rank, token, self.prevNode())
            if self.rank == 0:
                if token.color == Token.WHITE:
                    self.askToTerminate()
                else:
                    # black token arrived, try again
                    initialTokenSend(self)
            else:
                token.color = self.color
                sendToken(self, token)

        self.checkTerminationMsg()

        has_token = self.comm.Iprobe(source=self.prevNode(), tag=DFSSolver.TOKEN)
        if has_token:
            receiveToken(self)
        else:
            #print("No token for {}").format(self.rank)
            if not self.initial_token_sent and self.rank == 0:
               initialTokenSend(self)

    def checkTerminationMsg(self):
        should_terminate = self.comm.Iprobe(source=0, tag=DFSSolver.TERMINATE)
        if should_terminate:
            self.comm.recv(source=0, tag=DFSSolver.TERMINATE)
            self.mpi_logger.debug("%s has received termination token.", self.rank)
            self.finished = True
            return
예제 #10
0
class DFSSolver:

    BEST_PRICE_POSSIBLE = 2

    def __init__(self, graph, debug=False):
        self.graph = graph
        self.spanning_tree = SpanningTree(graph.vertexCount())
        self.edge_stack = []
        self.best_price = 0
        self.best = None
        self.debug = debug

    def findBestSolution(self):
        # push all edges adjacent to vertex 0 to stack
        for edge in self.firstEdgeCandidates():
            self.edge_stack.append(edge)

        # main loop
        while len(self.edge_stack) > 0:

            if self.debug:
                self.printStack()

            current_edge = self.edge_stack.pop()
            if self.isBacktrackMarker(current_edge):
                # found backtrack marker, remove last edge from spanning tree
                self.spanning_tree.removeLastEdge()
            else:
                # add current edge to spanning tree
                self.spanning_tree.addEdge(current_edge)

                if self.debug:
                    print(self.spanning_tree)

                if self.isSolution():

                    if self.debug:
                        print("Found solution!")

                    price = self.spanning_tree.maxDegree()
                    if self.isBestPossible(price):
                        # current solution is the best possible, return
                        self.updateBest(price)
                        return self.best, self.best_price
                    elif self.isBestSoFar(price):
                        # better that any solution so far, update best
                        self.updateBest(price)
                    # since we've found the solution, current edge is a leaf of the DFS tree -> backtrack
                    self.spanning_tree.removeLastEdge()
                else:
                    # add backtrack marker to stack so we will know when we are moving up the DFS tree
                    self.pushBacktrackMarker()
                    # find new edges to add to spanning tree
                    for edge in self.graph.edgeCandidates(self.spanning_tree):
                        if self.possibleWinner(edge):
                            # candidate edge can lead to better solution than best solution so far
                            self.edge_stack.append(edge)
                        else:
                            if self.debug:
                                print("Leaving out edge {}".format(edge))

        # DFS traversal completed
        if self.foundSolution():
            return self.best, self.best_price
        else:
            if self.debug:
                print("No solution found.")
            return None, None

    def firstEdgeCandidates(self):
        vertex = 0
        return self.graph.adjacentEdges(vertex)

    def isBacktrackMarker(self, edge):
        return edge == Edge(-1, -1)

    def pushBacktrackMarker(self):
        self.edge_stack.append(Edge(-1, -1))

    def possibleWinner(self, edge):
        price = self.spanning_tree.maxDegreeWith(edge)
        return self.isBestSoFar(price)

    def isSolution(self):
        tree_edges = self.spanning_tree.edgeCount()
        graph_vertices = self.graph.vertexCount()
        return tree_edges == graph_vertices - 1

    def isBestPossible(self, price):
        return price == self.BEST_PRICE_POSSIBLE

    def isBestSoFar(self, price):
        if self.best == None:
            return True
        return price < self.best_price

    def updateBest(self, price):
        self.best_price = price
        self.best = copy.deepcopy(self.spanning_tree)

    def foundSolution(self):
        return self.best != None

    def printStack(self):
        print ("|--------|")
        for edge in self.edge_stack:
            if self.isBacktrackMarker(edge):
                print("|   **   |")
            else:
                print("| {0:6} |".format(edge))
        print ("^        ^\n")