Esempio n. 1
0
    def enforce(self):
        """Calls the enforce methods of the constraint handlers for
        all violated constraints in the registered current instance.

        The prepare methods of the constraint handlers that are not
        part of the relaxation are called. The order is given by the
        enforce priority order (smaller priority first). All indices of
        constraints belonging to the handler are passed using the sets
        object. Before calling a plugin function, a current data
        object is appended to the _curdata stack.

        Unaccepted user operations: None.

        :return: A UserInputStatus.
        """
        assert self._cur_instance is not None
        instance = self._cur_instance
        # Iterate over all constraint handlers in the specified order.
        for (_, hdlr) in self._hdlrs_enf:
            if not hdlr.relax() and instance.nviolated(hdlr) > 0:
                # Generate constraint handler sets.
                sets = instance.violated_sets(hdlr)
                # Only possible to do inside if resolve after every
                # conshandler that performs changes.
                model = instance.relax_model()
                # Set up current data if not already existing.
                curdata = _CurrentData.create(hdlr)
                # Call enforce method.
                self._curdata.append(curdata)
                self._get_hdlr(hdlr).enforce(sets, model, self._solver)
                self._curdata.append(curdata)

                Stats.separate(self, curdata)

                # Handle user input.
                if curdata.infeasible:
                    return UserInputStatus.INFEASIBLE
                elif curdata.branched:
                    self._bnb_tree.register_child_instances(curdata.children)
                    return UserInputStatus.BRANCHED
                elif curdata.ncuts > 0 or curdata.ntighten > 0:
                    return UserInputStatus.RESOLVE

        raise UserInputError('Enforcement failed. No constraint handler '
                             'called an interface function.')
Esempio n. 2
0
    def _create_result_output(self, filename, is_min, is_quad,
                              fake_bounds, res):
        """Creates a string summarising the solving process of an
        instance.
        """
        # Get additional data.
        if self._sol_file is not None:
            instance_name = (filename.split('/')[-1]).split('.')[0]
            lower, upper = self._read_bounds(instance_name)
        else:
            upper = 'unknown'
            lower = 'unknown'

        out = '\n--------------------------------------------------\n'
        out += 'Instance: \t {}\n'.format(filename)
        out += 'Objective: \t {} {}\n'.format('min' if is_min else 'max',
                                              'quad' if is_quad else 'lin')
        out += 'Upper bound: \t {}\n'.format(upper)
        out += 'Lower bound: \t {}\n'.format(lower)
        out += 'Constraints: \t {}\n'.format(
            Stats.get_bounding_initial_nconss())
        out += 'Variables: \t {}\n'.format(res.problem.number_of_variables)
        out += 'Nonzeros: \t {}\n'.format(res.problem.number_of_nonzeros)
        out += '\n'
        out += 'Solver time: \t {:.3f}\n'.format(res.solver.wallclock_time)
        out += 'Solution status: {}\n'.format(
            res.solver.termination_condition)
        out += 'Upper bound: \t {}\n'.format(res.problem.upper_bound)
        out += 'Lower bound: \t {}\n'.format(res.problem.lower_bound)

        key = 'Number of created subproblems'
        nsubprob = res.solver.statistics.branch_and_bound[key]
        out += 'Created nodes: \t {}\n'.format(nsubprob)
        key = 'Number of considered subproblems'
        nsubprob = res.solver.statistics.branch_and_bound[key]
        out += 'Handled nodes: \t {}\n'.format(nsubprob)

        out += 'Constraints:\n'
        hdlr_list = Stats.get_initial_hdlr_constraints()
        for (hdlr, nconss) in hdlr_list:
            out += '  {} \t {}\n'.format(nconss, hdlr)

        out += 'Fake varbounds:  {}\n'.format(fake_bounds)
        out += '--------------------------------------------------\n\n'
        return out
