Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""Models for assessing primal feasibility""" 

2from .set import ConstraintSet 

3from ..nomials import Variable, VectorVariable, parse_subs, NomialArray 

4from ..keydict import KeyDict 

5from .. import NamedVariables, SignomialsEnabled 

6from ..small_scripts import appendsolwarning, mag 

7 

8 

9class ConstraintsRelaxedEqually(ConstraintSet): 

10 """Relax constraints the same amount, as in Eqn. 10 of [Boyd2007]. 

11 

12 Arguments 

13 --------- 

14 constraints : iterable 

15 Constraints which will be relaxed (made easier). 

16 

17 

18 Attributes 

19 ---------- 

20 relaxvar : Variable 

21 The variable controlling the relaxation. A solved value of 1 means no 

22 relaxation. Higher values indicate the amount by which all constraints 

23 have been made easier: e.g., a value of 1.5 means all constraints were 

24 50 percent easier in the final solution than in the original problem. 

25 

26 [Boyd2007] : "A tutorial on geometric programming", Optim Eng 8:67-122 

27 

28 """ 

29 

30 def __init__(self, original_constraints): 

31 if not isinstance(original_constraints, ConstraintSet): 

32 original_constraints = ConstraintSet(original_constraints) 

33 self.original_constraints = original_constraints 

34 original_substitutions = original_constraints.substitutions 

35 

36 with NamedVariables("Relax"): 

37 self.relaxvar = Variable("C") 

38 with SignomialsEnabled(): 

39 relaxed_constraints = [c.relaxed(self.relaxvar) 

40 for c in original_constraints.flat()] 

41 

42 ConstraintSet.__init__(self, { 

43 "minimum relaxation": self.relaxvar >= 1, 

44 "relaxed constraints": relaxed_constraints}, original_substitutions) 

45 

46 def process_result(self, result): 

47 "Warns if any constraints were relaxed" 

48 super().process_result(result) 

49 self.check_relaxed(result) 

50 

51 def check_relaxed(self, result): 

52 "Adds relaxation warnings to the result" 

53 for val, msg in get_relaxed([result["freevariables"][self.relaxvar]], 

54 ["All constraints relaxed by %i%%"]): 

55 appendsolwarning(msg % (0.9+(val-1)*100), self, result, 

56 "Relaxed Constraints") 

57 

58 

59class ConstraintsRelaxed(ConstraintSet): 

60 """Relax constraints, as in Eqn. 11 of [Boyd2007]. 

61 

62 Arguments 

63 --------- 

64 constraints : iterable 

65 Constraints which will be relaxed (made easier). 

66 

67 Attributes 

68 ---------- 

69 relaxvars : Variable 

70 The variables controlling the relaxation. A solved value of 1 means no 

71 relaxation was necessary or optimal for a particular constraint. 

72 Higher values indicate the amount by which that constraint has been 

73 made easier: e.g., a value of 1.5 means it was made 50 percent easier 

74 in the final solution than in the original problem. 

75 

76 [Boyd2007] : "A tutorial on geometric programming", Optim Eng 8:67-122 

77 

78 """ 

79 

80 def __init__(self, original_constraints): 

81 if not isinstance(original_constraints, ConstraintSet): 

82 original_constraints = ConstraintSet(original_constraints) 

83 self.original_constraints = original_constraints 

84 original_substitutions = original_constraints.substitutions 

85 with NamedVariables("Relax"): 

86 self.relaxvars = VectorVariable(len(original_constraints), "C") 

87 

88 with SignomialsEnabled(): 

89 relaxed_constraints = [ 

90 c.relaxed(self.relaxvars[i]) 

91 for i, c in enumerate(original_constraints.flat())] 

92 

93 ConstraintSet.__init__(self, { 

94 "minimum relaxation": self.relaxvars >= 1, 

95 "relaxed constraints": relaxed_constraints}, original_substitutions) 

96 

97 def process_result(self, result): 

98 "Warns if any constraints were relaxed" 

99 super().process_result(result) 

100 self.check_relaxed(result) 

101 

102 def check_relaxed(self, result): 

103 "Adds relaxation warnings to the result" 

104 relaxed = get_relaxed(result["freevariables"][self.relaxvars], 

105 range(len(self["relaxed constraints"]))) 

106 for relaxval, i in relaxed: 

107 relax_percent = "%i%%" % (0.5+(relaxval-1)*100) 

108 oldconstraint = self.original_constraints[i] 

109 newconstraint = self["relaxed constraints"][i][0] 

110 subs = {self.relaxvars[i]: relaxval} 

111 relaxdleft = newconstraint.left.sub(subs) 

112 relaxdright = newconstraint.right.sub(subs) 

113 oldleftstr = str(oldconstraint.left) 

114 relaxedleftstr = str(relaxdleft) 

115 padding = len(relaxedleftstr) - len(oldleftstr) 

116 if padding > 0: 

117 oldleftstr = " " * padding + oldleftstr 

118 elif padding < 0: 

119 relaxedleftstr = " " * padding + relaxedleftstr 

120 msg = (" %3i: %5s relaxed, from %s %s %s\n" 

121 " to %s %s %s" 

122 % (i, relax_percent, oldleftstr, 

123 oldconstraint.oper, oldconstraint.right, 

124 relaxedleftstr, newconstraint.oper, relaxdright)) 

125 appendsolwarning(msg, oldconstraint, result, "Relaxed Constraints") 

126 

127 

128class ConstantsRelaxed(ConstraintSet): 

129 """Relax constants in a constraintset. 

