Example #1
0
    def solve(self):
        start_time = time.time()
        total_lp_solve_time = 0

        # Create branching options for root
        branching_options = []
        for person in self.people:
            for day in range(1, self.num_days + 1):
                branching_options.append(BranchingOption(DecisionType.PERSON_DAY, person.uid, day))

        for set_constraint in set_constraints:
            if set_constraint.constraintType.value == SetConstraintType.DEPARTMENT.value:
                lower_bound = set_constraint.low_bound
                upper_bound = set_constraint.up_bound
                if upper_bound < 0:
                    upper_bound = len(set_constraint.personList)
                
                for day in range(1, self.num_days + 1):
                    branching_options.append(BranchingOption(DecisionType.DEPT_DAY, set_constraint.sid, 
                                                            day, lower_bound, upper_bound))

            elif set_constraint.constraintType.value == SetConstraintType.SYNERGY.value:
                for day in range(1, self.num_days + 1):
                    branching_options.append(BranchingOption(DecisionType.SYNERGY_DAY, set_constraint.sid, day))
            else:
                pass # Should not reach here unless new set constraint types are added


        root = BnbNode(None, branching_options)
        root.lp = pulp_utils.build_scheduling_lp(num_days, people, set_constraints)

        stack = deque()
        stack.append(root)

        count_explored_nodes = 0

        while stack: # implicit condition: stack is not empty
            elapsed_time = time.time() - start_time
            if elapsed_time > time_limit:
                break

            node = stack.pop()
            count_explored_nodes += 1

            # print(node)

            children, lp_solve_time = node.branch()
            total_lp_solve_time += lp_solve_time

            # print('Node at depth {0}:\n\tLP value: {1:.2f}'.format(node.depth, node.lp_value))

            if node.feasible_value > self.best_value:
                self.best_value = node.feasible_value
                self.best_solution = node.feasible_solution

            # Pruning: Don't need to add children if LP relaxation has 
            # opt value no better than best feasible integer solution seen so far
            if node.lp_value <= self.best_value or len(children) == 0:
                continue

            for child in children:
                stack.append(child) # Push children onto stack for DFS


        # Print summary stats
        print('Explored {0:d} nodes.'.format(count_explored_nodes))
        print('Elapsed time: {0:.3f} s'.format(elapsed_time))
        print('Total LP solve time: {0:.3f} s'.format(total_lp_solve_time))
        print('Best value: {0:d}'.format(int(self.best_value)))

        # Update solver status
        if elapsed_time > time_limit and time_limit > 0:
            if self.best_value <= 0:
                self.status = SolverStatus.OUT_OF_TIME
            else:
                self.status = SolverStatus.FEASIBLE
        else:
            if self.best_value <= 0:
                self.status = SolverStatus.INFEASIBLE
            else:
                self.status = SolverStatus.OPTIMAL

        # Build and return best Schedule
        best_schedule = Schedule(people=self.people)
        best_schedule.buildFromSolutionVariables(self.best_solution)

        return best_schedule