Esempio n. 3
0
    def identify(self):
        """Calls the identify methods of the constraint handlers for
        all unclassified constraints in the registered current instance.

        The identify methods of the constraint handlers are called. The
        order is given by the identify priority order (smaller priority
        first). All indices of unclassified constraints are passed
        using the sets parameter. Before calling a plugin function, a
        current data object is appended to the _curdata stack.

        Unaccepted user operations: Branching, bounds tightening,
        adding constraints, declaring infeasibility.

        :return: A UserInputStatus.
        """
        assert self._cur_instance is not None
        instance = self._cur_instance
        # Iterate over all constraint handlers in the specified order.
        for (_, hdlr) in self._hdlrs_ident:
            # Generate unclassified constraint sets.
            sets = {}
            model = instance.model()
            for constype in hdlr.constypes():
                sets[constype] = instance.unclassified(constype)
            # Set up current data.
            curdata = _CurrentData.create(hdlr)
            # Call identify method.
            self._curdata.append(curdata)
            self._get_hdlr(hdlr).identify(sets, model, self._solver)
            self._curdata.pop()

            Stats.identify(self, curdata)

            # Handle user input.
            if curdata.ncuts > 0 or curdata.ntighten > 0 or curdata.branched\
                    or curdata.infeasible:
                raise UserInputError('Unallowed interface operation during '
                                     'identify process.')
        if instance.nunclassified() == 0:
            return UserInputStatus.OK
        else:
            raise UserInputError('After calling all constraint handlers, '
                                 'there are still unclassified constraints.')
Esempio n. 4
0
 def solve(self, py_model):
     """Initiates the solving process. Calls the branch and bound
     algorithm.
     :param py_model: Model to solve.
     """
     Stats.initialise(verbosity=self._verbosity)
     self._py_model = py_model
     # Create instance.
     instance = Instance.create_instance(py_model.clone())
     # Create bnb_tree.
     self._bnb_tree = BranchAndBound.create(self)
     self._bnb_tree.register_root_instance(instance)
     # Check sanity of user setup.
     self._check_setup_sanity(instance)
     # Start solving process.
     self._stage = SolvingStage.SOLVING
     res = self._bnb_tree.execute(self._gap_epsilon)
     self._stage = SolvingStage.DONE
     self._postprocess(res)
     self._stage = SolvingStage.SETUP
     return self._result
Esempio n. 5
0
    def prepare(self):
        """Calls the prepare methods of the constraint handlers for
        all of their constraints in the registered current instance.

        The prepare methods of the constraint handlers are called. The
        order is given by the enforce priority order (smaller priority
        first). All indices of constraints belonging to the handler are
        passed using the sets parameter. Before calling a plugin
        function, a current data object is appended to the _curdata
        stack.

        Unaccepted user operations: None.

        :return: A UserInputStatus.
        """
        assert self._cur_instance is not None
        instance = self._cur_instance
        # Iterate over all constraint handlers in the specified order.
        for (_, hdlr) in self._hdlrs_enf:
            # Generate constraint handler sets.
            sets = instance.conshandler_sets(hdlr)
            model = instance.model()
            # Set up current data.
            curdata = _CurrentData.create(hdlr)
            # Call prepare method.
            self._curdata.append(curdata)
            self._get_hdlr(hdlr).prepare(sets, model, self._solver)
            self._curdata.pop()

            Stats.prepare(self, curdata)

            # Handle user input.
            if curdata.infeasible:
                return UserInputStatus.INFEASIBLE
            elif curdata.branched:
                return UserInputStatus.BRANCHED
            elif curdata.nmatched > 0:
                raise UserInputError('Unallowed interface operation during '
                                     'prepare process.')
        return UserInputStatus.OK
Esempio n. 6
0
    def solving_stage_test(self):
        """Test the call of different interface functions in the
        solving stage.
        """
        self.solver = PyMINLP()
        solver = self.solver
        self.coord = solver._coordinator
        coord = self.coord
        coord._bnb_tree = BranchAndBound.create(coord)
        Stats.initialise(1)

        # Test identify.
        solver.use_constraint_handler('INTTEST_Hdlr_1', ['Data'], 1, 1, True)
        solver.use_constraint_handler('INTTEST_Hdlr_2',
                                      ['InfDynamics', 'SusDynamics'], 2, 2)

        coord._stage = SolvingStage.SOLVING

        self.assertRaises(SolvingStageError, solver.set_epsilon)

        coord.set_instance(self.int_inst)

        self.assertRaises(UserInputError, coord.identify)

        # Test prepare.
        INTTEST_Hdlr_1._mode = 1
        INTTEST_Hdlr_2._mode = 1
        self.assertEqual(coord.prepare(), UserInputStatus.BRANCHED)
        INTTEST_Hdlr_1._mode = 2
        INTTEST_Hdlr_2._mode = 2
        self.assertEqual(coord.prepare(), UserInputStatus.INFEASIBLE)
        INTTEST_Hdlr_1._mode = 3
        INTTEST_Hdlr_2._mode = 3
        self.assertEqual(coord.prepare(), UserInputStatus.OK)
        INTTEST_Hdlr_1._mode = 4
        INTTEST_Hdlr_2._mode = 4
        self.assertRaises(UserInputError, coord.prepare)
