예제 #1
0
    def define(self, *names, **kwargs):
        """Define variable in the problem.

        Variables must be defined before they can be accessed by var() or
        set(). This function takes keyword arguments lower and upper to define
        the bounds of the variable (default: -inf to inf). The keyword argument
        types can be used to select the type of the variable (Continuous
        (default), Binary or Integer). Setting any variables different than
        Continuous will turn the problem into an MILP problem.
        """
        names = tuple(names)
        lower = kwargs.get('lower', None)
        upper = kwargs.get('upper', None)
        vartype = kwargs.get('types', None)

        # Repeat values if a scalar is given
        if lower is None or isinstance(lower, numbers.Number):
            lower = repeat(lower, len(names))
        if upper is None or isinstance(upper, numbers.Number):
            upper = repeat(upper, len(names))
        if vartype is None or vartype in (VariableType.Continuous,
                                          VariableType.Binary,
                                          VariableType.Integer):
            vartype = repeat(vartype, len(names))

        # Assign default values
        vartype = tuple(VariableType.Continuous if value is None else value
                        for value in vartype)

        if len(names) == 0:
            return

        var_indices = count(swiglpk.glp_add_cols(self._p, len(names)))

        for i, name, lb, ub, vt in zip(var_indices, names, lower, upper,
                                       vartype):
            self._variables[name] = i

            lb = None if lb == -_INF else lb
            ub = None if ub == _INF else ub

            if lb is None and ub is None:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_FR, 0, 0)
            elif lb is None:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_UP, 0,
                                         float(ub))
            elif ub is None:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_LO, float(lb),
                                         0)
            elif lb == ub:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_FX, float(lb),
                                         0)
            else:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_DB, float(lb),
                                         float(ub))

            if vt != VariableType.Continuous:
                swiglpk.glp_set_col_kind(self._p, i, self.VARTYPE_MAP[vt])

        self._do_presolve = True
예제 #2
0
 def _add_variables(self, variables):
     for variable in variables:
         glp_add_cols(self.problem, 1)
         index = glp_get_num_cols(self.problem)
         glp_set_col_name(self.problem, index, str(variable.name))
         variable.problem = self
         self._glpk_set_col_bounds(variable)
         glp_set_col_kind(self.problem, variable._index, _VTYPE_TO_GLPK_VTYPE[variable.type])
     super(Model, self)._add_variables(variables)
예제 #3
0
 def type(self, value):
     try:
         glpk_kind = _VTYPE_TO_GLPK_VTYPE[value]
     except KeyError:
         raise Exception("GLPK cannot handle variables of type %s. \
                     The following variable types are available:\n" +
                         " ".join(_VTYPE_TO_GLPK_VTYPE.keys()))
     glp_set_col_kind(self.problem.problem, self.index, glpk_kind)
     interface.Variable.type.fset(self, value)
예제 #4
0
 def _add_variables(self, variables):
     for variable in variables:
         glp_add_cols(self.problem, 1)
         index = glp_get_num_cols(self.problem)
         glp_set_col_name(self.problem, index, str(variable.name))
         variable.problem = self
         self._glpk_set_col_bounds(variable)
         glp_set_col_kind(self.problem, variable.index, _VTYPE_TO_GLPK_VTYPE[variable.type])
     super(Model, self)._add_variables(variables)
예제 #5
0
파일: glpk.py 프로젝트: jonls/psamm
    def define(self, *names, **kwargs):
        """Define variable in the problem.

        Variables must be defined before they can be accessed by var() or
        set(). This function takes keyword arguments lower and upper to define
        the bounds of the variable (default: -inf to inf). The keyword argument
        types can be used to select the type of the variable (Continuous
        (default), Binary or Integer). Setting any variables different than
        Continuous will turn the problem into an MILP problem.
        """
        names = tuple(names)
        lower = kwargs.get('lower', None)
        upper = kwargs.get('upper', None)
        vartype = kwargs.get('types', None)

        # Repeat values if a scalar is given
        if lower is None or isinstance(lower, numbers.Number):
            lower = repeat(lower, len(names))
        if upper is None or isinstance(upper, numbers.Number):
            upper = repeat(upper, len(names))
        if vartype is None or vartype in (
                VariableType.Continuous, VariableType.Binary,
                VariableType.Integer):
            vartype = repeat(vartype, len(names))

        # Assign default values
        vartype = tuple(VariableType.Continuous if value is None else value
                        for value in vartype)

        if len(names) == 0:
            return

        var_indices = count(swiglpk.glp_add_cols(self._p, len(names)))

        for i, name, lb, ub, vt in zip(
                var_indices, names, lower, upper, vartype):
            self._variables[name] = i

            lb = None if lb == -_INF else lb
            ub = None if ub == _INF else ub

            if lb is None and ub is None:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_FR, 0, 0)
            elif lb is None:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_UP, 0, ub)
            elif ub is None:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_LO, lb, 0)
            elif lb == ub:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_FX, lb, 0)
            else:
                swiglpk.glp_set_col_bnds(self._p, i, swiglpk.GLP_DB, lb, ub)

            if vt != VariableType.Continuous:
                swiglpk.glp_set_col_kind(self._p, i, self.VARTYPE_MAP[vt])

        self._do_presolve = True
