コード例 #1
0
ファイル: test_lp_constraint.py プロジェクト: nandi6uc/flipy
    def test_to_lp_terms(self, x, y):
        # 2x + 3y + 7
        lhs = LpExpression('lhs', {x: 2, y: 3}, 7)
        # x + 5y + 2
        rhs = LpExpression('rhs', {x: 1, y: 5}, 2)

        constraint = LpConstraint(lhs, 'leq', rhs, 'test_constraint')

        assert constraint.to_lp_terms() == ['x', '- 2 y', '<=', '-5']
コード例 #2
0
    def test_write_no_objective(self, problem, x):
        rhs = LpExpression('rhs', {x: 1})
        lhs = LpExpression('lhs', {}, -2)
        constraint = LpConstraint(rhs, 'geq', lhs, 'constraint')
        problem.add_constraint(constraint)
        buffer = StringIO()

        with pytest.raises(Exception) as e:
            problem.write_lp(buffer)
        assert e.value.args == ('No objective', )
コード例 #3
0
 def test_write(self, problem, x):
     objective = LpObjective(name='minimize_cpm',
                             expression={x: 998},
                             constant=8)
     rhs = LpExpression('rhs', {x: 1})
     lhs = LpExpression('lhs', {}, -2)
     constraint = LpConstraint(rhs, 'geq', lhs, 'constraint')
     problem.add_constraint(constraint)
     problem.set_objective(objective)
     buffer = StringIO()
     problem.write_lp(buffer)
     flipy_string = buffer.getvalue()
     assert flipy_string == '\\* test_problem *\\\nMinimize\nminimize_cpm: 998 x + 8\nSubject To\nconstraint: x >= -2\nBounds\nx <= 10\nEnd'
コード例 #4
0
 def test_write_with_empty_constraint(self, problem, x):
     objective = LpObjective(name='minimize_cpm',
                             expression={x: 998},
                             constant=8,
                             sense=Maximize)
     constraint = LpConstraint(LpExpression('lhs', {x: 0}), 'leq',
                               LpExpression('rhs', {}), 'constraint')
     problem.add_constraint(constraint)
     problem.set_objective(objective)
     buffer = StringIO()
     problem.write_lp(buffer)
     flipy_string = buffer.getvalue()
     assert flipy_string == '\\* test_problem *\\\nMaximize\nminimize_cpm: 998 x + 8\nSubject To\nBounds\nx <= 10\nEnd'
コード例 #5
0
 def test_write_slack(self, problem, x):
     objective = LpObjective(name='minimize_cpm',
                             expression={x: 998},
                             constant=8,
                             sense=Maximize)
     rhs = LpExpression('rhs', {x: 1})
     lhs = LpExpression('lhs', {}, -2)
     constraint = LpConstraint(rhs, 'leq', lhs, 'constraint', True, 100)
     problem.add_constraint(constraint)
     problem.set_objective(objective)
     buffer = StringIO()
     problem.write_lp(buffer)
     flipy_string = buffer.getvalue()
     assert flipy_string == '\\* test_problem *\\\nMaximize\nminimize_cpm: 998 x - 100 constraint_slack_variable + 8\nSubject To\nconstraint: - constraint_slack_variable + x <= -2\nBounds\nx <= 10\nEnd'
コード例 #6
0
ファイル: test_lp_expression.py プロジェクト: nandi6uc/flipy
 def test__eq__(self, expression, x, y):
     assert expression == LpExpression(name='text_expr_3',
                                       expression={
                                           x: 998,
                                           y: 0
                                       },
                                       constant=8.0)
コード例 #7
0
ファイル: test_lp_expression.py プロジェクト: nandi6uc/flipy
def expression_2(x, y, z):
    return LpExpression(name='text_expr_2',
                        expression={
                            x: -5,
                            y: -2,
                            z: 0
                        },
                        constant=0)
コード例 #8
0
ファイル: lp_constraint.py プロジェクト: nandi6uc/flipy
    def breakdown(self) -> List['LpConstraint']:
        """ Breaks down a equality constraint into an upper bound and a lower bound constraint """
        if self.sense != 'eq':
            return [self]

        upper_bound_constraint = LpConstraint(
            name=self.name + '_ub',
            lhs=LpExpression(expression=copy.copy(self.lhs.expr)),
            rhs=LpExpression(expression=copy.copy(self.rhs.expr)),
            sense='leq',
            slack=self.slack,
            slack_penalty=self.slack_penalty,
        )

        lower_bound_constraint = self
        lower_bound_constraint.sense = 'geq'
        lower_bound_constraint.name += '_lb'
        return [lower_bound_constraint, upper_bound_constraint]