Esempio n. 7
0
    def execute(self, epsilon=None):
        """Performs the branch and bound algorithm.
        Precondition: An instance is registered.
        :param epsilon: The gap epsilon for finishing the solving
        process.
        :return: The result (BnBResult)."""
        assert self._root_node is not None

        Stats.start_bnb(self)

        # Initialize data.
        self._lower_bound = -np.inf
        self._upper_bound = np.inf
        open_nodes = self._open_nodes
        heapq.heappush(open_nodes,
                       (self._root_node.lower_bound, self._root_node))
        self._node_count = 0

        # Outer loop for all generated nodes.
        while len(open_nodes) > 0:

            # Update global lower bound if possible.
            lower, _ = open_nodes[0]
            if lower > self._lower_bound:
                self._lower_bound = lower

            # Check if current solution is good enough.
            if epsilon is not None \
                    and self._upper_bound - self._lower_bound < epsilon:
                break

            # Perform node selection and register the chosen instance.
            # TODO maybe offer a plugin for this.
            _, self._cur_node = heapq.heappop(open_nodes)
            node = self._cur_node
            self._interface.set_instance(node.instance)
            self._child_nodes = []
            self._node_count += 1

            # Read the minimal lower bound of all other nodes.
            if len(open_nodes) > 0:
                minimal_lower, _ = open_nodes[0]
            else:
                minimal_lower = None

            # Check time limit.
            if self._interface.time_limit_reached():
                Stats.finish_bnb(self)
                return BnBResult.TIMEOUT

            Stats.start_node(self)

            # Assign all constraints of this node to a constraint
            # handler.
            result = self._interface.identify()
            if result != UserInputStatus.OK:
                raise ValueError('Status after identify method call is {}. '
                                 'Expected OK.'.format(result))

            # Call preparation routines.
            result = self._interface.prepare()
            if result == UserInputStatus.OK:
                node_done = False
            elif result == UserInputStatus.INFEASIBLE:
                node_done = True
            elif result == UserInputStatus.BRANCHED:
                if len(self._child_nodes) == 0:
                    raise ValueError('Branching performed but no child nodes '
                                     'were given.')
                for node in self._child_nodes:
                    heapq.heappush(self._open_nodes, (node.lower_bound, node))
                self._cur_node.set_branched()
                node_done = True
            else:
                raise ValueError('Status after preparation method call is {}. '
                                 'Expected OK, INFEASIBLE, '
                                 'BRANCHED.'.format(result))

            # Inner loop for a single node.
            while not node_done:

                # Check time limit.
                if self._interface.time_limit_reached():
                    Stats.finish_bnb(self)
                    return BnBResult.TIMEOUT

                Stats.start_rel_sol(self)

                # Solve relaxation.
                result = self._interface.solve_relaxation()
                # Handle the user input.
                if result != UserInputStatus.OK:
                    raise ValueError(
                        'Status after solve_relaxation method call is {}. '
                        'Expected OK.'.format(result))

                Stats.finish_rel_sol(self)

                # Evaluate result of the solution of the relaxation.
                if node.has_optimal_solution():
                    node.update_lower_bound()
                    # Update global lower bound if possible.
                    if node.lower_bound > self._lower_bound:
                        if minimal_lower is None:
                            self._lower_bound = node.lower_bound
                        elif minimal_lower > self._lower_bound:
                            self._lower_bound = min(minimal_lower,
                                                    node.lower_bound)
                elif node.relax_infeasible():
                    break
                else:
                    raise ValueError(
                        'The relaxation solver terminated with '
                        'termination condition {}.'.format(
                            node.instance.relax_termination_condition()))

                # Check dual bound.
                sol = node.rel_sol_value()
                if sol >= self._upper_bound:
                    # Pruning.
                    node_done = True
                else:
                    # Feasibility checking.
                    if node.feasible():
                        node_done = True
                        # Update the global bounds.
                        self._upper_bound = sol
                        self._best_node = node
                        # Check whether new solution prunes any other
                        # nodes.
                        del_count = 0
                        for i in reversed(range(len(open_nodes))):
                            _, open_node = open_nodes[i]
                            if open_node.lower_bound >= self._upper_bound:
                                open_nodes.pop(i)
                                del_count += 1
                    else:
                        # Enforce the violated constraints.
                        result = self._interface.enforce()
                        # Handle user decision.
                        if result == UserInputStatus.INFEASIBLE:
                            node_done = True
                        elif result == UserInputStatus.BRANCHED:
                            if len(self._child_nodes) == 0:
                                raise ValueError('Branching performed but no '
                                                 'child nodes were given.')
                            for node in self._child_nodes:
                                heapq.heappush(self._open_nodes,
                                               (node.lower_bound, node))
                            self._cur_node.set_branched()
                            node_done = True
                        elif result == UserInputStatus.RESOLVE:
                            node_done = False
                        else:
                            raise ValueError(
                                'Status after solve_relaxation method'
                                'call is {}. Expected OK, RESOLVE '
                                'or INFEASIBLE.'.format(result))

            Stats.finish_node(self)

        if len(open_nodes) == 0 and self._best_node is not None:
            self._lower_bound = self._best_node.lower_bound

        Stats.finish_bnb(self)

        if self._best_node is None:
            return BnBResult.INFEASIBLE
        else:
            return BnBResult.OPTIMAL
