Source code for picos.reforms.reform_epigraph
# ------------------------------------------------------------------------------
# Copyright (C) 2019 Maximilian Stahlberg
#
# This file is part of PICOS.
#
# PICOS is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------
"""Implementation of :class:`EpigraphReformulation`."""
import operator
from ..apidoc import api_end, api_start
from ..expressions import AffineExpression, PredictedFailure, RealVariable
from .reformulation import Reformulation
_API_START = api_start(globals())
# -------------------------------
[docs]class EpigraphReformulation(Reformulation):
"""Epigraph reformulation."""
NEW_OBJECTIVE = AffineExpression.make_type(
shape=(1, 1), constant=False, nonneg=False)
[docs] @classmethod
def supports(cls, footprint):
"""Implement :meth:`~.reformulation.Reformulation.supports`."""
if footprint.objective.clstype is AffineExpression:
return False
dir, obj = footprint.direction, footprint.objective
relation = operator.__ge__ if dir == "max" else operator.__le__
try:
obj.predict(relation, cls.NEW_OBJECTIVE)
except PredictedFailure:
return False
return True
[docs] @classmethod
def predict(cls, footprint):
"""Implement :meth:`~.reformulation.Reformulation.predict`."""
dir, obj = footprint.direction, footprint.objective
relation = operator.__ge__ if dir == "max" else operator.__le__
constraint = obj.predict(relation, cls.NEW_OBJECTIVE)
return footprint.updated((
("obj", footprint.NONE),
("obj", cls.NEW_OBJECTIVE, None),
("var", RealVariable.make_var_type(dim=1, bnd=0), 1),
("con", constraint, 1)))
[docs] def forward(self):
"""Implement :meth:`~.reformulation.Reformulation.forward`."""
self.output = self.input.clone(copyOptions=False)
direction, objective = self.output.objective
self.t = self.output.add_variable("__t")
self.C = self.output.add_constraint(
objective <= self.t if direction == "min" else objective >= self.t)
self.output.set_objective(direction, self.t)
[docs] def update(self):
"""Implement :meth:`~.reformulation.Reformulation.update`."""
if self._objective_has_changed():
# Remove the old auxilary constraint.
self.output.remove_constraint(self.C.id)
# Add a new one, using the existing variable.
newDir, newObj = self.input.objective
self.C = self.output.add_constraint(
newObj <= self.t if newDir == "min" else newObj >= self.t)
self._pass_updated_vars()
self._pass_updated_cons()
self._pass_updated_options()
[docs] def backward(self, solution):
"""Implement :meth:`~.reformulation.Reformulation.backward`."""
if self.t in solution.primals:
solution.primals.pop(self.t)
if self.C in solution.duals:
solution.duals.pop(self.C)
return solution
# --------------------------------------
__all__ = api_end(_API_START, globals())