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"Scripts for generating, solving and sweeping programs" 

2from time import time 

3import warnings as pywarnings 

4import numpy as np 

5from ad import adnumber 

6from ..nomials import parse_subs 

7from ..solution_array import SolutionArray 

8from ..keydict import KeyDict 

9from ..small_scripts import maybe_flatten 

10from ..small_classes import FixedScalar 

11from ..exceptions import Infeasible 

12from ..globals import SignomialsEnabled 

13 

14 

15def evaluate_linked(constants, linked): 

16 "Evaluates the values and gradients of linked variables." 

17 kdc = KeyDict({k: adnumber(maybe_flatten(v), k) 

18 for k, v in constants.items()}) 

19 kdc_plain = None 

20 array_calulated = {} 

21 for key in constants: # remove gradients from constants 

22 if key.gradients: 

23 del key.descr["gradients"] 

24 for v, f in linked.items(): 

25 try: 

26 if v.veckey and v.veckey.vecfn: 

27 if v.veckey not in array_calulated: 

28 with SignomialsEnabled(): # to allow use of gpkit.units 

29 vecout = v.veckey.vecfn(kdc) 

30 if not hasattr(vecout, "shape"): 

31 vecout = np.array(vecout) 

32 array_calulated[v.veckey] = vecout 

33 out = array_calulated[v.veckey][v.idx] 

34 else: 

35 with SignomialsEnabled(): # to allow use of gpkit.units 

36 out = f(kdc) 

37 if isinstance(out, FixedScalar): # to allow use of gpkit.units 

38 out = out.value 

39 if hasattr(out, "units"): 

40 out = out.to(v.units or "dimensionless").magnitude 

41 elif out != 0 and v.units: 

42 pywarnings.warn( 

43 "Linked function for %s did not return a united value." 

44 " Modifying it to do so (e.g. by using `()` instead of `[]`" 

45 " to access variables) will reduce errors." % v) 

46 out = maybe_flatten(out) 

47 if not hasattr(out, "x"): 

48 constants[v] = out 

49 continue # a new fixed variable, not a calculated one 

50 constants[v] = out.x 

51 gradients = {adn.tag: 

52 grad for adn, grad in out.d().items() if adn.tag} 

53 if gradients: 

54 v.descr["gradients"] = gradients 

55 except Exception as exception: # pylint: disable=broad-except 

56 from .. import settings 

57 if settings.get("ad_errors_raise", None): 

58 raise 

59 if kdc_plain is None: 

60 kdc_plain = KeyDict(constants) 

61 constants[v] = f(kdc_plain) 

62 v.descr.pop("gradients", None) 

63 print("Warning: skipped auto-differentiation of linked variable" 

64 " %s because %s was raised. Set `gpkit.settings" 

65 "[\"ad_errors_raise\"] = True` to raise such Exceptions" 

66 " directly.\n" % (v, repr(exception))) 

67 if ("Automatic differentiation not yet supported for <class " 

68 "'gpkit.nomials.math.Monomial'> objects") in str(exception): 

69 print("This particular warning may have come from using" 

70 " gpkit.units.* in the function for %s; try using" 

71 " gpkit.ureg.* or gpkit.units.*.units instead." % v) 

72 

73 

74def progify(program, return_attr=None): 

75 """Generates function that returns a program() and optionally an attribute. 

76 

77 Arguments 

78 --------- 

79 program: NomialData 

80 Class to return, e.g. GeometricProgram or SequentialGeometricProgram 

81 return_attr: string 

82 attribute to return in addition to the program 

83 """ 

84 def programfn(self, constants=None, **initargs): 

85 "Return program version of self" 

86 if not constants: 

87 constants, _, linked = parse_subs(self.varkeys, self.substitutions) 

88 if linked: 

89 evaluate_linked(constants, linked) 

90 prog = program(self.cost, self, constants, **initargs) 

91 prog.model = self # NOTE SIDE EFFECTS 

92 if return_attr: 

93 return prog, getattr(prog, return_attr) 

94 return prog 

95 return programfn 

96 

97 

98def solvify(genfunction): 

99 "Returns function for making/solving/sweeping a program." 

100 def solvefn(self, solver=None, *, verbosity=1, skipsweepfailures=False, 

101 **kwargs): 