コード例 #9
0
    def test_add_constraint(self, problem, x):
        rhs = LpExpression('rhs', {x: 1})
        lhs = LpExpression('lhs', {x: 1}, 2)
        constraint = LpConstraint(rhs, 'geq', lhs, 'constraint')
        problem.add_constraint(constraint)

        assert problem.lp_constraints[constraint.name] == constraint
        assert problem.lp_variables[x.name] == x

        constraint = LpConstraint(lhs, 'geq', rhs, 'constraint')
        with pytest.raises(Exception) as e:
            problem.add_constraint(constraint)
        assert e.value.args == (
            'LP constraint name %s conflicts with an existing LP constraint' %
            constraint.name, )

        with pytest.raises(Exception) as e:
            problem.add_constraint(10)
        assert e.value.args == ('%s is not an LpConstraint' % 10, )
コード例 #10
0
ファイル: lp_constraint.py プロジェクト: nandi6uc/flipy
    def __init__(self,
                 lhs: LpExpression,
                 sense: str,
                 rhs: Optional[LpExpression] = None,
                 name: Optional[str] = None,
                 slack: bool = False,
                 slack_penalty: Numeric = 0,
                 copy_expr: bool = False) -> None:
        """ Initialize the constraint

        Parameters
        ----------
        lhs:
            The left-hand-side expression
        sense:
            The type of constraint (leq, geq, or eq)
        rhs:
            The right-hand-side expression
        name:
            The name of the constraint
        slack:
            Whether the constraint has slack
        slack_penalty:
            The penalty for the slack in the constraint
        copy_expr:
            Whether to copy the lhs and rhs expressions
        """
        self.lhs = lhs if not copy_expr else copy.copy(lhs)
        if rhs:
            self.rhs = rhs
        else:
            self.rhs = LpExpression()
        self.sense = sense
        self.name = name or ''
        self.slack = slack
        self.slack_penalty = slack_penalty

        self._slack_variable = None
コード例 #11
0
    def test_write_long(self, problem, x):
        a = LpVariable('a', low_bound=0, up_bound=10, var_type=VarType.Integer)
        b = LpVariable('b', low_bound=0, up_bound=10, var_type=VarType.Integer)
        c = LpVariable('c', low_bound=0, up_bound=10, var_type=VarType.Integer)
        d = LpVariable('d', low_bound=0, up_bound=10, var_type=VarType.Integer)
        e = LpVariable('e', var_type=VarType.Binary)
        f = LpVariable('f', var_type=VarType.Binary)
        g = LpVariable('g', var_type=VarType.Binary)
        h = LpVariable('h', var_type=VarType.Binary)
        vars = [a, b, c, d, e, f, g, h]

        # make sure objective is long enough to test the line break
        objective = LpObjective(name='minimize_cpm',
                                expression={v: 3.1415926535
                                            for v in vars},
                                constant=8)

        rhs = LpExpression('rhs', {a: 1000, b: 1000, c: 1000, d: 1000})
        lhs = LpExpression('lhs', {}, -2)
        constraint = LpConstraint(rhs, 'geq', lhs, 'constraint')

        problem.add_constraint(constraint)
        problem.set_objective(objective)
        buffer = StringIO()
        problem.write_lp(buffer)
        lp_str = buffer.getvalue()
        assert lp_str.split('\n') == [
            '\\* test_problem *\\', 'Minimize',
            'minimize_cpm: 3.1415926535 a + 3.1415926535 b + 3.1415926535 c + 3.1415926535 d',
            '+ 3.1415926535 e + 3.1415926535 f + 3.1415926535 g + 3.1415926535 h + 8',
            'Subject To',
            'constraint: 1000 a + 1000 b + 1000 c + 1000 d >= -2', 'Bounds',
            '0 <= a <= 10', '0 <= b <= 10', '0 <= c <= 10', '0 <= d <= 10',
            '0 <= e <= 1', '0 <= f <= 1', '0 <= g <= 1', '0 <= h <= 1',
            'Generals', 'a', 'b', 'c', 'd', 'Binaries', 'e', 'f', 'g', 'h',
            'End'
        ]
