Skip to content

Commit

Permalink
Add a "Functions" scene group (#394)
Browse files Browse the repository at this point in the history
This change adds a "Functions" scene group if the graph being visualized has a function library, which contains definitions for functions (templates for subgraphs) used in the TensorFlow graph.

As part of that effort, this change also strips the arbitrary hexadecimal suffix from names of functions in the graph. That suffix is not user-generated - it merely serves to distinguish between functions with the same name but different signatures.

This refactors the implementation to prefix metanodes representing functions with FUNCTION_LIBRARY_NODE_PREFIX instead of lumping them under a metanode named FUNCTION_LIBRARY_NODE. We now also remove the function metanodes from the core graph later when building the sub-hierarchy instead of excluding them in the first place from the __root__ metagraph. That lets us use existing paths to render the metanodes for functions in the scene group.

The metanodes in the scene group (which represent function definitions) are not yet linked to individual calls throughout the graph - that will happen in a later change.

This change is complex and will be tested via internal Selenium-based integration tests.
  • Loading branch information
chihuahua authored Aug 24, 2017
1 parent f4f0a09 commit 407806a
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 52 deletions.
34 changes: 27 additions & 7 deletions tensorboard/plugins/graph/tf_graph/tf-graph-scene.html
Original file line number Diff line number Diff line change
Expand Up @@ -504,11 +504,7 @@
top: 20px;
}

.title {
position: absolute;
}

.auxTitle {
.title, .auxTitle, .functionLibraryTitle {
position: absolute;
}

Expand All @@ -521,6 +517,7 @@
<div class="titleContainer">
<div id="title" class="title">Main Graph</div>
<div id="auxTitle" class="auxTitle">Auxiliary Nodes</div>
<div id="functionLibraryTitle" class="functionLibraryTitle">Functions</div>
</div>
<svg id="svg">
<defs>
Expand Down Expand Up @@ -899,7 +896,10 @@
_updateLabels: function(showLabels) {
var mainGraphTitleElement = this.getElementsByClassName('title')[0];
var titleStyle = mainGraphTitleElement.style;
var auxTitleStyle = this.getElementsByClassName('auxTitle')[0].style;
var auxTitleElement = this.getElementsByClassName('auxTitle')[0];
var auxTitleStyle = auxTitleElement.style;
var functionLibraryTitleStyle =
this.getElementsByClassName('functionLibraryTitle')[0].style;
var core = d3.select("." + tf.graph.scene.Class.Scene.GROUP + ">." +
tf.graph.scene.Class.Scene.CORE).node();
// Only show labels if the graph is fully loaded.
Expand All @@ -925,9 +925,29 @@
} else {
auxTitleStyle.display = 'none';
}

let functionLibrary = d3.select(
"." + tf.graph.scene.Class.Scene.GROUP + ">." +
tf.graph.scene.Class.Scene.FUNCTION_LIBRARY).node();
let functionLibraryX =
functionLibrary ? functionLibrary.getCTM().e : null;
if (functionLibraryX !== null && functionLibraryX !== auxX) {
functionLibraryTitleStyle.display = 'inline';

// Make sure that the function library title is positioned rightwards
// enough so as to prevent overlap with other content.
functionLibraryX = Math.max(
auxX + auxTitleElement.getBoundingClientRect().width,
functionLibraryX);

functionLibraryTitleStyle.left = functionLibraryX + 'px';
} else {
functionLibraryTitleStyle.display = 'none';
}
} else {
titleStyle.display='none';
titleStyle.display = 'none';
auxTitleStyle.display = 'none';
functionLibraryTitleStyle.display = 'none';
}
},
/**
Expand Down
10 changes: 8 additions & 2 deletions tensorboard/plugins/graph/tf_graph_common/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module tf.graph {
/** Delimiter used in node names to denote namespaces. */
export const NAMESPACE_DELIM = '/';
export const ROOT_NAME = '__root__';
export const FUNCTION_LIBRARY_NODE = '__function_library__';
export const FUNCTION_LIBRARY_NODE_PREFIX = '__function_library__';

