Coverage for gpkit\constraints\relax.py : 100%
![Show keyboard shortcuts](keybd_closed.png)
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"""Models for assessing primal feasibility"""
2from .set import ConstraintSet
3from ..nomials import Variable, VectorVariable, parse_subs, NomialArray
4from ..keydict import KeyDict
5from .. import NamedVariables, SignomialsEnabled
8class ConstraintsRelaxedEqually(ConstraintSet):
9 """Relax constraints the same amount, as in Eqn. 10 of [Boyd2007].
11 Arguments
12 ---------
13 constraints : iterable
14 Constraints which will be relaxed (made easier).
17 Attributes
18 ----------
19 relaxvar : Variable
20 The variable controlling the relaxation. A solved value of 1 means no
21 relaxation. Higher values indicate the amount by which all constraints
22 have been made easier: e.g., a value of 1.5 means all constraints were
23 50 percent easier in the final solution than in the original problem.
25 [Boyd2007] : "A tutorial on geometric programming", Optim Eng 8:67-122
27 """
29 def __init__(self, original_constraints):
30 if not isinstance(original_constraints, ConstraintSet):
31 original_constraints = ConstraintSet(original_constraints)
32 self.original_constraints = original_constraints
33 original_substitutions = original_constraints.substitutions
35 with NamedVariables("Relax"):
36 self.relaxvar = Variable("C")
37 with SignomialsEnabled():
38 relaxed_constraints = [c.relaxed(self.relaxvar)
39 for c in original_constraints.flat()]
41 ConstraintSet.__init__(self, {
42 "minimum relaxation": self.relaxvar >= 1,
43 "relaxed constraints": relaxed_constraints}, original_substitutions)
46class ConstraintsRelaxed(ConstraintSet):
47 """Relax constraints, as in Eqn. 11 of [Boyd2007].
49 Arguments
50 ---------
51 constraints : iterable
52 Constraints which will be relaxed (made easier).
54 Attributes
55 ----------
56 relaxvars : Variable
57 The variables controlling the relaxation. A solved value of 1 means no
58 relaxation was necessary or optimal for a particular constraint.
59 Higher values indicate the amount by which that constraint has been
60 made easier: e.g., a value of 1.5 means it was made 50 percent easier
61 in the final solution than in the original problem.
63 [Boyd2007] : "A tutorial on geometric programming", Optim Eng 8:67-122
65 """
67 def __init__(self, original_constraints):
68 if not isinstance(original_constraints, ConstraintSet):
69 original_constraints = ConstraintSet(original_constraints)
70 self.original_constraints = original_constraints
71 original_substitutions = original_constraints.substitutions
72 with NamedVariables("Relax"):
73 self.relaxvars = VectorVariable(len(original_constraints), "C")
75 with SignomialsEnabled():
76 relaxed_constraints = [
77 c.relaxed(self.relaxvars[i])
78 for i, c in enumerate(original_constraints.flat())]
80 ConstraintSet.__init__(self, {
81 "minimum relaxation": self.relaxvars >= 1,
82 "relaxed constraints": relaxed_constraints}, original_substitutions)
85class ConstantsRelaxed(ConstraintSet):
86 """Relax constants in a constraintset.
88 Arguments
89 ---------
90 constraints : iterable
91 Constraints which will be relaxed (made easier).
93 include_only : set (optional)
94 variable names must be in this set to be relaxed
96 exclude : set (optional)
97 variable names in this set will never be relaxed
100 Attributes
101 ----------
102 relaxvars : Variable
103 The variables controlling the relaxation. A solved value of 1 means no
104 relaxation was necessary or optimal for a particular constant.
105 Higher values indicate the amount by which that constant has been
106 made easier: e.g., a value of 1.5 means it was made 50 percent easier
107 in the final solution than in the original problem. Of course, this
108 can also be determined by looking at the constant's new value directly.
109 """
110 # pylint:disable=too-many-locals
111 def __init__(self, constraints, *, include_only=None, exclude=None):
112 exclude = frozenset(exclude) if exclude else frozenset()
113 include_only = frozenset(include_only) if include_only else frozenset()
114 with NamedVariables("Relax") as (self.lineage, _):
115 pass # gives this model the correct lineage.
117 if not isinstance(constraints, ConstraintSet):
118 constraints = ConstraintSet(constraints)
119 substitutions = KeyDict(constraints.substitutions)
120 constants, _, linked = parse_subs(constraints.varkeys, substitutions)
121 if linked:
122 kdc = KeyDict(constants)
123 constrained_varkeys = constraints.constrained_varkeys()
124 constants.update({k: f(kdc) for k, f in linked.items()
125 if k in constrained_varkeys})
127 self._derelax_map = {}
128 relaxvars, self.freedvars, relaxation_constraints = [], [], {}
129 for const, val in sorted(constants.items(), key=lambda i: i[0].eqstr):
130 if val == 0:
131 substitutions[const] = 0
132 continue
133 if include_only and const.name not in include_only:
134 continue
135 if const.name in exclude:
136 continue
137 # set up the lineage
138 const.descr.pop("gradients", None) # nothing wants an old gradient
139 newconstd = const.descr.copy()
140 newconstd.pop("veckey", None) # only const wants an old veckey
141 # everything but const wants a new lineage, to distinguish them
142 newconstd["lineage"] = (newconstd.pop("lineage", ())
143 + (self.lineage[-1],))
144 # make the relaxation variable, free to get as large as it needs
145 relaxedd = newconstd.copy()
146 relaxedd["unitrepr"] = "-" # and unitless, importantly
147 relaxvar = Variable(**relaxedd)
148 relaxvars.append(relaxvar)
149 # the newly freed const can acquire a new value
150 del substitutions[const]
151 freed = Variable(**const.descr)
152 self.freedvars.append(freed)
153 # becuase the make the newconst will take its old value
154 newconstd["lineage"] += (("OriginalValues", 0),)
155 newconst = Variable(**newconstd)
156 substitutions[newconst] = val
157 self._derelax_map[newconst.key] = const
158 # add constraints so the newly freed's wiggle room
159 # is proportional to the value relaxvar, and it can't antirelax
160 relaxation_constraints[str(const)] = [relaxvar >= 1,
161 newconst/relaxvar <= freed,
162 freed <= newconst*relaxvar]
163 ConstraintSet.__init__(self, {
164 "original constraints": constraints,
165 "relaxation constraints": relaxation_constraints})
166 self.relaxvars = NomialArray(relaxvars) # so they can be .prod()'d
167 self.substitutions = substitutions
168 self.constants = constants
170 def process_result(self, result):
171 "Transfers the constant sensitivities back to the original constants"
172 ConstraintSet.process_result(self, result)
173 constant_senss = result["sensitivities"]["variables"]
174 for new_constant, former_constant in self._derelax_map.items():
175 constant_senss[former_constant] = constant_senss[new_constant]
176 del constant_senss[new_constant]