Skip to content

Commit

Permalink
sort by model sensitivities and print a table of them
Browse files Browse the repository at this point in the history
  • Loading branch information
bqpd committed Dec 14, 2020
1 parent a436015 commit dc5076d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 25 deletions.
16 changes: 8 additions & 8 deletions docs/source/examples/performance_modeling_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ Optimal Cost

Free Variables
--------------
| Aircraft
W : 144.1 [lbf] weight
| Mission.FlightSegment.AircraftP.WingAero
D : [ 2.74 2.73 2.72 2.72 ] [lbf] drag force

| Mission.FlightSegment.AircraftP
Wburn : [ 0.274 0.273 0.272 0.272 ] [lbf] segment fuel burn
Wfuel : [ 1.09 0.817 0.544 0.272 ] [lbf] fuel weight

| Aircraft.Wing
S : 44.14 [ft²] surface area
W : 44.14 [lbf] weight
c : 1.279 [ft] mean chord

| Mission.FlightSegment.AircraftP
Wburn : [ 0.274 0.273 0.272 0.272 ] [lbf] segment fuel burn
Wfuel : [ 1.09 0.817 0.544 0.272 ] [lbf] fuel weight

| Mission.FlightSegment.AircraftP.WingAero
D : [ 2.74 2.73 2.72 2.72 ] [lbf] drag force
| Aircraft
W : 144.1 [lbf] weight

Variable Sensitivities
----------------------
Expand Down
5 changes: 5 additions & 0 deletions docs/source/examples/relaxation_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ Relaxed Constants
x_min: relaxed from 2 to 1
~~~~~~~~

Model Sensitivities (sorts models in sections below)
-------------------
+1.99 : Relax2.OriginalValues
+0.00 : Relax2

Free Variables
--------------
x : 1
Expand Down
9 changes: 8 additions & 1 deletion gpkit/constraints/gp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from time import time
from collections import defaultdict
import numpy as np
from ..repr_conventions import lineagestr
from ..small_classes import CootMatrix, SolverLog, Numbers, FixedScalar
from ..keydict import KeyDict
from ..solution_array import SolutionArray
Expand Down Expand Up @@ -306,6 +307,7 @@ def _compile_result(self, solver_out):
cost_senss = sum(nu_i*exp for (nu_i, exp) in zip(self.nu_by_posy[0],
self.cost.hmap))
gpv_ss = cost_senss.copy()
m_senss = defaultdict(float)
for las, nus, c in zip(la[1:], self.nu_by_posy[1:], self.hmaps[1:]):
while getattr(c, "parent", None) is not None:
c = c.parent
Expand All @@ -314,7 +316,12 @@ def _compile_result(self, solver_out):
gpv_ss[vk] = x + gpv_ss.get(vk, 0)
while getattr(c, "generated_by", None):
c = c.generated_by
result["sensitivities"]["constraints"][c] = c_senss
result["sensitivities"]["constraints"][c] = abs(c_senss)
m_senss[lineagestr(c)] += c_senss
# add fixed variables sensitivities to models
for vk, senss in gpv_ss.items():
m_senss[lineagestr(vk)] += abs(senss)
result["sensitivities"]["models"] = dict(m_senss)
# carry linked sensitivities over to their constants
for v in list(v for v in gpv_ss if v.gradients):
dlogcost_dlogv = gpv_ss.pop(v)
Expand Down
67 changes: 51 additions & 16 deletions gpkit/solution_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ def __exit__(self, type_, val, traceback):
else:
self.solarray["sensitivities"]["constraints"] = self.constraintstore

def msenss_table(data, _, **kwargs):
"Returns model sensitivity table lines"
if "models" not in data.get("sensitivities", {}):
return ""
data = sorted(data["sensitivities"]["models"].items(),
key=lambda i: -np.mean(i[1]))
lines = ["Model Sensitivities", "-------------------"]
if kwargs["sortmodelsbysenss"]:
lines[0] += " (sorts models in sections below)"
for model, msenss in data:
if not model: # for now let's only do named models
continue
if not msenss.shape:
msenssstr = "%+5.2f" % msenss
else:
meansenss = np.mean(msenss)
deltas = msenss - meansenss
deltastrs = ["%+4.1f" % d if abs(d) >= 0.1 else " - "
for d in deltas]
msenssstr = "%+5.2f + [ %s ]" % (meansenss, " ".join(deltastrs))

lines.append(" %s : %s" % (msenssstr, model))
return lines + [""] if len(lines) > 3 else []