102 """Forms a mathematical program and attempts to solve it. 

103 

104 Arguments 

105 --------- 

106 solver : string or function (default None) 

107 If None, uses the default solver found in installation. 

108 verbosity : int (default 1) 

109 If greater than 0 prints runtime messages. 

110 Is decremented by one and then passed to programs. 

111 skipsweepfailures : bool (default False) 

112 If True, when a solve errors during a sweep, skip it. 

113 **kwargs : Passed to solve and program init calls 

114 

115 Returns 

116 ------- 

117 sol : SolutionArray 

118 See the SolutionArray documentation for details. 

119 

120 Raises 

121 ------ 

122 ValueError if the program is invalid. 

123 RuntimeWarning if an error occurs in solving or parsing the solution. 

124 """ 

125 constants, sweep, linked = parse_subs(self.varkeys, self.substitutions) 

126 solution = SolutionArray() 

127 solution.modelstr = str(self) 

128 

129 # NOTE SIDE EFFECTS: self.program and self.solution set below 

130 if sweep: 

131 run_sweep(genfunction, self, solution, skipsweepfailures, 

132 constants, sweep, linked, solver, verbosity, **kwargs) 

133 else: 

134 self.program, progsolve = genfunction(self, **kwargs) 

135 result = progsolve(solver, verbosity=verbosity, **kwargs) 

136 if kwargs.get("process_result", True): 

137 self.process_result(result) 

138 solution.append(result) 

139 solution.to_arrays() 

140 self.solution = solution 

141 solution.costposy = self.cost 

142 solution.vks = self.vks 

143 return solution 

144 return solvefn 

145 

146 

147# pylint: disable=too-many-locals,too-many-arguments,too-many-branches 

148def run_sweep(genfunction, self, solution, skipsweepfailures, 

149 constants, sweep, linked, solver, verbosity, **kwargs): 

150 "Runs through a sweep." 

151 # sort sweeps by the eqstr of their varkey 

152 sweepvars, sweepvals = zip(*sorted(list(sweep.items()), 

153 key=lambda vkval: vkval[0].eqstr)) 

154 if len(sweep) == 1: 

155 sweep_grids = np.array(list(sweepvals)) 

156 else: 

157 sweep_grids = np.meshgrid(*list(sweepvals)) 

158 

159 N_passes = sweep_grids[0].size 

160 sweep_vects = {var: grid.reshape(N_passes) 

161 for (var, grid) in zip(sweepvars, sweep_grids)} 

162 

163 if verbosity > 0: 

164 print("Sweeping with %i solves:" % N_passes) 

165 tic = time() 

166 

167 self.program = [] 

168 last_error = None 

169 for i in range(N_passes): 

170 constants.update({var: sweep_vect[i] 

171 for (var, sweep_vect) in sweep_vects.items()}) 

172 if linked: 

173 evaluate_linked(constants, linked) 

174 program, solvefn = genfunction(self, constants, **kwargs) 

175 self.program.append(program) # NOTE: SIDE EFFECTS 

176 try: 

177 if verbosity > 1: 

178 print("\nSolve %i:" % i) 

179 result = solvefn(solver, verbosity=verbosity-1, **kwargs) 

180 if kwargs.get("process_result", True): 

181 self.process_result(result) 

182 solution.append(result) 

183 except Infeasible as e: 

184 last_error = e 

185 if not skipsweepfailures: 

186 raise RuntimeWarning( 

187 "Solve %i was infeasible; progress saved to m.program." 

188 " To continue sweeping after failures, solve with" 

189 " skipsweepfailures=True." % i) from e 

190 if verbosity > 0: 

191 print("Solve %i was %s." % (i, e.__class__.__name__)) 

192 if not solution: 

193 raise RuntimeWarning("All solves were infeasible.") from last_error 

194 

195 solution["sweepvariables"] = KeyDict() 

196 ksweep = KeyDict(sweep) 

197 for var, val in list(solution["constants"].items()): 

198 if var in ksweep: 

199 solution["sweepvariables"][var] = val 

200 del solution["constants"][var] 

201 elif linked: # if any variables are linked, we check all of them 

202 if hasattr(val[0], "shape"): 

203 differences = ((l != val[0]).any() for l in val[1:]) 

204 else: 

205 differences = (l != val[0] for l in val[1:]) 

206 if not any(differences): 

207 solution["constants"][var] = [val[0]] 

208 else: 

209 solution["constants"][var] = [val[0]] 

210 

211 if verbosity > 0: 

212 soltime = time() - tic 

213 print("Sweeping took %.3g seconds." % (soltime,))