/** Attribute key used for storing attributes that are too large. */
export const LARGE_ATTRS_KEY = '_too_large_attrs';
Expand Down Expand Up @@ -313,6 +313,10 @@ export interface Metanode extends GroupNode {
depth: number;
templateId: string;
opHistogram: {[op: string]: number};

// The name of the function this metanode is associated with if any.
associatedFunction: string;

getFirstChild(): GroupNode|OpNode;
getRootOp(): OpNode;
/** Return name of all leaves inside a metanode. */
Expand Down Expand Up @@ -596,6 +600,7 @@ export class MetanodeImpl implements Metanode {
hasNonControlEdges: boolean;
include: InclusionType;
nodeAttributes: {[key: string]: any;};
associatedFunction: string;

/** A label object for meta-nodes in the graph hierarchy */
constructor(name: string, opt = {}) {
Expand Down Expand Up @@ -626,6 +631,7 @@ export class MetanodeImpl implements Metanode {
this.parentNode = null;
this.hasNonControlEdges = false;
this.include = InclusionType.UNSPECIFIED;
this.associatedFunction = '';
}

getFirstChild(): GroupNode|OpNode {
Expand Down Expand Up @@ -1059,7 +1065,7 @@ export function build(
const processFunction = (func: tf.graph.proto.FunctionDef) => {
// Give the function itself a node.
const functionNodeName =
FUNCTION_LIBRARY_NODE + NAMESPACE_DELIM + func.signature.name;
FUNCTION_LIBRARY_NODE_PREFIX + func.signature.name;
// Create an op node for the function. Mark it as part of a
// function library.
processRawNode({
Expand Down
39 changes: 15 additions & 24 deletions tensorboard/plugins/graph/tf_graph_common/hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,24 @@ function addNodes(h: Hierarchy, graph: SlimGraph) {
child.parentNode = parent;
h.setNode(name, child);
parent.metagraph.setNode(name, child);

if (name.indexOf(tf.graph.FUNCTION_LIBRARY_NODE_PREFIX) === 0 &&
parent.name === tf.graph.ROOT_NAME) {
// This metanode represents a function in the Library. We later copy
// its contents to dynamically inject function data into the graph
// when the subhierarchy of a metanode is built (upon its expansion).
const functionName = name.substring(
tf.graph.FUNCTION_LIBRARY_NODE_PREFIX.length);

// For now, remember the metanode that represents the function with
// this name.
h.libraryFunctions[functionName] = child;
child.associatedFunction = functionName;
}
}
parent = child;
}

// Assuming node name is 'a/b/c', assign the OpNode as a child of the
// metanode 'a/b'.
h.setNode(node.name, node);
Expand All @@ -611,30 +626,6 @@ function addNodes(h: Hierarchy, graph: SlimGraph) {
embedding.parentNode = node;
});
});

const libraryFunctionNode =
h.node(tf.graph.FUNCTION_LIBRARY_NODE) as Metanode;
if (libraryFunctionNode) {
// This graph has a function library. Remove the library from the root node
// itself. We later dynamically add copies of functions into metanodes that
// are actually function calls.
const rootNode = libraryFunctionNode.parentNode as Metanode;
rootNode.metagraph.removeNode(libraryFunctionNode.name);

// Add all of the library function node's children to a mapping. All of the
// nodes within this special node for library functions are themselves
// metanodes for library functions.
_.each(libraryFunctionNode.metagraph.nodes(), functionNodeName => {
const childNode =
libraryFunctionNode.metagraph.node(functionNodeName) as Metanode;
if (childNode.type === tf.graph.NodeType.META) {
const functionName = functionNodeName.substring(
tf.graph.FUNCTION_LIBRARY_NODE.length +
tf.graph.NAMESPACE_DELIM.length);
h.libraryFunctions[functionName] = childNode;
}
});
}
};

/**
Expand Down
52 changes: 43 additions & 9 deletions tensorboard/plugins/graph/tf_graph_common/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const PARAMS = {
/** X-space between each extracted node and the core graph. */
extractXOffset: 15,
/** Y-space between each extracted node. */
extractYOffset: 20
extractYOffset: 20,
},
series: {
paddingTop: 10,
Expand Down Expand Up @@ -200,6 +200,15 @@ export const PARAMS = {
}
};

/**
* The minimum width we confer upon the auxiliary nodes section if functions
* also appear. Without enforcing this minimum, metanodes in the function
* library section could jut into the auxiliary nodes section because the
* title "Auxiliary Nodes" is longer than the width of the auxiliary nodes
* section itself.
*/
export const MIN_AUX_WIDTH = 140;

/** Calculate layout for a scene of a group node. */
export function layoutScene(renderNodeInfo: render.RenderGroupNodeInfo): void {
// Update layout, size, and annotations of its children nodes and edges.
Expand Down Expand Up @@ -228,8 +237,7 @@ function updateTotalWidthOfNode(renderInfo: render.RenderNodeInfo): void {
renderInfo.coreBox.width = renderInfo.width;
renderInfo.coreBox.height = renderInfo.height;
// TODO: Account for font width rather than using a magic number.
let labelLength = renderInfo.node.name.length -
renderInfo.node.name.lastIndexOf(NAMESPACE_DELIM) - 1;
let labelLength = renderInfo.displayName.length;
let charWidth = 3; // 3 pixels per character.
// Compute the total width of the node.
renderInfo.width = Math.max(renderInfo.coreBox.width +
Expand All @@ -245,7 +253,8 @@ function layoutChildren(renderNodeInfo: render.RenderGroupNodeInfo): void {
let children = renderNodeInfo.coreGraph.nodes().map(n => {
return renderNodeInfo.coreGraph.node(n);
}).concat(renderNodeInfo.isolatedInExtract,
renderNodeInfo.isolatedOutExtract);
renderNodeInfo.isolatedOutExtract,
renderNodeInfo.libraryFunctionsExtract);

_.each(children, childNodeInfo => {
// Set size of each child
Expand Down Expand Up @@ -475,6 +484,23 @@ function layoutMetanode(renderNodeInfo: render.RenderGroupNodeInfo): void {
return height + yOffset + child.height;
}, 0);

// Calculate the position of nodes in libraryFunctionsExtract relative to the
// top-left corner of libraryFunctionsBox (the bounding box for all library
// function nodes) and calculate the size of the libraryFunctionsBox.
let maxLibraryFunctionsWidth = _.max(renderNodeInfo.libraryFunctionsExtract,
renderNode => renderNode.width).width;
renderNodeInfo.libraryFunctionsBox.width = maxLibraryFunctionsWidth != null ?
maxLibraryFunctionsWidth : 0;

renderNodeInfo.libraryFunctionsBox.height =
_.reduce(renderNodeInfo.libraryFunctionsExtract, (height, child, i) => {
let yOffset = i > 0 ? params.extractYOffset : 0;
// use width/height here to avoid overlaps between extracts
child.x = 0;
child.y = height + yOffset + child.height / 2;
return height + yOffset + child.height;
}, 0);

// Compute the total padding between the core graph, in-extract and
// out-extract boxes.
let numParts = 0;
Expand All @@ -484,20 +510,28 @@ function layoutMetanode(renderNodeInfo: render.RenderGroupNodeInfo): void {
if (renderNodeInfo.isolatedOutExtract.length > 0) {
numParts++;
}
if (renderNodeInfo.libraryFunctionsExtract.length > 0) {
numParts++;
}
if (renderNodeInfo.coreGraph.nodeCount() > 0) {
numParts++;
}
let offset = PARAMS.subscene.meta.extractXOffset;
let padding = numParts <= 1 ? 0 : (numParts <= 2 ? offset : 2 * offset);

// Add the in-extract and out-extract width to the core box width.
renderNodeInfo.coreBox.width += renderNodeInfo.inExtractBox.width +
renderNodeInfo.outExtractBox.width + padding;
let padding = numParts <= 1 ? 0 : (numParts * offset);

// Add the in-extract and out-extract width to the core box width. Do not let
// the auxiliary width be too small, lest it be smaller than the title.
const auxWidth = Math.max(
MIN_AUX_WIDTH,
renderNodeInfo.inExtractBox.width + renderNodeInfo.outExtractBox.width);
renderNodeInfo.coreBox.width += auxWidth + padding +
renderNodeInfo.libraryFunctionsBox.width + padding;
renderNodeInfo.coreBox.height =
params.labelHeight +
Math.max(
renderNodeInfo.inExtractBox.height,
renderNodeInfo.coreBox.height,
renderNodeInfo.libraryFunctionsBox.height,
renderNodeInfo.outExtractBox.height
);
// Determine the whole metanode's width (from left to right).
Expand Down
7 changes: 4 additions & 3 deletions tensorboard/plugins/graph/tf_graph_common/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,7 @@ export function getGroupSettingLabel(node: Node) {
*/
function labelBuild(nodeGroup, renderNodeInfo: render.RenderNodeInfo,
sceneElement) {
let namePath = renderNodeInfo.node.name.split('/');
let text = namePath[namePath.length - 1];
let text = renderNodeInfo.displayName;

// Truncate long labels for unexpanded Metanodes.
let useFontScale = renderNodeInfo.node.type === NodeType.META &&
Expand Down Expand Up @@ -739,7 +738,9 @@ export function stylize(nodeGroup, renderInfo: render.RenderNodeInfo,
nodeClass = nodeClass || Class.Node.SHAPE;
let isHighlighted = sceneElement.isNodeHighlighted(renderInfo.node.name);
let isSelected = sceneElement.isNodeSelected(renderInfo.node.name);
let isExtract = renderInfo.isInExtract || renderInfo.isOutExtract;
let isExtract = renderInfo.isInExtract ||
renderInfo.isOutExtract ||
renderInfo.isLibraryFunction;
let isExpanded = renderInfo.expanded && nodeClass !== Class.Annotation.NODE;
let isFadedOut = renderInfo.isFadedOut;
nodeGroup.classed('highlighted', isHighlighted);
Expand Down
Loading

0 comments on commit 407806a

Please sign in to comment.