From d2057e6b0588410195982123acfd4f388c4950c9 Mon Sep 17 00:00:00 2001 From: Edward Burnell Date: Mon, 10 Aug 2020 03:09:26 -0800 Subject: [PATCH] fixes --- .../examples/performance_modeling_output.txt | 2 +- gpkit/interactive/sankey.py | 56 +++++++++++-------- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/docs/source/examples/performance_modeling_output.txt b/docs/source/examples/performance_modeling_output.txt index 925067a43..c6c9bb742 100644 --- a/docs/source/examples/performance_modeling_output.txt +++ b/docs/source/examples/performance_modeling_output.txt @@ -125,4 +125,4 @@ Sensitivity Differences |above 0.1| ----------------------------------- The largest is -0.00451643. -Links exceed link limit (56 > 50); skipping constraints inside arrays. +Links exceed link limit (52 > 50); skipping constraints inside arrays. diff --git a/gpkit/interactive/sankey.py b/gpkit/interactive/sankey.py index f41995c63..955895e4e 100644 --- a/gpkit/interactive/sankey.py +++ b/gpkit/interactive/sankey.py @@ -22,10 +22,13 @@ def getcolor(value): return "#cfcfcf" return GPCOLORS[0 if value < 0 else 1] + +# pylint: disable=too-many-instance-attributes class Sankey: "diagrams of sensitivity flow" maxdepth = np.inf maxlinks = 50 + constraintlabels = True def __init__(self, solution, constraintset, leftlabel=None): self.csenss = solution["sensitivities"]["constraints"] @@ -37,18 +40,19 @@ def __init__(self, solution, constraintset, leftlabel=None): self.links_to_target = defaultdict(int) self.nodes = {} - def add_node(self, target, title, type): + def add_node(self, target, title, tag): + "adds nodes of a given target, title, and tag to self.nodes" self.links_to_target[target] += 1 node = "%s.%04i" % (target, self.links_to_target[target]) - self.nodes[node] = {"id": node, "title": title, type: True} + self.nodes[node] = {"id": node, "title": title, tag: True} return node # pylint: disable=too-many-branches - def link(self, cset, target, var, depth=0, subarray=False, named=False): + def link(self, cset, target, var, depth=0, subarray=False, labeled=False): "adds links of a given constraint set to self.links" total_sens = 0 switchedtarget = False - if not named and isnamedmodel(cset) and 0 < depth <= self.maxdepth: + if not labeled and isnamedmodel(cset) and 0 < depth <= self.maxdepth: switchedtarget = target target = self.add_node(target, cset.lineage[-1][0], "namedmodel") depth += 1 @@ -65,12 +69,13 @@ def link(self, cset, target, var, depth=0, subarray=False, named=False): cset = {k: cset[i] for k, i in cset.idxlookup.items()} if isinstance(cset, dict): for label, c in cset.items(): - if depth > self.maxdepth: - subtotal_sens = self.link(c, target, var, depth, subarray) + if depth > self.maxdepth or not self.constraintlabels: + subtotal_sens = self.link(c, target, var, depth, + subarray, True) else: source = self.add_node(target, label, "constraintlabel") subtotal_sens = self.link(c, source, var, depth+1, - subarray, isnamedmodel(c)) + subarray, True) self.links[source, target] += subtotal_sens total_sens += subtotal_sens elif isinstance(cset, Iterable): @@ -79,36 +84,40 @@ def link(self, cset, target, var, depth=0, subarray=False, named=False): total_sens += subtotal_sens else: if var is None and cset in self.csenss: - total_sens = -abs(self.csenss[cset]) + total_sens = -abs(self.csenss[cset]) or -1e-10 elif var is not None and var.key in cset.v_ss: - total_sens = cset.v_ss[var.key] or 1e-30 # just an epsilon - if subarray: - source = self.add_node(target, "", "subarray") - else: - cstr = cset.str_without(["lineage", "units"]) - label = cstr if len(cstr) <= 30 else "%s ..." % cstr[:30] - source = self.add_node(target, label, "constraint") - self.links[source, target] = total_sens + total_sens = cset.v_ss[var.key] or 1e-10 # just an epsilon + if not labeled and depth <= self.maxdepth: + if subarray: + source = self.add_node(target, "", "subarray") + else: + cstr = cset.str_without(["lineage", "units"]) + label = cstr if len(cstr) <= 30 else "%s ..." % cstr[:30] + source = self.add_node(target, label, "constraint") + self.links[source, target] = total_sens if switchedtarget: self.links[target, switchedtarget] += total_sens return total_sens - def filterlinks(self, label, function): + def filterlinks(self, label, function, forced=False): "If over maxlinks, removes links that do not match criteria." - if len(self.links) > self.maxlinks: - if label: + if len(self.links) > self.maxlinks or forced: + if label and not forced: print("Links exceed link limit (%s > %s); skipping %s." % (len(self.links), self.maxlinks, label)) self.links = {(s, t): v for (s, t), v in self.links.items() if function(s, t, v)} # pylint: disable=too-many-locals - def diagram(self, variable=None, *, maxdepth=None, - width=900, height=400, top=0, bottom=0, left=120, right=120): + def diagram(self, variable=None, *, + top=0, bottom=0, left=120, right=120, width=900, height=400, + maxdepth=None, constraints=True, constraintlabels=True): "creates links and an ipython widget to show them" margins = dict(top=top, bottom=bottom, left=left, right=right) if maxdepth is not None: self.maxdepth = maxdepth # NOTE: side effects + if constraintlabels is not None: + self.constraintlabels = constraintlabels total_sens = self.link(self.cset, self.leftlabel, variable) if variable: @@ -122,7 +131,8 @@ def diagram(self, variable=None, *, maxdepth=None, self.filterlinks("constraints inside arrays", lambda s, t, v: "subarray" not in self.nodes[s]) self.filterlinks("all constraints", - lambda s, t, v: "constraint" not in self.nodes[s]) + lambda s, t, v: "constraint" not in self.nodes[s], + forced=constraints) self.filterlinks("constraint labels", lambda s, t, v: "constraintlabel" not in self.nodes[s]) @@ -137,6 +147,7 @@ def diagram(self, variable=None, *, maxdepth=None, out = SankeyWidget(nodes=nodes, links=links, margins=margins, layout=Layout(width=str(width), height=str(height))) + filename = self.leftlabel if variable: filename += "_" + variable.key.name @@ -144,4 +155,5 @@ def diagram(self, variable=None, *, maxdepth=None, # clean up side effects self.links = defaultdict(float) self.maxdepth = Sankey.maxdepth + self.constraintlabels = Sankey.constraintlabels return out