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
8def varkey_bounds(varkeys, lower, upper):
9 """Returns constraints list bounding all varkeys.
11 Arguments
12 ---------
13 varkeys : iterable
14 list of varkeys to create bounds for
16 lower : float
17 lower bound for all varkeys
19 upper : float
20 upper bound for all varkeys
21 """
22 constraints = []
23 for varkey in varkeys:
24 variable = Variable(**varkey.descr)
25 if variable.units:
26 variable.hmap.units = None
27 variable.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 so as to ensure dual feasibility.
40 Arguments
41 ---------
42 constraints : iterable
43 constraints whose varkeys will be bounded
45 verbosity : int (default 1)
46 how detailed of a warning to print
47 0: nothing
48 1: print warnings
50 eps : float (default 1e-30)
51 default lower bound is eps, upper bound is 1/eps
53 lower : float (default None)
54 lower bound for all varkeys, replaces eps
56 upper : float (default None)
57 upper bound for all varkeys, replaces 1/eps
58 """
59 sens_threshold = 1e-7
60 logtol_threshold = 3
62 def __init__(self, constraints, *, verbosity=1,
63 eps=1e-30, lower=None, upper=None):
64 if not isinstance(constraints, ConstraintSet):
65 constraints = ConstraintSet(constraints)
66 self.verbosity = verbosity
67 self.lowerbound = lower if (lower or upper) else eps
68 self.upperbound = upper if (lower or upper) else 1/eps
69 constrained_varkeys = constraints.constrained_varkeys()
70 self.bound_varkeys = frozenset(vk for vk in constrained_varkeys
71 if vk not in constraints.substitutions)
72 bounding_constraints = varkey_bounds(self.bound_varkeys,
73 self.lowerbound, self.upperbound)
74 super().__init__({"original constraints": constraints,
75 "variable bounds": bounding_constraints})
77 def process_result(self, result):
78 "Add boundedness to the model's solution"
79 ConstraintSet.process_result(self, result)
80 if "boundedness" not in result:
81 result["boundedness"] = {}
82 result["boundedness"].update(
83 self.check_boundaries(result, verbosity=self.verbosity))
85 def check_boundaries(self, result, *, verbosity=0):
86 "Creates (and potentially prints) a dictionary of unbounded variables."
87 out = defaultdict(set)
88 for i, varkey in enumerate(self.bound_varkeys):
89 value = result["variables"][varkey]
90 c_senss = [result["sensitivities"]["constraints"].get(c, 0)
91 for c in self["variable bounds"][i]]
92 if self.lowerbound:
93 if c_senss[0] >= self.sens_threshold:
94 out["sensitive to lower bound"].add(varkey)
95 if np.log(value/self.lowerbound) <= self.logtol_threshold:
96 out["value near lower bound"].add(varkey)
97 if self.upperbound:
98 if c_senss[-1] >= self.sens_threshold:
99 out["sensitive to upper bound"].add(varkey)
100 if np.log(self.upperbound/value) <= self.logtol_threshold:
101 out["value near upper bound"].add(varkey)
102 if verbosity > 0 and out:
103 print("")
104 print("Solves with these variables bounded:")
105 for key, value in sorted(out.items()):
106 print("% 25s: %s" % (key, ", ".join([str(v) for v in value])))
107 print("")
108 return out