Exemple #1
0
    def solve(self, sense=None):
        """Solve problem."""
        if sense is not None:
            self.set_objective_sense(sense)

        parm = swiglpk.glp_smcp()
        swiglpk.glp_init_smcp(parm)
        parm.presolve = swiglpk.GLP_ON

        logger.debug('Solving using glp_simplex()')
        r = swiglpk.glp_simplex(self._p, parm)
        if r in (swiglpk.GLP_ENOPFS, swiglpk.GLP_ENODFS):
            self._result = Result(self, r)
            return self._result
        elif r != 0:
            raise GLPKError('glp_simplex: {}'.format(r))

        if swiglpk.glp_get_num_int(self._p) == 0:
            self._result = Result(self)
        else:
            # The intopt MILP solver need an optimal solution to the LP
            # relaxation. Therefore, glp_simplex has to run before glp_intopt
            # for MILP problems.
            logger.debug('Solving using glp_intopt()')
            r = swiglpk.glp_intopt(self._p, None)
            if r != 0:
                raise GLPKError('glp_intopt: {}'.format(r))

            self._result = MIPResult(self)

        return self._result
Exemple #2
0
    def solve(self, sense=None):
        """Solve problem."""
        if sense is not None:
            self.set_objective_sense(sense)

        parm = swiglpk.glp_smcp()
        swiglpk.glp_init_smcp(parm)
        if self._do_presolve:
            parm.presolve = swiglpk.GLP_ON
            self._do_presolve = False
        else:
            parm.presolve = swiglpk.GLP_OFF

        logger.debug('Solving using glp_simplex()')
        r = swiglpk.glp_simplex(self._p, parm)
        if r in (swiglpk.GLP_ENOPFS, swiglpk.GLP_ENODFS):
            self._result = Result(self, r)
            return self._result
        elif r != 0:
            raise GLPKError('glp_simplex: {}'.format(r))

        if swiglpk.glp_get_num_int(self._p) == 0:
            self._result = Result(self)
        else:
            # The intopt MILP solver need an optimal solution to the LP
            # relaxation. Therefore, glp_simplex has to run before glp_intopt
            # for MILP problems.
            logger.debug('Solving using glp_intopt()')
            r = swiglpk.glp_intopt(self._p, None)
            if r != 0:
                raise GLPKError('glp_intopt: {}'.format(r))

            self._result = MIPResult(self)

        return self._result
Exemple #3
0
 def _run_glp_mip(self):
     return_value = glp_intopt(self.problem, self.configuration._iocp)
     glpk_status = glp_mip_status(self.problem)
     if return_value == 0:
         status = _GLPK_STATUS_TO_STATUS[glpk_status]
     elif return_value == GLP_ETMLIM:
         status = interface.TIME_LIMIT
     else:
         status = _GLPK_STATUS_TO_STATUS[glpk_status]
         if status == interface.UNDEFINED:
             log.debug("Status undefined. GLPK status code returned by glp_intopt was %d" % return_value)
     return status
 def _run_glp_mip(self):
     return_value = glp_intopt(self.problem, self.configuration._iocp)
     glpk_status = glp_mip_status(self.problem)
     if return_value == 0:
         status = _GLPK_STATUS_TO_STATUS[glpk_status]
     elif return_value == GLP_ETMLIM:
         status = interface.TIME_LIMIT
     else:
         status = _GLPK_STATUS_TO_STATUS[glpk_status]
         if status == interface.UNDEFINED:
             log.debug("Status undefined. GLPK status code returned by glp_intopt was %d" % return_value)
     return status
Exemple #5
0
    def solve_IP(self):
        #clear solution file
        open("lp.sol", "w").close()

        #create the LP
        lp = glpk.glp_create_prob()

        #create the model translator
        tran = glpk.glp_mpl_alloc_wksp()

        #read the model intro translator
        glpk.glp_mpl_read_model(tran, "lp.mod", 0)
        #generate the model
        glpk.glp_mpl_generate(tran, None)
        #build the LP from the model
        glpk.glp_mpl_build_prob(tran, lp)

        #create and init params for MIP solver
        params = glpk.glp_iocp()
        glpk.glp_init_iocp(params)
        params.presolve = glpk.GLP_ON

        #solve the MIP
        glpk.glp_intopt(lp, params)

        #save solution
        #glpk.glp_write_sol(lp,"lp2.sol")
        glpk.glp_mpl_postsolve(tran, lp, glpk.GLP_MIP)

        #free resources
        glpk.glp_mpl_free_wksp(tran)
        glpk.glp_delete_prob(lp)

        #read solution from model
        self.read_solution()

        #delete model and solution files
        os.remove("lp.mod")
        os.remove("lp.sol")