コード例 #12
0
ファイル: lp_constraint.py プロジェクト: nandi6uc/flipy
    def _shift_variables(self):
        """ Shifts all variables to the left hand side of the constraint, and shifts the
        constant to the right hand side of the constraints

        Returns
        -------
        flipy.LpExpression
            The shfited lhs expression
        float
            The shifted rhs constant
        """
        new_expr = LpExpression(expression=copy.copy(self.lhs.expr))

        for var, coeff in self.rhs.expr.items():
            new_expr.expr[var] -= coeff

        if self.slack:
            new_expr.expr[
                self.slack_variable] = -1 if self.sense == 'leq' else 1

        const = self.rhs.const - self.lhs.const
        return new_expr, const
コード例 #13
0
def expression(x):
    return LpExpression(name='test_expr', expression={x: 998}, constant=8)
コード例 #14
0
ファイル: test_lp_constraint.py プロジェクト: nandi6uc/flipy
def lhs(x, y):
    # x + 3y + 7
    return LpExpression('lhs', {x: 1, y: 3}, 7)
コード例 #15
0
ファイル: test_lp_constraint.py プロジェクト: nandi6uc/flipy
    def test_init_no_rhs(self, lhs):
        lp_constraint = LpConstraint(lhs, 'leq')

        assert lp_constraint.rhs == LpExpression()
コード例 #16
0
ファイル: test_lp_expression.py プロジェクト: nandi6uc/flipy
 def test_init(self):
     expression = LpExpression('', None, 5)
     assert expression.name == ''
     assert type(expression.expr) == defaultdict
     assert expression.const == 5
