Skip to content

Commit

Permalink
Merge ae1b6c3 into 65827cc
Browse files Browse the repository at this point in the history
  • Loading branch information
bqpd committed Mar 10, 2021
2 parents 65827cc + ae1b6c3 commit 02d3f26
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 22 deletions.
3 changes: 2 additions & 1 deletion gpkit/constraints/bounded.py
Expand Up @@ -3,7 +3,7 @@
import numpy as np
from .. import Variable
from .set import ConstraintSet
from ..small_scripts import appendsolwarning
from ..small_scripts import appendsolwarning, initsolwarning


def varkey_bounds(varkeys, lower, upper):
Expand Down Expand Up @@ -77,6 +77,7 @@ def process_result(self, result):
def check_boundaries(self, result):
"Creates (and potentially prints) a dictionary of unbounded variables."
out = defaultdict(set)
initsolwarning(result, "Arbitrarily Bounded Variables")
for i, varkey in enumerate(self.bound_varkeys):
value = result["variables"][varkey]
c_senss = [result["sensitivities"]["constraints"].get(c, 0)
Expand Down
11 changes: 6 additions & 5 deletions gpkit/constraints/gp.py
Expand Up @@ -222,15 +222,17 @@ def solve(self, solver=None, *, verbosity=1, gen_result=True, **kwargs):
msg = ("The model ran to an infinitely low cost;"
" bounding the right variables would prevent this.")
elif isinstance(infeasibility, UnknownInfeasible):
msg = "The solver failed for an unknown reason."
msg = ("Solver failed for an unknown reason. Relaxing"
" constraints/constants, bounding variables, or"
" using a different solver might fix it.")
if (verbosity > 0 and solver_out["soltime"] < 1
and hasattr(self, "model")): # fast, top-level model
print(msg + "\nSince the model solved in less than a second,"
" let's run `.debug()` to analyze what happened.\n`")
return self.model.debug(solver=solver)
# else, raise a clarifying error
msg += (" Running `.debug()` may pinpoint the trouble. You can"
" also try another solver, or increase the verbosity.")
msg += (" Running `.debug()` or increasing verbosity may pinpoint"
" the trouble.")
raise infeasibility.__class__(msg) from infeasibility

if not gen_result:
Expand Down Expand Up @@ -330,8 +332,7 @@ def _compile_result(self, solver_out):
with warnings.catch_warnings(): # skip pesky divide-by-zeros
warnings.simplefilter("ignore")
dlogv_dlogc = dv_dc * result["constants"][c]/val
before = gpv_ss.get(c, 0)
gpv_ss[c] = before + dlogcost_dlogv*dlogv_dlogc
gpv_ss[c] = gpv_ss.get(c, 0) + dlogcost_dlogv*dlogv_dlogc
if v in cost_senss:
if c in self.cost.vks:
dlogcost_dlogv = cost_senss.pop(v)
Expand Down
3 changes: 2 additions & 1 deletion gpkit/constraints/loose.py
@@ -1,6 +1,6 @@
"Implements Loose"
from .set import ConstraintSet
from ..small_scripts import appendsolwarning
from ..small_scripts import appendsolwarning, initsolwarning


class Loose(ConstraintSet):
Expand All @@ -15,6 +15,7 @@ def __init__(self, constraints, *, senstol=None):
def process_result(self, result):
"Checks that all constraints are satisfied with equality"
super().process_result(result)
initsolwarning(result, "Unexpectedly Tight Constraints")
for constraint in self.flat():
c_senss = result["sensitivities"]["constraints"].get(constraint, 0)
if c_senss >= self.senstol:
Expand Down
16 changes: 12 additions & 4 deletions gpkit/constraints/prog_factories.py
Expand Up @@ -15,6 +15,9 @@ def evaluate_linked(constants, linked):
for k, v in constants.items()})
kdc_plain = None
array_calulated = {}
for key in constants: # remove gradients from constants
if key.gradients:
del key.descr["gradients"]
for v, f in linked.items():
try:
if v.veckey and v.veckey.original_fn:
Expand Down Expand Up @@ -130,7 +133,7 @@ def run_sweep(genfunction, self, solution, skipsweepfailures,
for (var, grid) in zip(sweepvars, sweep_grids)}

if verbosity > 0:
print("Sweeping over %i solves." % N_passes)
print("Sweeping with %i solves:" % N_passes)
tic = time()