130 

131 Arguments 

132 --------- 

133 constraints : iterable 

134 Constraints which will be relaxed (made easier). 

135 

136 include_only : set (optional) 

137 variable names must be in this set to be relaxed 

138 

139 exclude : set (optional) 

140 variable names in this set will never be relaxed 

141 

142 

143 Attributes 

144 ---------- 

145 relaxvars : Variable 

146 The variables controlling the relaxation. A solved value of 1 means no 

147 relaxation was necessary or optimal for a particular constant. 

148 Higher values indicate the amount by which that constant has been 

149 made easier: e.g., a value of 1.5 means it was made 50 percent easier 

150 in the final solution than in the original problem. Of course, this 

151 can also be determined by looking at the constant's new value directly. 

152 """ 

153 # pylint:disable=too-many-locals 

154 def __init__(self, constraints, *, include_only=None, exclude=None): 

155 exclude = frozenset(exclude) if exclude else frozenset() 

156 include_only = frozenset(include_only) if include_only else frozenset() 

157 with NamedVariables("Relax") as (self.lineage, _): 

158 pass # gives this model the correct lineage. 

159 

160 if not isinstance(constraints, ConstraintSet): 

161 constraints = ConstraintSet(constraints) 

162 substitutions = KeyDict(constraints.substitutions) 

163 constants, _, linked = parse_subs(constraints.vks, substitutions) 

164 if linked: 

165 kdc = KeyDict(constants) 

166 constrained_varkeys = constraints.constrained_varkeys() 

167 constants.update({k: f(kdc) for k, f in linked.items() 

168 if k in constrained_varkeys}) 

169 

170 self._derelax_map = {} 

171 relaxvars, self.freedvars, relaxation_constraints = [], [], {} 

172 for const, val in sorted(constants.items(), key=lambda i: i[0].eqstr): 

173 if val == 0: 

174 substitutions[const] = 0 

175 continue 

176 if include_only and const.name not in include_only: 

177 continue 

178 if const.name in exclude: 

179 continue 

180 # set up the lineage 

181 const.descr.pop("gradients", None) # nothing wants an old gradient 

182 newconstd = const.descr.copy() 

183 newconstd.pop("veckey", None) # only const wants an old veckey 

184 # everything but const wants a new lineage, to distinguish them 

185 newconstd["lineage"] = (newconstd.pop("lineage", ()) 

186 + (self.lineage[-1],)) 

187 # make the relaxation variable, free to get as large as it needs 

188 relaxedd = newconstd.copy() 

189 relaxedd["unitrepr"] = "-" # and unitless, importantly 

190 relaxvar = Variable(**relaxedd) 

191 relaxvars.append(relaxvar) 

192 # the newly freed const can acquire a new value 

193 del substitutions[const] 

194 freed = Variable(**const.descr) 

195 self.freedvars.append(freed) 

196 # becuase the make the newconst will take its old value 

197 newconstd["lineage"] += (("OriginalValues", 0),) 

198 newconst = Variable(**newconstd) 

199 substitutions[newconst] = val 

200 self._derelax_map[newconst.key] = const 

201 # add constraints so the newly freed's wiggle room 

202 # is proportional to the value relaxvar, and it can't antirelax 

203 relaxation_constraints[str(const)] = [relaxvar >= 1, 

204 newconst/relaxvar <= freed, 

205 freed <= newconst*relaxvar] 

206 ConstraintSet.__init__(self, { 

207 "original constraints": constraints, 

208 "relaxation constraints": relaxation_constraints}) 

209 self.relaxvars = NomialArray(relaxvars) # so they can be .prod()'d 

210 self.substitutions = substitutions 

211 self.constants = constants 

212 

213 def process_result(self, result): 

214 "Transfers constant sensitivities back to the original constants" 

215 super().process_result(result) 

216 constant_senss = result["sensitivities"]["variables"] 

217 for new_constant, former_constant in self._derelax_map.items(): 

218 constant_senss[former_constant] = constant_senss[new_constant] 

219 del constant_senss[new_constant] 

220 self.check_relaxed(result) 

221 

222 

223 def check_relaxed(self, result): 

224 "Adds relaxation warnings to the result" 

225 relaxed = get_relaxed([result["freevariables"][r] 

226 for r in self.relaxvars], self.freedvars) 

227 for (_, freed) in relaxed: 

228 msg = (" %s: relaxed from %-.4g to %-.4g" 

229 % (freed, 

230 mag(self.constants[freed.key]), 

231 mag(result["freevariables"][freed]))) 

232 appendsolwarning(msg, freed, result, "Relaxed Constants") 

233 

234 

235def get_relaxed(relaxvals, mapped_list): 

236 "Returns 'relaxed' vals (those above an arbitrary threshold of 1.01)." 

237 sortrelaxed = sorted(zip(relaxvals, mapped_list), key=lambda x: -x[0]) 

238 mostrelaxed = max(sortrelaxed[0][0], 1.01) 

239 for i, (val, _) in enumerate(sortrelaxed): 

240 if val <= 1.01 and (val-1) <= (mostrelaxed-1)/10: 

241 return sortrelaxed[:i] 

242 return sortrelaxed