Coverage for gpkit/constraints/bounded.py: 100%
57 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-28 11:39 -0400
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-28 11:39 -0400
1"Implements Bounded"
2from collections import defaultdict
3import numpy as np
4from .. import Variable
5from .set import ConstraintSet
6from ..small_scripts import appendsolwarning, initsolwarning
9def varkey_bounds(varkeys, lower, upper):
10 """Returns constraints list bounding all varkeys.
12 Arguments
13 ---------
14 varkeys : iterable
15 list of varkeys to create bounds for
17 lower : float
18 lower bound for all varkeys
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
37class Bounded(ConstraintSet):
38 """Bounds contained variables, generally ensuring dual feasibility.
40 Arguments
41 ---------
42 constraints : iterable
43 constraints whose varkeys will be bounded
45 eps : float (default 1e-30)
46 default lower bound is eps, upper bound is 1/eps
48 lower : float (default None)
49 lower bound for all varkeys, replaces eps
51 upper : float (default None)
52 upper bound for all varkeys, replaces 1/eps
53 """
54 sens_threshold = 1e-7
55 logtol_threshold = 3
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})
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))
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