Esempio n. 8
0
    def _postprocess(self, result):
        """Create a SolverResult object, save it as _result and write
        solution data onto the user given Pyomo object (_py_model).
        :param result: The BnBResult indicating the result of the
        branch and bound process.
        """
        assert self._stage == SolvingStage.DONE
        assert type(result) is BnBResult

        # Copy solution to model.
        optinst = self._bnb_tree.best_instance()
        if optinst is not None:
            optinst.write_solution(self._py_model)

        # Create result object.
        self._result = SolverResults()
        soln = Solution()

        # Set some result meta data.
        self._result.solver.name = ('PyMINLP')
        self._result.solver.wallclock_time = Stats.get_solver_time()

        # Set solver status.
        if result == BnBResult.INFEASIBLE:
            self._result.solver.status = SolverStatus.ok
            self._result.solver.termination_condition \
                = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible
        elif result == BnBResult.OPTIMAL:
            self._result.solver.status = SolverStatus.ok
            self._result.solver.termination_condition \
                = TerminationCondition.optimal
            soln.status = SolutionStatus.optimal
        elif result == BnBResult.TIMEOUT:
            self._result.solver.status = SolverStatus.ok
            self._result.solver.termination_condition \
                = TerminationCondition.maxTimeLimit
            soln.status = SolutionStatus.stoppedByLimit

        if result == BnBResult.OPTIMAL or result == BnBResult.TIMEOUT:
            # Set solution data.
            self._result.problem.sense = pyomo.core.kernel.minimize
            self._result.problem.lower_bound = self._bnb_tree.lower_bound()
            self._result.problem.upper_bound = self._bnb_tree.upper_bound()
            soln.gap = self._bnb_tree.upper_bound() \
                       - self._bnb_tree.lower_bound()

            if optinst is not None:
                # Set objective data.
                obj = self._py_model.component_objects(Objective)
                for o in obj:
                    obj_name = o.name
                    obj_val = value(o)
                    break
                soln.objective[obj_name] = {'Value': obj_val}

                # Set variable data.
                #for vartype in self._py_model.component_objects(Var):
                #    for v in vartype:
                #        name = vartype[v].name
                #        val = value(vartype[v])
                #        soln.variable[name] = {'Value': val}

        # Set problem instance data.
        self._result.problem.name = self._py_model.name
        self._result.problem.number_of_constraints = \
            self._py_model.nconstraints()
        # self._result.problem.number_of_nonzeros = None
        self._result.problem.number_of_variables = self._py_model.nvariables()
        # self._result.problem.number_of_binary_variables = None
        # self._result.problem.number_of_integer_variables = None
        # self._result.problem.number_of_continuous_variables = None
        self._result.problem.number_of_objectives = \
            self._py_model.nobjectives()

        # Set branch and bound data.
        nsubprob = 'Number of created subproblems'
        self._result.solver.statistics.branch_and_bound[nsubprob] = \
            self._bnb_tree.nopennodes() + self._bnb_tree.nconsiderednodes()
        nsubprob = 'Number of considered subproblems'
        self._result.solver.statistics.branch_and_bound[nsubprob] = \
            self._bnb_tree.nconsiderednodes()

        self._result.solution.insert(soln)
Esempio n. 9
0
 def time_limit_reached(self):
     return Stats.get_solver_time() > self._time_limit