Exemple #6
0
def solve_linprog(problem: SwigPyObject):
    level = level_for_parm()

    parm = lp.glp_smcp()
    lp.glp_init_smcp(parm)
    parm.msg_lev = level
    check_lp(problem, lp.glp_simplex(problem, parm))
    log_soln(problem, 'sol')

    parm = lp.glp_iocp()
    lp.glp_init_iocp(parm)
    parm.msg_lev = level
    check_lp(problem, lp.glp_intopt(problem, parm))
    log_soln(problem, 'mip')
Exemple #7
0
    def solve_unchecked(self, sense=None):
        """Solve problem and return result.

        The user must manually check the status of the result to determine
        whether an optimal solution was found. A :class:`SolverError` may still
        be raised if the underlying solver raises an exception.
        """
        if sense is not None:
            self.set_objective_sense(sense)

        parm = swiglpk.glp_smcp()
        swiglpk.glp_init_smcp(parm)
        if self._do_presolve:
            parm.presolve = swiglpk.GLP_ON
            self._do_presolve = False
        else:
            parm.presolve = swiglpk.GLP_OFF

        parm.tol_bnd = self._feasibility_tolerance
        parm.tol_dj = self._optimality_tolerance

        logger.debug('Solving using glp_simplex()')
        r = swiglpk.glp_simplex(self._p, parm)
        if r in (swiglpk.GLP_ENOPFS, swiglpk.GLP_ENODFS):
            self._result = Result(self, r)
            return self._result
        elif r != 0:
            raise GLPKError('glp_simplex: {}'.format(r))

        if swiglpk.glp_get_num_int(self._p) == 0:
            self._result = Result(self)
        else:
            # The intopt MILP solver needs an optimal solution to the LP
            # relaxation. Therefore, glp_simplex has to run before glp_intopt
            # for MILP problems.
            logger.debug('Solving using glp_intopt()')
            parm = swiglpk.glp_iocp()
            swiglpk.glp_init_iocp(parm)
            parm.tol_int = self._integrality_tolerance
            r = swiglpk.glp_intopt(self._p, parm)
            if r != 0:
                raise GLPKError('glp_intopt: {}'.format(r))

            self._result = MIPResult(self)

        return self._result