def senss_table(data, showvars=(), title="Variable Sensitivities", **kwargs):
"Returns sensitivity table lines"
Expand Down Expand Up @@ -156,7 +180,7 @@ def constraint_table(data, title, sortbymodel=True, showmodels=True, **_):
if lines:
lines.append(["", ""])
if model or lines:
lines.append([("modelname",), model])
lines.append([("newmodelline",), model])
previous_model = model
constrstr = constrstr.replace(model, "")
minlen, maxlen = 25, 80
Expand All @@ -183,12 +207,12 @@ def constraint_table(data, title, sortbymodel=True, showmodels=True, **_):
if not lines:
lines = [("", "(none)")]
maxlens = np.max([list(map(len, line)) for line in lines
if line[0] != ("modelname",)], axis=0)
if line[0] != ("newmodelline",)], axis=0)
dirs = [">", "<"] # we'll check lengths before using zip
assert len(list(dirs)) == len(list(maxlens))
fmts = ["{0:%s%s}" % (direc, L) for direc, L in zip(dirs, maxlens)]
for i, line in enumerate(lines):
if line[0] == ("modelname",):
if line[0] == ("newmodelline",):
linelist = [fmts[0].format(" | "), line[1]]
else:
linelist = [fmt.format(s) for fmt, s in zip(fmts, line)]
Expand Down Expand Up @@ -231,6 +255,7 @@ def warnings_table(self, _, **kwargs):
TABLEFNS = {"sensitivities": senss_table,
"top sensitivities": topsenss_table,
"insensitivities": insenss_table,
"model sensitivities": msenss_table,
"tightest constraints": tight_table,
"loose constraints": loose_table,
"warnings": warnings_table,
Expand Down Expand Up @@ -552,12 +577,14 @@ def savecsv(self, showvars=None, filename="solution.csv", valcols=5):
valcols = 1
if maxspan < valcols:
valcols = maxspan
lines = var_table(data, "", rawlines=True, maxcolumns=valcols)
lines = var_table(data, "", rawlines=True, maxcolumns=valcols,
tables=("cost", "sweepvariables", "freevariables",
"constants", "sensitivities"))
with open(filename, "w") as f:
f.write("Model Name,Variable Name,Value(s)" + ","*valcols
+ "Units,Description\n")
for line in lines:
if line[0] == ("modelname",):
if line[0] == ("newmodelline",):
f.write(line[1])
elif not line[1]: # spacer line
f.write("\n")
Expand Down Expand Up @@ -612,9 +639,10 @@ def summary(self, showvars=(), ntopsenss=5, **kwargs):
return out

def table(self, showvars=(),
tables=("cost", "warnings", "sweepvariables", "freevariables",
tables=("cost", "warnings", "sweepvariables",
"model sensitivities", "freevariables",
"constants", "sensitivities", "tightest constraints"),
**kwargs):
sortmodelsbysenss=True, **kwargs):
"""A table representation of this SolutionArray
Arguments
Expand All @@ -634,6 +662,10 @@ def table(self, showvars=(),
-------
str
"""
if sortmodelsbysenss:
kwargs["sortmodelsbysenss"] = self["sensitivities"]["models"]
else:
kwargs["sortmodelsbysenss"] = False
varlist = list(self["variables"])
has_only_one_model = True
for var in varlist[1:]:
Expand Down Expand Up @@ -703,7 +735,7 @@ def var_table(data, title, *, printunits=True, latex=False, rawlines=False,
varfmt="%s : ", valfmt="%-.4g ", vecfmt="%-8.3g",
minval=0, sortbyvals=False, hidebelowminval=False,
included_models=None, excluded_models=None, sortbymodel=True,
maxcolumns=5, skipifempty=True, **_):
maxcolumns=5, skipifempty=True, sortmodelsbysenss=None, **_):
"""
Pretty string representation of a dict of VarKeys
Iterable values are handled specially (partial printing)
Expand Down Expand Up @@ -740,15 +772,18 @@ def var_table(data, title, *, printunits=True, latex=False, rawlines=False,
if minval and hidebelowminval and getattr(v, "shape", None):
v[np.abs(v) <= minval] = np.nan
model = lineagestr(k.lineage) if sortbymodel else ""
msenss = -sortmodelsbysenss.get(model, 0) if sortmodelsbysenss else 0
if hasattr(msenss, "shape"):
msenss = np.mean(msenss)
models.add(model)
b = bool(getattr(v, "shape", None))
s = k.str_without(("lineage", "vec"))
if not sortbyvals:
decorated.append((model, b, (varfmt % s), i, k, v))
decorated.append((msenss, model, b, (varfmt % s), i, k, v))
else: # for consistent sorting, add small offset to negative vals
val = np.nanmean(np.abs(v)) - (1e-9 if np.nanmean(v) < 0 else 0)
sort = (float("%.4g" % -val), k.name)
decorated.append((model, sort, b, (varfmt % s), i, k, v))
decorated.append((model, sort, msenss, b, (varfmt % s), i, k, v))
if not decorated and skipifempty:
return []
if included_models:
Expand All @@ -760,18 +795,18 @@ def var_table(data, title, *, printunits=True, latex=False, rawlines=False,
decorated.sort()
previous_model, lines = None, []
for varlist in decorated:
if not sortbyvals:
model, isvector, varstr, _, var, val = varlist
if sortbyvals:
model, _, msenss, isvector, varstr, _, var, val = varlist
else:
model, _, isvector, varstr, _, var, val = varlist
msenss, model, isvector, varstr, _, var, val = varlist
if model not in models:
continue
if model != previous_model:
if lines:
lines.append(["", "", "", ""])
if model:
if not latex:
lines.append([("modelname",), model, "", ""])
lines.append([("newmodelline",), model, "", ""])
else:
lines.append(
[r"\multicolumn{3}{l}{\textbf{" + model + r"}} \\"])
Expand Down Expand Up @@ -830,13 +865,13 @@ def var_table(data, title, *, printunits=True, latex=False, rawlines=False,
if not latex:
if lines:
maxlens = np.max([list(map(len, line)) for line in lines
if line[0] != ("modelname",)], axis=0)
if line[0] != ("newmodelline",)], axis=0)
dirs = [">", "<", "<", "<"]
# check lengths before using zip
assert len(list(dirs)) == len(list(maxlens))
fmts = ["{0:%s%s}" % (direc, L) for direc, L in zip(dirs, maxlens)]
for i, line in enumerate(lines):
if line[0] == ("modelname",):
if line[0] == ("newmodelline",):
line = [fmts[0].format(" | "), line[1]]
else:
line = [fmt.format(s) for fmt, s in zip(fmts, line)]
Expand Down

0 comments on commit dc5076d

Please sign in to comment.