Coverage for gpkit\constraints\relax.py: 0%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

120 statements  

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, initsolwarning, 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 initsolwarning(result, "Relaxed Constraints") 

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

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

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

57 "Relaxed Constraints") 

58 

59 

60class ConstraintsRelaxed(ConstraintSet): 

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

62 

63 Arguments 

64 --------- 

65 constraints : iterable 

66 Constraints which will be relaxed (made easier). 

67 

68 Attributes 

69 ---------- 

70 relaxvars : Variable 

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

72 relaxation was necessary or optimal for a particular constraint. 

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

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

75 in the final solution than in the original problem. 

76 

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

78 

79 """ 

80 

81 def __init__(self, original_constraints): 

82 if not isinstance(original_constraints, ConstraintSet): 

83 original_constraints = ConstraintSet(original_constraints) 

84 self.original_constraints = original_constraints 

85 original_substitutions = original_constraints.substitutions 

86 with NamedVariables("Relax"): 

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

88 

89 with SignomialsEnabled(): 

90 relaxed_constraints = [ 

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

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

93 

94 ConstraintSet.__init__(self, { 

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

96 "relaxed constraints": relaxed_constraints}, original_substitutions) 

97 

98 def process_result(self, result): 

99 "Warns if any constraints were relaxed" 

100 super().process_result(result) 

101 self.check_relaxed(result) 

102 

103 def check_relaxed(self, result): 

104 "Adds relaxation warnings to the result" 

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

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

107 initsolwarning(result, "Relaxed Constraints") 

108 for relaxval, i in relaxed: 

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

110 oldconstraint = self.original_constraints[i] 

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

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

113 relaxdleft = newconstraint.left.sub(subs) 

114 relaxdright = newconstraint.right.sub(subs) 

115 oldleftstr = str(oldconstraint.left) 

116 relaxedleftstr = str(relaxdleft) 

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

118 if padding > 0: 

119 oldleftstr = " " * padding + oldleftstr 

120 elif padding < 0: 

121 relaxedleftstr = " " * padding + relaxedleftstr 

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

123 " to %s %s %s" 

124 % (i, relax_percent, oldleftstr, 

125 oldconstraint.oper, oldconstraint.right, 

126 relaxedleftstr, newconstraint.oper, relaxdright)) 

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

128 

129 

130class ConstantsRelaxed(ConstraintSet): 

131 """Relax constants in a constraintset. 

132 

133 Arguments 

134 --------- 

135 constraints : iterable 

136 Constraints which will be relaxed (made easier). 

137 

138 include_only : set (optional) 

139 variable names must be in this set to be relaxed 

140 

141 exclude : set (optional) 

142 variable names in this set will never be relaxed 

143 

144 

145 Attributes 

146 ---------- 

147 relaxvars : Variable 

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

149 relaxation was necessary or optimal for a particular constant. 

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

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

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

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

154 """ 

155 # pylint:disable=too-many-locals 

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

157 exclude = frozenset(exclude) if exclude else frozenset() 

158 include_only = frozenset(include_only) if include_only else frozenset() 

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

160 pass # gives this model the correct lineage. 

161 

162 if not isinstance(constraints, ConstraintSet): 

163 constraints = ConstraintSet(constraints) 

164 substitutions = KeyDict(constraints.substitutions) 

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

166 if linked: 

167 kdc = KeyDict(constants) 

168 constrained_varkeys = constraints.constrained_varkeys() 

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

170 if k in constrained_varkeys}) 

171 

172 self._derelax_map = {} 

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

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

175 if val == 0: 

176 substitutions[const] = 0 

177 continue 

178 if include_only and const.name not in include_only: 

179 continue 

180 if const.name in exclude: 

181 continue 

182 # set up the lineage 

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

184 newconstd = const.descr.copy() 

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

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

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

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

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

190 relaxedd = newconstd.copy() 

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

192 relaxvar = Variable(**relaxedd) 

193 relaxvars.append(relaxvar) 

194 # the newly freed const can acquire a new value 

195 del substitutions[const] 

196 freed = Variable(**const.descr) 

197 self.freedvars.append(freed) 

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

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

200 newconst = Variable(**newconstd) 

201 substitutions[newconst] = val 

202 self._derelax_map[newconst.key] = const 

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

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

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

206 newconst/relaxvar <= freed, 

207 freed <= newconst*relaxvar] 

208 ConstraintSet.__init__(self, { 

209 "original constraints": constraints, 

210 "relaxation constraints": relaxation_constraints}) 

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

212 self.substitutions = substitutions 

213 self.constants = constants 

214 

215 def process_result(self, result): 

216 "Transfers constant sensitivities back to the original constants" 

217 super().process_result(result) 

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

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

220 constant_senss[former_constant] = constant_senss[new_constant] 

221 del constant_senss[new_constant] 

222 self.check_relaxed(result) 

223 

224 

225 def check_relaxed(self, result): 

226 "Adds relaxation warnings to the result" 

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

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

229 initsolwarning(result, "Relaxed Constants") 

230 for (_, freed) in relaxed: 

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

232 % (freed, 

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

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

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

236 

237 

238def get_relaxed(relaxvals, mapped_list): 

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

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

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

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

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

244 return sortrelaxed[:i] 

245 return sortrelaxed