예제 #6
0
 def type(self, value):
     try:
         glpk_kind = _VTYPE_TO_GLPK_VTYPE[value]
     except KeyError:
         raise ValueError(
             "GLPK cannot handle variables of type %s. The following variable types are available:\n"
             + " ".join(_VTYPE_TO_GLPK_VTYPE.keys()))
     if self.problem is not None:
         glp_set_col_kind(self.problem.problem, self._index, glpk_kind)
     interface.Variable.type.fset(self, value)
예제 #7
0
def setup_col(
    problem: SwigPyObject,
    j: int,
    recipe: 'Recipe',
    resource_indices: Dict[str, int],
    min_clock: Optional[int] = None,
    fixed_clock: Optional[int] = None,
):
    lp.glp_set_col_name(problem, j, recipe.name)

    # The game's clock scaling resolution is one percentage point, so we
    # ask for integers with an implicit scale of 100
    lp.glp_set_col_kind(problem, j, lp.GLP_IV)

    # All recipes are currently weighed the same
    lp.glp_set_obj_coef(problem, j, 1)

    if fixed_clock is None:
        # All recipes must have at least 0 instances
        lp.glp_set_col_bnds(
            problem,
            j,
            lp.GLP_LO,
            min_clock or 0,
            float('inf'),  # Lower and upper boundaries
        )
    else:
        # Set our desired (fixed) outputs
        lp.glp_set_col_bnds(
            problem,
            j,
            lp.GLP_FX,
            fixed_clock,
            fixed_clock,  # Boundaries are equal (variable is fixed)
        )

    # The constraint coefficients are just the recipe rates
    n_sparse = len(recipe.rates)
    ind = lp.intArray(n_sparse + 1)
    val = lp.doubleArray(n_sparse + 1)
    for i, (resource, rate) in enumerate(recipe.rates.items(), 1):
        ind[i] = resource_indices[resource]
        val[i] = rate
    lp.glp_set_mat_col(problem, j, n_sparse, ind, val)
