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"Implements Bounded"
2from collections import defaultdict
3import numpy as np
4from .. import Variable
5from .set import ConstraintSet
6from ..small_scripts import appendsolwarning
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 for i, varkey in enumerate(self.bound_varkeys):
81 value = result["variables"][varkey]
82 c_senss = [result["sensitivities"]["constraints"].get(c, 0)
83 for c in self["variable bounds"][i]]
84 if self.lowerbound:
85 bound = "lower bound of %.2g" % self.lowerbound
86 if c_senss[0] >= self.sens_threshold:
87 out["sensitive to " + bound].add(varkey)
88 if np.log(value/self.lowerbound) <= self.logtol_threshold:
89 out["value near " + bound].add(varkey)
90 if self.upperbound:
91 bound = "upper bound of %.2g" % self.upperbound
92 if c_senss[-1] >= self.sens_threshold:
93 out["sensitive to " + bound].add(varkey)
94 if np.log(self.upperbound/value) <= self.logtol_threshold:
95 out["value near " + bound].add(varkey)
96 for bound, vks in out.items():
97 msg = "% 34s: %s" % (bound, ", ".join([str(v) for v in vks]))
98 appendsolwarning(msg, out, result,
99 "Arbitrarily Bounded Variables")
100 return out