コード例 #17
0
ファイル: lp_constraint.py プロジェクト: nandi6uc/flipy
class LpConstraint:
    """ A class representing a linear constraint """
    def __init__(self,
                 lhs: LpExpression,
                 sense: str,
                 rhs: Optional[LpExpression] = None,
                 name: Optional[str] = None,
                 slack: bool = False,
                 slack_penalty: Numeric = 0,
                 copy_expr: bool = False) -> None:
        """ Initialize the constraint

        Parameters
        ----------
        lhs:
            The left-hand-side expression
        sense:
            The type of constraint (leq, geq, or eq)
        rhs:
            The right-hand-side expression
        name:
            The name of the constraint
        slack:
            Whether the constraint has slack
        slack_penalty:
            The penalty for the slack in the constraint
        copy_expr:
            Whether to copy the lhs and rhs expressions
        """
        self.lhs = lhs if not copy_expr else copy.copy(lhs)
        if rhs:
            self.rhs = rhs
        else:
            self.rhs = LpExpression()
        self.sense = sense
        self.name = name or ''
        self.slack = slack
        self.slack_penalty = slack_penalty

        self._slack_variable = None

    def _shift_variables(self):
        """ Shifts all variables to the left hand side of the constraint, and shifts the
        constant to the right hand side of the constraints

        Returns
        -------
        flipy.LpExpression
            The shfited lhs expression
        float
            The shifted rhs constant
        """
        new_expr = LpExpression(expression=copy.copy(self.lhs.expr))

        for var, coeff in self.rhs.expr.items():
            new_expr.expr[var] -= coeff

        if self.slack:
            new_expr.expr[
                self.slack_variable] = -1 if self.sense == 'leq' else 1

        const = self.rhs.const - self.lhs.const
        return new_expr, const

    def _shift_constant_right(self) -> None:
        """ Moves the constant on the lhs to the rhs """
        if not self.lhs.const:
            return
        self.rhs.const -= self.lhs.const
        self.lhs.const = 0

    @property
    def lhs(self) -> LpExpression:
        """ Getter for lhs expression """
        return self._lhs

    @lhs.setter
    def lhs(self, lhs_exp: LpExpression) -> None:
        """ Setter for lhs expression

        Raises
        ------
        ValueError
            If `lhs_exp` is not an LpExpression objective

        Parameters
        ----------
        lhs_exp:
            The lhs expression to set
        """
        if not isinstance(lhs_exp, LpExpression):
            raise ValueError('LHS of LpConstraint must be LpExpression')

        self._lhs = lhs_exp  # pylint: disable=W0201

    @property
    def rhs(self) -> LpExpression:
        """ Getter for rhs expression """
        return self._rhs

    @rhs.setter
    def rhs(self, rhs_exp: LpExpression) -> None:
        """ Setter for rhs expression

        Raises
        ------
        ValueError
            If `rhs_exp` is not an LpExpression objective

        Parameters
        -------
        rhs_exp:
            The rhs expression to set
        """
        if not isinstance(rhs_exp, LpExpression):
            raise ValueError('RHS of LpConstraint must be LpExpression')

        self._rhs = rhs_exp  # pylint: disable=W0201

    @property
    def sense(self) -> str:
        """ Getter for the sense of the constraint """
        return self._sense

    @sense.setter
    def sense(self, snse: str) -> None:
        """ Setter for the sense of the constraint. Raises error if not one of 'leq', 'eq', 'geq'

        Raises
        ------
        ValueError
            If `snse` is not one of `leq`, `eq` or `geq`

        Parameters
        ----------
        snse:
            The sense to set
        """
        try:
            assert snse.lower() in ('leq', 'eq', 'geq')
            self._sense = snse.lower()  # pylint: disable=W0201
        except (AttributeError, AssertionError):
            raise ValueError("Sense must be one of ('leq', 'eq', 'geq')")

    @property
    def lower_bound(self) -> Optional[Numeric]:
        """ Returns the lower bound on the shifted expression """
        if self.sense == 'leq':
            return None
        return self.rhs.const - self.lhs.const

    @property
    def upper_bound(self) -> Optional[Numeric]:
        """ Returns the upper bound on the shifted expression """
        if self.sense == 'geq':
            return None
        return self.rhs.const - self.lhs.const

    def breakdown(self) -> List['LpConstraint']:
        """ Breaks down a equality constraint into an upper bound and a lower bound constraint """
        if self.sense != 'eq':
            return [self]

        upper_bound_constraint = LpConstraint(
            name=self.name + '_ub',
            lhs=LpExpression(expression=copy.copy(self.lhs.expr)),
            rhs=LpExpression(expression=copy.copy(self.rhs.expr)),
            sense='leq',
            slack=self.slack,
            slack_penalty=self.slack_penalty,
        )

        lower_bound_constraint = self
        lower_bound_constraint.sense = 'geq'
        lower_bound_constraint.name += '_lb'
        return [lower_bound_constraint, upper_bound_constraint]

    @property
    def slack(self) -> bool:
        """ Getter for slack indicator """
        return self._slack

    @slack.setter
    def slack(self, slck: bool) -> None:
        """ Setter for slack indicator

        Raises
        ------
        ValueError
            If `slck` is not a bool type variable

        Parameters
        ----------
        slck:
            Whether the constraint has slack
        """
        if slck not in (True, False):
            raise ValueError('Slack indicator must be True or False')

        self._slack = slck  # pylint: disable=W0201

    @property
    def slack_variable(self) -> LpVariable:
        """ Getter for the slack variable of the problem. Sets if does not exist. """
        self._slack_variable = (self._slack_variable or LpVariable(
            name=self.name + '_slack_variable',
            var_type=VarType.Continuous,
            low_bound=0)) if self.slack else None
        return self._slack_variable

    @property
    def slack_penalty(self) -> Numeric:
        """ Getter for slack penalty of the constraint """
        return self._slack_penalty

    @slack_penalty.setter
    def slack_penalty(self, penalty: Numeric) -> None:
        """ Setter for slack penalty of the constraint. Raises error if negative.

        Raises
        ------
        ValueError
            If `penalty` has positive value

        Parameters
        ----------
        penalty:
            The slack penalty to set

        Raises
        ------
        ValueError
        """
        if penalty < 0:
            raise ValueError('Slack penalty must be non-negative')
        if penalty == 0 and self.slack:
            warnings.warn(
                'Slack penalty is zero. No incentive to meet this constraint')

        self._slack_penalty = penalty  # pylint: disable=W0201

    def check(self) -> bool:
        """ Checks if the constraint is satisfied given variable assignments """
        return {
            'leq': operator.le,
            'eq': operator.eq,
            'geq': operator.ge
        }[self.sense](
            self.lhs.evaluate() +
            (0 if not self.slack else self.slack_variable.evaluate() *
             (-1 if self.sense == 'leq' else 1)), self.rhs.evaluate())

    def to_lp_terms(self):
        """
        Returns the constraint as a list of terms like ['5', 'a', '<=', '10']

        Returns
        -------
        list(str)
            List of terms in string
        """
        new_expr, const = self._shift_variables()

        if not any(new_expr.expr.values()):
            return []

        if self.sense.lower() == 'leq':
            sense = '<='
        elif self.sense.lower() == 'geq':
            sense = '>='
        else:
            sense = '='

        terms = []
        terms += new_expr.to_lp_terms()
        terms.append(sense)
        terms.append(str(const))
        return terms