예제 #8
0
    def _import_problem(self):
        import swiglpk as glpk

        if self.verbosity() >= 1:
            glpk.glp_term_out(glpk.GLP_ON)
        else:
            glpk.glp_term_out(glpk.GLP_OFF)

        # Create a problem instance.
        p = self.int = glpk.glp_create_prob();

        # Set the objective.
        if self.ext.objective[0] in ("find", "min"):
            glpk.glp_set_obj_dir(p, glpk.GLP_MIN)
        elif self.ext.objective[0] is "max":
            glpk.glp_set_obj_dir(p, glpk.GLP_MAX)
        else:
            raise NotImplementedError("Objective '{0}' not supported by GLPK."
                .format(self.ext.objective[0]))

        # Set objective function shift
        if self.ext.objective[1] is not None \
        and self.ext.objective[1].constant is not None:
            if not isinstance(self.ext.objective[1], AffinExp):
                raise NotImplementedError("Non-linear objective function not "
                    "supported by GLPK.")

            if self.ext.objective[1].constant.size != (1,1):
                raise NotImplementedError("Non-scalar objective function not "
                    "supported by GLPK.")

            glpk.glp_set_obj_coef(p, 0, self.ext.objective[1].constant[0])

        # Add variables.
        # Multideminsional variables are split into multiple scalar variables
        # represented as matrix columns within GLPK.
        for varName in self.ext.varNames:
            var = self.ext.variables[varName]

            # Add a column for every scalar variable.
            numCols = var.size[0] * var.size[1]
            glpk.glp_add_cols(p, numCols)

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

                # Assign a name to the scalar variable.
                scalarName = varName
                if numCols > 1:
                    x = localIndex // var.size[0]
                    y = localIndex % var.size[0]
                    scalarName += "_{:d}_{:d}".format(x + 1, y + 1)
                glpk.glp_set_col_name(p, glpkIndex, scalarName)

                # Assign bounds to the scalar variable.
                lower, upper = var.bnd.get(localIndex, (None, None))
                if lower is not None and upper is not None:
                    if lower == upper:
                        glpk.glp_set_col_bnds(
                            p, glpkIndex, glpk.GLP_FX, lower, upper)
                    else:
                        glpk.glp_set_col_bnds(
                            p, glpkIndex, glpk.GLP_DB, lower, upper)
                elif lower is not None and upper is None:
                    glpk.glp_set_col_bnds(p, glpkIndex, glpk.GLP_LO, lower, 0)
                elif lower is None and upper is not None:
                    glpk.glp_set_col_bnds(p, glpkIndex, glpk.GLP_UP, 0, upper)
                else:
                    glpk.glp_set_col_bnds(p, glpkIndex, glpk.GLP_FR, 0, 0)

                # Assign a type to the scalar variable.
                if var.vtype in ("continuous", "symmetric"):
                    glpk.glp_set_col_kind(p, glpkIndex, glpk.GLP_CV)
                elif var.vtype == "integer":
                    glpk.glp_set_col_kind(p, glpkIndex, glpk.GLP_IV)
                elif var.vtype == "binary":
                    glpk.glp_set_col_kind(p, glpkIndex, glpk.GLP_BV)
                else:
                    raise NotImplementedError("Variable type '{0}' not "
                        "supported by GLPK.".format(var.vtype()))

                # Set objective function coefficient of the scalar variable.
                if self.ext.objective[1] is not None \
                and var in self.ext.objective[1].factors:
                    glpk.glp_set_obj_coef(p, glpkIndex,
                        self.ext.objective[1].factors[var][localIndex])

        # Add constraints.
        # Multideminsional constraints are split into multiple scalar
        # constraints represented as matrix rows within GLPK.
        rowOffset = 1
        for constraintNum, constraint in enumerate(self.ext.constraints):
            if not isinstance(constraint, AffineConstraint):
                raise NotImplementedError(
                    "Non-linear constraints not supported by GLPK.")

            # Add a row for every scalar constraint.
            # Internally, GLPK uses an auxiliary variable for every such row,
            # bounded by the right hand side of the scalar constraint in a
            # canonical form.
            numRows = len(constraint)
            glpk.glp_add_rows(p, numRows)

            self._debug("Handling PICOS Constraint: " + str(constraint))

            # Split multidimensional constraints into multiple scalar ones.
            for localConIndex, (glpkVarIndices, coefficients, rhs) in \
                    enumerate(constraint.sparse_Ab_rows(
                    None, indexFunction = lambda picosVar, i:
                    self._picos2glpk_variable_index(picosVar.startIndex + i))):
                # Determine GLPK's row index of the scalar constraint.
                glpkConIndex = rowOffset + localConIndex
                numColumns   = len(glpkVarIndices)

                # Name the auxiliary variable associated with the current row.
                if constraint.name:
                    name = constraint.name
                else:
                    name = "rhs_{:d}".format(constraintNum)
                if numRows > 1:
                    x = localConIndex // constraint.size[0]
                    y = localConIndex % constraint.size[0]
                    name += "_{:d}_{:d}".format(x + 1, y + 1)
                glpk.glp_set_row_name(p, glpkConIndex, name)

                # Assign bounds to the auxiliary variable.
                if constraint.is_equality():
                    glpk.glp_set_row_bnds(p, glpkConIndex, glpk.GLP_FX, rhs,rhs)
                elif constraint.is_increasing():
                    glpk.glp_set_row_bnds(p, glpkConIndex, glpk.GLP_UP, 0, rhs)
                elif constraint.is_decreasing():
                    glpk.glp_set_row_bnds(p, glpkConIndex, glpk.GLP_LO, rhs, 0)
                else:
                    assert False, "Unexpected constraint relation."

                # Set coefficients for current row.
                # Note that GLPK requires a glpk.intArray containing column
                # indices and a glpk.doubleArray of same size containing the
                # coefficients for the listed column index. The first element
                # of both arrays (with index 0) is skipped by GLPK.
                glpkVarIndicesArray = glpk.intArray(numColumns + 1)
                for i in range(numColumns):
                    glpkVarIndicesArray[i + 1] = glpkVarIndices[i]

                coefficientsArray = glpk.doubleArray(numColumns + 1)
                for i in range(numColumns):
                    coefficientsArray[i + 1] = coefficients[i]

                glpk.glp_set_mat_row(p, glpkConIndex, numColumns,
                    glpkVarIndicesArray, coefficientsArray)

            rowOffset += numRows
예제 #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)