self.program = []
Expand All @@ -143,6 +146,8 @@ def run_sweep(genfunction, self, solution, skipsweepfailures,
program, solvefn = genfunction(self, constants)
self.program.append(program) # NOTE: SIDE EFFECTS
try:
if verbosity > 1:
print("\nSolve %i:" % i)
result = solvefn(solver, verbosity=verbosity-1, **solveargs)
if solveargs.get("process_result", True):
self.process_result(result)
Expand All @@ -151,10 +156,13 @@ def run_sweep(genfunction, self, solution, skipsweepfailures,
last_error = e
if not skipsweepfailures:
raise RuntimeWarning(
"Sweep halted! Progress saved to m.program. To skip over"
" such failures, solve with skipsweepfailures=True.") from e
"Solve %i was infeasible; progress saved to m.program."
" To continue sweeping after failures, solve with"
" skipsweepfailures=True." % i) from e
if verbosity > 0:
print("Solve %i was %s." % (i, e.__class__.__name__))
if not solution:
raise RuntimeWarning("No sweeps solved successfully.") from last_error
raise RuntimeWarning("All solves were infeasible.") from last_error

solution["sweepvariables"] = KeyDict()
ksweep = KeyDict(sweep)
Expand Down
5 changes: 4 additions & 1 deletion gpkit/constraints/relax.py
Expand Up @@ -3,7 +3,7 @@
from ..nomials import Variable, VectorVariable, parse_subs, NomialArray
from ..keydict import KeyDict
from .. import NamedVariables, SignomialsEnabled
from ..small_scripts import appendsolwarning, mag
from ..small_scripts import appendsolwarning, initsolwarning, mag


class ConstraintsRelaxedEqually(ConstraintSet):
Expand Down Expand Up @@ -50,6 +50,7 @@ def process_result(self, result):

def check_relaxed(self, result):
"Adds relaxation warnings to the result"
initsolwarning(result, "Relaxed Constraints")
for val, msg in get_relaxed([result["freevariables"][self.relaxvar]],
["All constraints relaxed by %i%%"]):
appendsolwarning(msg % (0.9+(val-1)*100), self, result,
Expand Down Expand Up @@ -103,6 +104,7 @@ def check_relaxed(self, result):
"Adds relaxation warnings to the result"
relaxed = get_relaxed(result["freevariables"][self.relaxvars],
range(len(self["relaxed constraints"])))
initsolwarning(result, "Relaxed Constraints")
for relaxval, i in relaxed:
relax_percent = "%i%%" % (0.5+(relaxval-1)*100)
oldconstraint = self.original_constraints[i]
Expand Down Expand Up @@ -224,6 +226,7 @@ def check_relaxed(self, result):
"Adds relaxation warnings to the result"
relaxed = get_relaxed([result["freevariables"][r]
for r in self.relaxvars], self.freedvars)
initsolwarning(result, "Relaxed Constants")
for (_, freed) in relaxed:
msg = (" %s: relaxed from %-.4g to %-.4g"
% (freed,
Expand Down
3 changes: 2 additions & 1 deletion gpkit/constraints/tight.py
@@ -1,7 +1,7 @@
"Implements Tight"
from .set import ConstraintSet
from ..small_scripts import mag
from ..small_scripts import appendsolwarning
from ..small_scripts import appendsolwarning, initsolwarning
from .. import SignomialsEnabled


Expand All @@ -18,6 +18,7 @@ def process_result(self, result):
"Checks that all constraints are satisfied with equality"
super().process_result(result)
variables = result["variables"]
initsolwarning(result, "Unexpectedly Loose Constraints")
for constraint in self.flat():
with SignomialsEnabled():
leftval = constraint.left.sub(variables).value
Expand Down
8 changes: 6 additions & 2 deletions gpkit/small_scripts.py
Expand Up @@ -3,12 +3,16 @@
import numpy as np


def appendsolwarning(msg, data, result, category="uncategorized"):
"Append a particular category of warnings to a solution."
def initsolwarning(result, category="uncategorized"):
"Creates a results dictionary for a particular category of warning."
if "warnings" not in result:
result["warnings"] = {}
if category not in result["warnings"]:
result["warnings"][category] = []


def appendsolwarning(msg, data, result, category="uncategorized"):
"Append a particular category of warnings to a solution."
result["warnings"][category].append((msg, data))


Expand Down
24 changes: 17 additions & 7 deletions gpkit/solution_array.py
Expand Up @@ -67,24 +67,30 @@ def msenss_table(data, _, **kwargs):
lines = ["Model Sensitivities", "-------------------"]
if kwargs["sortmodelsbysenss"]:
lines[0] += " (sorts models in sections below)"
previousmsenssstr = ""
for model, msenss in data:
if not model: # for now let's only do named models
continue
if (msenss < 0.1).all():
msenss = np.max(msenss)
if msenss:
msenssstr = "%6s" % ("<1E%i" % np.log10(msenss))
msenssstr = "%6s" % ("<1e%i" % np.log10(msenss))
else:
msenssstr = " =0 "
elif not msenss.shape:
msenssstr = "%+6.1f" % msenss
else:
meansenss = np.mean(msenss)
msenssstr = "%+6.1f" % meansenss
deltas = msenss - meansenss
deltastrs = ["%+4.1f" % d if abs(d) >= 0.1 else " - "
for d in deltas]
msenssstr = "%+6.1f + [ %s ]" % (meansenss, " ".join(deltastrs))

if np.max(np.abs(deltas)) > 0.1:
deltastrs = ["%+4.1f" % d if abs(d) >= 0.1 else " - "
for d in deltas]
msenssstr += " + [ %s ]" % " ".join(deltastrs)
if msenssstr == previousmsenssstr:
msenssstr = " "
else:
previousmsenssstr = msenssstr
lines.append("%s : %s" % (msenssstr, model))
return lines + [""] if len(lines) > 3 else []

Expand Down Expand Up @@ -234,9 +240,13 @@ def warnings_table(self, _, **kwargs):
return []
for wtype in sorted(self["warnings"]):
data_vec = self["warnings"][wtype]
if len(data_vec) == 0:
continue
if not hasattr(data_vec, "shape"):
data_vec = [data_vec]
for i, data in enumerate(data_vec):
if len(data) == 0:
continue
data = sorted(data, key=lambda l: l[0]) # sort by msg
title = wtype
if len(data_vec) > 1:
Expand Down Expand Up @@ -650,8 +660,8 @@ def summary(self, showvars=(), ntopsenss=5, **kwargs):
return out

def table(self, showvars=(),
tables=("cost", "warnings", "sweepvariables",
"model sensitivities", "freevariables",
tables=("cost", "warnings", "model sensitivities",
"sweepvariables", "freevariables",
"constants", "sensitivities", "tightest constraints"),
sortmodelsbysenss=True, **kwargs):
"""A table representation of this SolutionArray
Expand Down

0 comments on commit 02d3f26

Please sign in to comment.