Coverage for gpkit/constraints/bounded.py: 100%

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

57 statements  

1"Implements Bounded" 

2from collections import defaultdict 

3import numpy as np 

4from .. import Variable 

5from .set import ConstraintSet 

6from ..small_scripts import appendsolwarning, initsolwarning 

7 

8 

9def varkey_bounds(varkeys, lower, upper): 

10 """Returns constraints list bounding all varkeys. 

11 

12 Arguments 

13 --------- 

14 varkeys : iterable 

15 list of varkeys to create bounds for 

16 

17 lower : float 

18 lower bound for all varkeys 

19 

20 upper : float 

21 upper bound for all varkeys 

22 """ 

23 constraints = [] 

24 for varkey in varkeys: 

25 variable = Variable(**varkey.descr) 

26 if variable.units: # non-dimensionalize the variable monomial 

27 variable.units = variable.hmap.units = None 

28 constraint = [] 

29 if lower: 

30 constraint.append(lower <= variable) 

31 if upper: 

32 constraint.append(variable <= upper) 

33 constraints.append(constraint) 

34 return constraints 

35 

36 

37class Bounded(ConstraintSet): 

38 """Bounds contained variables, generally ensuring dual feasibility. 

39 

40 Arguments 

41 --------- 

42 constraints : iterable 

43 constraints whose varkeys will be bounded 

44 

45 eps : float (default 1e-30) 

46 default lower bound is eps, upper bound is 1/eps 

47 

48 lower : float (default None) 

49 lower bound for all varkeys, replaces eps 

50 

51 upper : float (default None) 

52 upper bound for all varkeys, replaces 1/eps 

53 """ 

54 sens_threshold = 1e-7 

55 logtol_threshold = 3 

56 

57 def __init__(self, constraints, *, eps=1e-30, lower=None, upper=None): 

58 if not isinstance(constraints, ConstraintSet): 

59 constraints = ConstraintSet(constraints) 

60 self.lowerbound = lower or eps 

61 self.upperbound = upper or 1/eps 

62 constrained_varkeys = constraints.constrained_varkeys() 

63 self.bound_varkeys = frozenset(vk for vk in constrained_varkeys 

64 if vk not in constraints.substitutions) 

65 bounding_constraints = varkey_bounds(self.bound_varkeys, 

66 self.lowerbound, self.upperbound) 

67 super().__init__({"original constraints": constraints, 

68 "variable bounds": bounding_constraints}) 

69 

70 def process_result(self, result): 

71 "Add boundedness to the model's solution" 

72 super().process_result(result) 

73 if "boundedness" not in result: 

74 result["boundedness"] = {} 

75 result["boundedness"].update(self.check_boundaries(result)) 

76 

77 def check_boundaries(self, result): 

78 "Creates (and potentially prints) a dictionary of unbounded variables." 

79 out = defaultdict(set) 

80 initsolwarning(result, "Arbitrarily Bounded Variables") 

81 for i, varkey in enumerate(self.bound_varkeys): 

82 value = result["variables"][varkey] 

83 c_senss = [result["sensitivities"]["constraints"].get(c, 0) 

84 for c in self["variable bounds"][i]] 

85 if self.lowerbound: 

86 bound = "lower bound of %.2g" % self.lowerbound 

87 if c_senss[0] >= self.sens_threshold: 

88 out["sensitive to " + bound].add(varkey) 

89 if np.log(value/self.lowerbound) <= self.logtol_threshold: 

90 out["value near " + bound].add(varkey) 

91 if self.upperbound: 

92 bound = "upper bound of %.2g" % self.upperbound 

93 if c_senss[-1] >= self.sens_threshold: 

94 out["sensitive to " + bound].add(varkey) 

95 if np.log(self.upperbound/value) <= self.logtol_threshold: 

96 out["value near " + bound].add(varkey) 

97 for bound, vks in out.items(): 

98 msg = "% 34s: %s" % (bound, ", ".join([str(v) for v in vks])) 

99 appendsolwarning(msg, out, result, 

100 "Arbitrarily Bounded Variables") 

101 return out