Coverage for picos/reforms/reform_epigraph.py: 89.13%

46 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-26 07:46 +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# ------------------------------------------------------------------------------ 

18 

19"""Implementation of :class:`EpigraphReformulation`.""" 

20 

21import operator 

22 

23from ..apidoc import api_end, api_start 

24from ..expressions import AffineExpression, PredictedFailure, RealVariable 

25from .reformulation import Reformulation 

26 

27_API_START = api_start(globals()) 

28# ------------------------------- 

29 

30 

31class EpigraphReformulation(Reformulation): 

32 """Epigraph reformulation.""" 

33 

34 NEW_OBJECTIVE = AffineExpression.make_type( 

35 shape=(1, 1), constant=False, nonneg=False) 

36 

37 @classmethod 

38 def supports(cls, footprint): 

39 """Implement :meth:`~.reformulation.Reformulation.supports`.""" 

40 if footprint.objective.clstype is AffineExpression: 

41 return False 

42 

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 

49 

50 return True 

51 

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) 

58 

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))) 

64 

65 def forward(self): 

66 """Implement :meth:`~.reformulation.Reformulation.forward`.""" 

67 self.output = self.input.clone(copyOptions=False) 

68 

69 direction, objective = self.output.objective 

70 

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) 

75 

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) 

81 

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) 

86 

87 self._pass_updated_vars() 

88 self._pass_updated_cons() 

89 self._pass_updated_options() 

90 

91 def backward(self, solution): 

92 """Implement :meth:`~.reformulation.Reformulation.backward`.""" 

93 if self.t in solution.primals: 

94 solution.primals.pop(self.t) 

95 

96 if self.C in solution.duals: 

97 solution.duals.pop(self.C) 

98 

99 return solution 

100 

101 

102# -------------------------------------- 

103__all__ = api_end(_API_START, globals())