Exemple #8
0
    def _solve(self):
        import swiglpk as glpk

        # An alias to the internal problem instance.
        p = self.int

        continuous = self.ext.is_continuous()

        # Select LP solver (Simplex or Interior Point Method).
        if continuous:
            if self.ext.options["lp_root_method"] == "interior":
                interior = True
            else:
                # Default to Simplex.
                interior = False
            simplex = not interior
        else:
            simplex = interior = False

        # Select appropriate options container.
        if simplex:
            options = glpk.glp_smcp()
            glpk.glp_init_smcp(options)
        elif interior:
            options = glpk.glp_iptcp()
            glpk.glp_init_iptcp(options)
        else:
            options = glpk.glp_iocp()
            glpk.glp_init_iocp(options)

        # Handle "verbose" option.
        verbosity = self.verbosity()
        if verbosity < 0:
            options.msg_lev = glpk.GLP_MSG_OFF
        elif verbosity == 0:
            options.msg_lev = glpk.GLP_MSG_ERR
        elif verbosity == 1:
            options.msg_lev = glpk.GLP_MSG_ON
        elif verbosity >= 2:
            options.msg_lev = glpk.GLP_MSG_ALL

        # Handle "tol" option.
        # Note that GLPK knows three different tolerances for Simplex but none
        # for the Interior Point Method, while PICOS states that "tol" is meant
        # only for the IPM.
        # XXX: The option is unsupported but does not default to None, so we
        #      cannot warn the user.
        pass

        # Handle "maxit" option.
        if not simplex:
            self._handle_unsupported_option("maxit",
                "GLPK supports the 'maxit' option only with Simplex.")
        elif self.ext.options["maxit"] is not None:
                options.it_lim = int(self.ext.options["maxit"])

        # Handle "lp_root_method" option.
        # Note that the PICOS option is explicitly also meant for the MIP
        # preprocessing step but GLPK does not support it in that scenario.
        if not continuous:
            self._handle_unsupported_option("lp_root_method",
                "GLPK supports the 'lp_root_method' option only for LPs.")
        elif self.ext.options["lp_root_method"] is not None:
            if self.ext.options["lp_root_method"] == "interior":
                # Handled above.
                pass
            elif self.ext.options["lp_root_method"] == "psimplex":
                assert simplex
                options.meth = glpk.GLP_PRIMAL
            elif self.ext.options["lp_root_method"] == "dsimplex":
                assert simplex
                options.meth = glpk.GLP_DUAL
            else:
                self._handle_bad_option_value("lp_root_method")

        # Handle "timelimit" option.
        if interior:
            self._handle_unsupported_option("timelimit",
                "GLPK does not support the 'timelimit' option with the "
                "Interior Point Method.")
        elif self.ext.options["timelimit"] is not None:
            options.tm_lim = 1000 * int(self.ext.options["timelimit"])

        # Handle "gaplim" option.
        # Note that the option is silently ignored if passed alongside an LP;
        # while the solver does not allow us to pass the option in that case, it
        # is still technically a valid option as every LP is also a MIP.
        # TODO: Find out if "mip_gap" is really equivalent to "gaplim".
        if self.ext.options["gaplim"] is not None:
            if not continuous:
                options.mip_gap = float(self.ext.options["gaplim"])

        # Handle unsupported options.
        self._handle_unsupported_options(
            "lp_node_method", "treememory", "nbsol", "hotstart")

        # TODO: Add GLPK-sepcific options. Candidates are:
        #       For both Simplex and MIPs:
        #           tol_*, out_*
        #       For Simplex:
        #           pricing, r_test, obj_*
        #       For the Interior Point Method:
        #           ord_alg
        #       For MIPs:
        #           *_tech, *_heur, ps_tm_lim, *_cuts, cb_size, binarize

        # Attempt to solve the problem.
        with self._header():
            with self._stopwatch():
                if simplex:
                    # TODO: Support glp_exact.
                    error = glpk.glp_simplex(p, options)
                elif interior:
                    error = glpk.glp_interior(p, options)
                else:
                    options.presolve = glpk.GLP_ON
                    error = glpk.glp_intopt(p, options)

            # Conert error codes to text output.
            # Note that by printing it above the footer, this output is made to
            # look like it's coming from GLPK, which is technically wrong but
            # semantically correct.
            if error == glpk.GLP_EBADB:
                self._warn("Unable to start the search, because the initial "
                    "basis specified in the problem object is invalid.")
            elif error == glpk.GLP_ESING:
                self._warn("Unable to start the search, because the basis "
                    "matrix corresponding to the initial basis is singular "
                    "within the working precision.")
            elif error == glpk.GLP_ECOND:
                self._warn("Unable to start the search, because the basis "
                    "matrix corresponding to the initial basis is "
                    "ill-conditioned.")
            elif error == glpk.GLP_EBOUND:
                self._warn("Unable to start the search, because some double-"
                    "bounded variables have incorrect bounds.")
            elif error == glpk.GLP_EFAIL:
                self._warn("The search was prematurely terminated due to a "
                    "solver failure.")
            elif error == glpk.GLP_EOBJLL:
                self._warn("The search was prematurely terminated, because the "
                    "objective function being maximized has reached its lower "
                    "limit and continues decreasing.")
            elif error == glpk.GLP_EOBJUL:
                self._warn("The search was prematurely terminated, because the "
                    "objective function being minimized has reached its upper "
                    "limit and continues increasing.")
            elif error == glpk.GLP_EITLIM:
                self._warn("The search was prematurely terminated, because the "
                    "simplex iteration limit has been exceeded.")
            elif error == glpk.GLP_ETMLIM:
                self._warn("The search was prematurely terminated, because the "
                    "time limit has been exceeded.")
            elif error == glpk.GLP_ENOPFS:
                self._verbose("The LP has no primal feasible solution.")
            elif error == glpk.GLP_ENODFS:
                self._verbose("The LP has no dual feasible solution.")
            elif error != 0:
                self._warn("GLPK error {:d}.".format(error))

        # Retrieve primals.
        if self.ext.options["noprimals"]:
            primals = None
        else:
            primals = {}

            for variable in self.ext.variables.values():
                value = []

                for localIndex, picosIndex \
                in enumerate(range(variable.startIndex, variable.endIndex)):
                    glpkIndex = self._picos2glpk_variable_index(picosIndex)

                    if simplex:
                        localValue = glpk.glp_get_col_prim(p, glpkIndex);
                    elif interior:
                        localValue = glpk.glp_ipt_col_prim(p, glpkIndex);
                    else:
                        localValue = glpk.glp_mip_col_val(p, glpkIndex);

                    value.append(localValue)

                primals[variable.name] = value

        # Retrieve duals.
        # XXX: Returns the duals as a flat cvx.matrix to be consistent with
        #      other solvers. This feels incorrect when the constraint was given
        #      as a proper two dimensional matrix.
        if self.ext.options["noduals"] or not continuous:
            duals = None
        else:
            duals = []
            rowOffset = 1
            for constraintNum, constraint in enumerate(self.ext.constraints):
                assert isinstance(constraint, AffineConstraint)
                numRows = len(constraint)
                values = []
                for localConIndex in range(numRows):
                    glpkConIndex = rowOffset + localConIndex
                    if simplex:
                        localValue = glpk.glp_get_row_dual(p, glpkConIndex);
                    elif interior:
                        localValue = glpk.glp_ipt_row_dual(p, glpkConIndex);
                    else:
                        assert False
                    values.append(localValue)
                if constraint.is_decreasing():
                    duals.append(-cvxopt.matrix(values))
                else:
                    duals.append(cvxopt.matrix(values))
                rowOffset += numRows
            if glpk.glp_get_obj_dir(p) == glpk.GLP_MIN:
                duals = [-d for d in duals]

        # Retrieve objective value.
        if simplex:
            objectiveValue = glpk.glp_get_obj_val(p)
        elif interior:
            objectiveValue = glpk.glp_ipt_obj_val(p)
        else:
            objectiveValue = glpk.glp_mip_obj_val(p)

        # Retrieve solution metadata.
        meta = {}

        if simplex:
            # Set common entry "status".
            status = glpk.glp_get_status(p)
            if status is glpk.GLP_OPT:
                meta["status"] = "optimal"
            elif status is glpk.GLP_FEAS:
                meta["status"] = "feasible"
            elif status in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS):
                meta["status"] = "infeasible"
            elif status is glpk.GLP_UNBND:
                meta["status"] = "unbounded"
            elif status is glpk.GLP_UNDEF:
                meta["status"] = "undefined"
            else:
                meta["status"] = "unknown"

            # Set GLPK-specific entry "primal_status".
            primalStatus = glpk.glp_get_prim_stat(p)
            if primalStatus is glpk.GLP_FEAS:
                meta["primal_status"] = "feasible"
            elif primalStatus in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS):
                meta["primal_status"] = "infeasible"
            elif primalStatus is glpk.GLP_UNDEF:
                meta["primal_status"] = "undefined"
            else:
                meta["primal_status"] = "unknown"

            # Set GLPK-specific entry "dual_status".
            dualStatus = glpk.glp_get_dual_stat(p)
            if dualStatus is glpk.GLP_FEAS:
                meta["dual_status"] = "feasible"
            elif dualStatus in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS):
                meta["dual_status"] = "infeasible"
            elif dualStatus is glpk.GLP_UNDEF:
                meta["dual_status"] = "undefined"
            else:
                meta["dual_status"] = "unknown"
        elif interior:
            # Set common entry "status".
            status = glpk.glp_ipt_status(p)
            if status is glpk.GLP_OPT:
                meta["status"] = "optimal"
            elif status in (glpk.GLP_INFEAS, glpk.GLP_NOFEAS):
                meta["status"] = "infeasible"
            elif status is glpk.GLP_UNDEF:
                meta["status"] = "undefined"
            else:
                meta["status"] = "unknown"
        else:
            # Set common entry "status".
            status = glpk.glp_mip_status(p)
            if status is glpk.GLP_OPT:
                meta["status"] = "optimal"
            elif status is glpk.GLP_FEAS:
                meta["status"] = "feasible"
            elif status is glpk.GLP_NOFEAS:
                meta["status"] = "infeasible"
            elif status is glpk.GLP_UNDEF:
                meta["status"] = "undefined"
            else:
                meta["status"] = "unknown"

        return (primals, duals, objectiveValue, meta)