コード例 #18
0
ファイル: test_lp_expression.py プロジェクト: nandi6uc/flipy
 def test_to_lp_lp_expr_constant_zero(self, expression):
     expr = LpExpression(expression={}, constant=0)
     assert expr.to_lp_terms() == ['0']
コード例 #19
0
ファイル: test_lp_expression.py プロジェクト: nandi6uc/flipy
 def test_to_lp_lp_expr(self, x, y):
     expr = LpExpression(expression={x: 1, y: -1}, constant=100)
     assert expr.to_lp_terms() == ['x', '- y', '+ 100']
コード例 #20
0
ファイル: lp_reader.py プロジェクト: nandi6uc/flipy
    def read(cls, obj: Union[str, IO, TextIO, io.StringIO]) -> LpProblem:
        """ Reads in an LP file and parse it into a flipy.LpProblem object

        Raises
        ------
        ValueError
            If `obj` is unreadable and is not a LP string

        Parameters
        ----------
        obj: str or buffer

        Returns
        -------
        LpProblem
            Parsed LpProblem based on the LP file
        """
        if hasattr(obj, 'read'):
            content = obj.read()
        elif isinstance(obj, (str, bytes)):
            content = obj
            try:
                if os.path.isfile(content):
                    with open(content, "rb") as f:
                        content = f.read()
            except (TypeError, ValueError):
                pass
        else:
            raise ValueError("Cannot read object of type %r" %
                             type(obj).__name__)

        content = content.strip()

        problem_name = cls._find_problem_name(content)

        is_maximize, sections = cls._split_content_by_sections(
            cls._remove_comments(content))

        obj_name, obj_expr, obj_coeff = cls._parse_named_expression(
            sections['objective'])
        constraints = cls._parse_constraints(
            sections['constraints']) if 'constraints' in sections else []
        bounds = cls._parse_bounds(
            sections['bounds']) if 'bounds' in sections else []
        generals = cls._parse_generals(
            sections['generals']) if 'generals' in sections else []
        binaries = cls._parse_binaries(
            sections['binaries']) if 'binaries' in sections else []

        lp_variables = {}
        lp_objective = LpObjective(obj_name,
                                   constant=obj_coeff,
                                   sense=Maximize if is_maximize else Minimize)

        for obj_var_name, obj_coeff in obj_expr.items():
            obj_var = cls._find_variable(lp_variables, obj_var_name)
            lp_objective.expr[obj_var] = obj_coeff

        lp_constraints = []
        for constraint in constraints:
            lhs_expr = LpExpression()
            for var_name, cons_var_coeff in constraint['lhs'].items():
                var = cls._find_variable(lp_variables, var_name)
                lhs_expr.expr[var] = cons_var_coeff
            lhs_expr.const = constraint['lhs_const']

            rhs_expr = LpExpression()
            for var_name, cons_var_coeff in constraint['rhs'].items():
                var = cls._find_variable(lp_variables, var_name)
                rhs_expr.expr[var] = cons_var_coeff
            rhs_expr.const = constraint['rhs_const']

            lp_constraints.append(
                LpConstraint(lhs_expr,
                             constraint['sense'],
                             rhs_expr,
                             name=constraint['name']))

        cls._parse_variables(lp_variables, bounds, generals, binaries)

        return LpProblem(problem_name,
                         lp_objective=lp_objective,
                         lp_constraints=lp_constraints)
コード例 #21
0
ファイル: test_lp_constraint.py プロジェクト: nandi6uc/flipy
def rhs(x, y):
    # x + 5y + 2
    return LpExpression('rhs', {x: 1, y: 5}, 2)