Coverage for picos/reforms/reform_epigraph.py: 89.13%
46 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +0000
1# ------------------------------------------------------------------------------
2# Copyright (C) 2019 Maximilian Stahlberg
3#
4# This file is part of PICOS.
5#
6# PICOS is free software: you can redistribute it and/or modify it under the
7# terms of the GNU General Public License as published by the Free Software
8# Foundation, either version 3 of the License, or (at your option) any later
9# version.
10#
11# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY
12# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# this program. If not, see <http://www.gnu.org/licenses/>.
17# ------------------------------------------------------------------------------
19"""Implementation of :class:`EpigraphReformulation`."""
21import operator
23from ..apidoc import api_end, api_start
24from ..expressions import AffineExpression, PredictedFailure, RealVariable
25from .reformulation import Reformulation
27_API_START = api_start(globals())
28# -------------------------------
31class EpigraphReformulation(Reformulation):
32 """Epigraph reformulation."""
34 NEW_OBJECTIVE = AffineExpression.make_type(
35 shape=(1, 1), constant=False, nonneg=False)
37 @classmethod
38 def supports(cls, footprint):
39 """Implement :meth:`~.reformulation.Reformulation.supports`."""
40 if footprint.objective.clstype is AffineExpression:
41 return False
43 dir, obj = footprint.direction, footprint.objective
44 relation = operator.__ge__ if dir == "max" else operator.__le__
45 try:
46 obj.predict(relation, cls.NEW_OBJECTIVE)
47 except PredictedFailure:
48 return False
50 return True
52 @classmethod
53 def predict(cls, footprint):
54 """Implement :meth:`~.reformulation.Reformulation.predict`."""
55 dir, obj = footprint.direction, footprint.objective
56 relation = operator.__ge__ if dir == "max" else operator.__le__
57 constraint = obj.predict(relation, cls.NEW_OBJECTIVE)
59 return footprint.updated((
60 ("obj", footprint.NONE),
61 ("obj", cls.NEW_OBJECTIVE, None),
62 ("var", RealVariable.make_var_type(dim=1, bnd=0), 1),
63 ("con", constraint, 1)))
65 def forward(self):
66 """Implement :meth:`~.reformulation.Reformulation.forward`."""
67 self.output = self.input.clone(copyOptions=False)
69 direction, objective = self.output.objective
71 self.t = self.output.add_variable("__t")
72 self.C = self.output.add_constraint(
73 objective <= self.t if direction == "min" else objective >= self.t)
74 self.output.set_objective(direction, self.t)
76 def update(self):
77 """Implement :meth:`~.reformulation.Reformulation.update`."""
78 if self._objective_has_changed():
79 # Remove the old auxilary constraint.
80 self.output.remove_constraint(self.C.id)
82 # Add a new one, using the existing variable.
83 newDir, newObj = self.input.objective
84 self.C = self.output.add_constraint(
85 newObj <= self.t if newDir == "min" else newObj >= self.t)
87 self._pass_updated_vars()
88 self._pass_updated_cons()
89 self._pass_updated_options()
91 def backward(self, solution):
92 """Implement :meth:`~.reformulation.Reformulation.backward`."""
93 if self.t in solution.primals:
94 solution.primals.pop(self.t)
96 if self.C in solution.duals:
97 solution.duals.pop(self.C)
99 return solution
102# --------------------------------------
103__all__ = api_end(_API_START, globals())