Exemple #9
0
def solve(nutrition_target, foods):
    '''
    Calculate food amounts to reach the nutrition target
    
    Parameters
    ----------
    nutrition_target : soylent_recipes.nutrition_target.NormalizedNutritionTarget
        The desired nutrition
    foods : np.array
        The foods to use to achieve the nutrition target. Contains exactly the
        nutrients required by the nutrition target in the exact same order. Rows
        represent foods, columns represent nutrients.
        
    Returns
    -------
    amounts : np.array(int) or None
        The amounts of each food to use to optimally achieve the nutrition
        target. ``amounts[i]`` is the amount of the i-th food to use. If the
        nutrition target cannot be achieved, returns None.
    '''
    # Implementation: using the GLPK C library via ecyglpki Python library binding
    # GLPK documentation: download it and look inside the package (http://ftp.gnu.org/gnu/glpk/)
    # GLPK wikibook: https://en.wikibooks.org/wiki/GLPK
    #
    # GPLK lingo: rows and columns refer to Ax=b where b_i are auxiliary
    # variables, x_i are structural variables. Setting constraints on rows, set
    # constraints on b_i, while column constraints are applied to x_i.
    
    # Note: glpk is powerful. We're using mostly the default settings.
    # Performance likely can be improved by tinkering with the settings; or even
    # by providing the solution to the least squares equivalent, with amounts
    # rounded afterwards, as starting point could improve performance.
    
    nutrition_target = nutrition_target.values
    
    problem = glp.glp_create_prob()
    try:
        glp.glp_add_rows(problem, len(nutrition_target))
        glp.glp_add_cols(problem, len(foods))
        
        # Configure columns/amounts
        for i in range(len(foods)):
            glp.glp_set_col_kind(problem, i+1, glp.GLP_IV)  # int
            glp.glp_set_col_bnds(problem, i+1, glp.GLP_LO, 0.0, np.nan)  # >=0
        
        # Configure rows/nutrients
        for i, extrema in enumerate(nutrition_target):
            if np.isnan(extrema[0]):
                bounds_type = glp.GLP_UP
            elif np.isnan(extrema[1]):
                bounds_type = glp.GLP_LO
            else:
                # Note: a nutrition target has either min, max or both and min!=max
                bounds_type = glp.GLP_DB
            glp.glp_set_row_bnds(problem, i+1, bounds_type, *extrema)
            
        # Load A of our Ax=b
        non_zero_count = foods.size
        row_indices = glp.intArray(non_zero_count+1)  # +1 because (insane) 1-indexing
        column_indices = glp.intArray(non_zero_count+1)
        values = glp.doubleArray(non_zero_count+1)
        for i, ((row, column), value) in enumerate(np.ndenumerate(foods.transpose())):
            row_indices[i+1] = row+1
            column_indices[i+1] = column+1
            values[i+1] = value
        glp.glp_load_matrix(problem, non_zero_count, row_indices, column_indices, values)
        
        # Solve
        int_opt_args = glp.glp_iocp()
        glp.glp_init_iocp(int_opt_args)
        int_opt_args.presolve = glp.GLP_ON  # without this, you have to provide an LP relaxation basis
        int_opt_args.msg_lev = glp.GLP_MSG_OFF  # be quiet, no stdout
        glp.glp_intopt(problem, int_opt_args)  # returns an error code; can safely ignore
        
        # Check we've got a valid solution
        #
        # Note: glp_intopt returns whether the algorithm completed successfully.
        # This does not imply you've got a good solution, it could even be
        # infeasible. glp_mip_status returns whether the solution is optimal,
        # feasible, infeasible or undefined. An optimal/feasible solution is not
        # necessarily a good solution. An optimal solution may even violate
        # bounds constraints. The thing you actually need to use is
        # glp_check_kkt and check that the solution satisfies KKT.PB (all within
        # bounds)
        max_error = glp.doubleArray(1)
        glp.glp_check_kkt(problem, glp.GLP_MIP, glp.GLP_KKT_PB, max_error, None, None, None)
        if not np.isclose(max_error[0], 0.0):
            # A row/column value exceeds its bounds
            return None
        
        # Return solution
        amounts = np.fromiter((glp.glp_mip_col_val(problem, i+1) for i in range(len(foods))), int)
        
        return amounts
    finally:
        glp.glp_delete_prob(problem)