aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts')
-rw-r--r--tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts889
1 files changed, 889 insertions, 0 deletions
diff --git a/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts b/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
new file mode 100644
index 0000000000..64e537154b
--- /dev/null
+++ b/tensorflow/tensorboard/components/tf-graph-common/lib/graph.ts
@@ -0,0 +1,889 @@
+/// <reference path="../../../typings/tsd.d.ts" />
+/// <reference path="common.ts" />
+module tf.graph {
+
+/** Delimiter used in node names to denote namespaces. */
+export const NAMESPACE_DELIM = "/";
+const FULL_GRAPH_NAME = "fullGraph";
+export const ROOT_NAME = "__root__";
+
+// Separator between the source and the destination name of the edge.
+export const EDGE_KEY_DELIM = "--";
+
+export enum GraphType {FULL, EMBEDDED, META, SERIES, CORE, SHADOW, BRIDGE,
+ EDGE};
+export enum NodeType {META, OP, SERIES, BRIDGE, ELLIPSIS};
+
+/**
+ * A BaseEdge is the label object (in the graphlib sense) for an edge in the
+ * original, full graph produced after parsing. Subsequent graphs, like those
+ * which belong to Metanodes, should not use BaseEdge objects, but instead
+ * contain Metaedges (which in turn may contain any number of BaseEdges).
+ */
+export interface BaseEdge extends graphlib.EdgeObject {
+ isControlDependency: boolean;
+ isReferenceEdge: boolean;
+}
+
+/**
+ * A SlimGraph is inspired by graphlib.Graph, but having only the functionality
+ * that we need.
+ */
+export class SlimGraph {
+ nodes: { [nodeName: string]: OpNode };
+ edges: BaseEdge[];
+
+ constructor() {
+ this.nodes = {};
+ this.edges = [];
+ }
+}
+
+interface NormalizedInput {
+ name: string;
+ hasNumberPart: boolean;
+ isControlDependency: boolean;
+}
+
+interface BuildParams {
+ enableEmbedding: boolean;
+ inEmbeddingTypes: string[];
+ outEmbeddingTypes: string[];
+ refEdges: { [inputEdge: string]: boolean };
+}
+
+/**
+ * The most basic information about a node in the hierarchical graph.
+ */
+export interface Node {
+ /** The name of the node, used frequently to look up nodes by name. */
+ name: string;
+ /** Which type of node this is. */
+ type: NodeType;
+ /**
+ * Whether this node is a type that may contain other nodes. Those types
+ * should extend from GroupNode.
+ *
+ * For an OpNode, isGroupNode will be false, even though it may have
+ * embeddings. These embedding Nodes will have their parentNode set to the
+ * OpNode. However, embeddings are later rendered as annotations, not as
+ * children to be made visible on expansion (like a Metanode or SeriesNode).
+ */
+ isGroupNode: boolean;
+ /**
+ * The number of nodes this node represents. For OpNodes, this will be 1, and
+ * for GroupNodes it will be a count of the total number of descendents it
+ * contains.
+ */
+ cardinality: number;
+ /**
+ * The Node which is this Node's parent. This is of type Node and not
+ * GroupNode because of embeddings, which will have a parent OpNode.
+ */
+ parentNode: Node;
+ /** Runtime execution stats for this node, if available */
+ stats: NodeStats;
+}
+
+export interface OpNode extends Node {
+ op: string;
+ device: string;
+ attr: {key: string, value: Object}[];
+ inputs: NormalizedInput[];
+ inEmbeddings: OpNode[];
+ outEmbeddings: OpNode[];
+}
+
+export interface BridgeNode extends Node {
+ /**
+ * Whether this bridge node represents edges coming into its parent node.
+ */
+ inbound: boolean;
+}
+
+/**
+ * A node that is used when there are more than the maximum number of allowed
+ * annotations hanging off of a node. This node represents an ellipsis
+ * annotation, indicating a number of additional annotations.
+ */
+export interface EllipsisNode extends Node {
+ /**
+ * The number of nodes this ellipsis represents.
+ */
+ numMoreNodes: number;
+
+ /**
+ * Sets the number of nodes this ellipsis represents and changes the node
+ * name accordingly.
+ */
+ setNumMoreNodes(numNodes: number);
+}
+
+export interface GroupNode extends Node {
+ /**
+ * The metagraph contains nodes and metaedges between the immediate children
+ * of this group. The node label objects may be other GroupNodes (like
+ * SeriesNodes and Metanodes) or individual OpNodes. All edge label objects
+ * are Metaedges, each of which contains references to the original
+ * BaseEdge(s) from which it was created.
+ */
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+
+ /**
+ * The bridgegraph contains only edges which link immediate children of this
+ * group with nodes outside of the metagraph. As in the metagraph, all edge
+ * label objects are Metaedges which contain references to the original
+ * BaseEdge(s) that contribute to it.
+ *
+ * For a Metaedge in the bridgegraph, its external endpoint will be the same
+ * as the metagraph edge from which it came. This is most easily explained
+ * by example.
+ *
+ * Consider an original graph that contains a BaseEdge A/B/C->Z/Y/X.
+ *
+ * +-------+ (BaseEdge) +-------+
+ * | A/B/C |>----------------->| Z/Y/X |
+ * +-------+ +-------+
+ *
+ * When we construct the Root's metagraph, it will contain nodes for A and Z,
+ * and a Metaedge A->Z. The A->Z Metaedge will contain the original BaseEdge
+ * A/B/C->Z/Y/X in its baseEdgeGraph. The Root's bridgegraph will always be
+ * empty.
+ *
+ * +---+ (Root.metagraph edge) +---+
+ * | A |>--------------------------->| Z |
+ * +---+ +---+
+ *
+ * Now consider the Metanode A. Its metagraph will contain a Metanode for A/B
+ * and no edges. A's bridgegraph will have one Metaedge from A/B->Z, which
+ * was derived from the Root's Metaedge A->Z. That Metaedge will contain the
+ * original BaseEdge in its baseEdgeGraph.
+ *
+ * +---------+
+ * | A |
+ * | +---+ | (A.bridgegraph edge) +---+
+ * | | B |>---------------------------->| Z |
+ * | +---+ | +---+
+ * +---------+
+ *
+ * Finally, consider the Metanode A/B. Its metagraph will contain a Metanode
+ * for A/B/C and again no edges. A/B's bridgegraph will have one Metaedge
+ * from A/B/C->Z, which was derived from A's bridgegraph Metaedge A/B->Z.
+ * As before, the A/B/C->Z Metaedge will contain the original BaseEdge in its
+ * baseEdgeGraph.
+ *
+ * +---------------+
+ * | A |
+ * | +---------+ |
+ * | | B | |
+ * | | +---+ | | (A/B.bridgegraph edge) +---+
+ * | | | C |>----------------------------------->| Z |
+ * | | +---+ | | +---+
+ * | +---------+ |
+ * +---------------+
+ *
+ * Likewise, under the Metanode Z and Z/Y, to compute the bridgegraph, we'll
+ * end up with Metaedges A->Z/Y and A->Z/Y/X respectively. So the original
+ * BaseEdge A/B/C->Z/Y/X becomes four different Metaedges in four different
+ * bridgegraphs:
+ *
+ * + A/B->Z in GroupNode A's bridgegraph,
+ * + A/B/C->Z in GroupNode A/B's bridgegraph,
+ * + A->Z/Y in GroupNode Z's bridgegraph, and
+ * + A->Z/Y/X in GroupNode Z/Y's bridgegraph.
+ *
+ * Considering any BaseEdge then, if N is the number of path segments in the
+ * source and M is the number of path semgents in the destination, then the
+ * total number of bridgegraph edges you could create would be (N-1)(M-1).
+ *
+ * For this reason, it is computationally expensive to generate all the
+ * bridgegraphs for all the Metanodes, and instead they should be computed
+ * on demand as needed.
+ */
+ bridgegraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+
+ /**
+ * Stores how many times each device name appears in its children
+ * op nodes. Used to color group nodes by devices.
+ */
+ deviceHistogram: {[device: string]: number};
+
+ /**
+ * Flag indicating whether this GroupNode's metagraph contains any edges that
+ * are not control edges. Used to quickly determine how to draw a collapsed
+ * series (vertically or horizontally).
+ */
+ hasNonControlEdges: boolean;
+}
+
+export interface Metanode extends GroupNode {
+ depth: number;
+ templateId: string;
+ opHistogram: {[op: string]: number};
+ getFirstChild(): GroupNode|OpNode;
+ getRootOp(): OpNode;
+ /** Return name of all leaves inside a metanode. */
+ leaves(): string[];
+}
+
+export interface SeriesNode extends GroupNode {
+ hasLoop: boolean;
+ prefix: string;
+ suffix: string;
+ clusterId: number;
+ ids: number[];
+ parent: string;
+}
+
+export class EllipsisNodeImpl implements EllipsisNode {
+ name: string;
+ numMoreNodes: number;
+ stats: NodeStats;
+ type: NodeType;
+ isGroupNode: boolean;
+ cardinality: number;
+ parentNode: Node;
+
+ /**
+ * Constructs a new ellipsis annotation node.
+ *
+ * @param numNodes The number of additional annotations this node represents.
+ */
+ constructor(numNodes: number) {
+ this.type = NodeType.ELLIPSIS;
+ this.isGroupNode = false;
+ this.cardinality = 1;
+ this.parentNode = null;
+ this.stats = null;
+ this.setNumMoreNodes(numNodes);
+ }
+
+ setNumMoreNodes(numNodes: number) {
+ this.numMoreNodes = numNodes;
+ this.name = "... " + numNodes + " more";
+ }
+};
+
+/**
+ * A label object for nodes in the full graph and leaf nodes in the render
+ * graph.
+ */
+class OpNodeImpl implements OpNode {
+ name: string;
+ op: string;
+ device: string;
+ stats: NodeStats;
+ attr: {key: string, value: Object}[];
+ inputs: NormalizedInput[];
+ type: NodeType;
+ isGroupNode: boolean;
+ cardinality: number;
+ inEmbeddings: OpNode[];
+ outEmbeddings: OpNode[];
+ parentNode: Node;
+
+ /**
+ * Constructs a new Op node.
+ *
+ * @param rawNode The raw node.
+ * @param normalizedInputs An array of normalized
+ * inputs that denote the incoming edges to the current node. Each input
+ * contains the normalized name of the source node, whether it has a number
+ * part and whether it is a control dependency.
+ */
+ constructor(rawNode: tf.TFNode, normalizedInputs: NormalizedInput[]) {
+ this.op = rawNode.op;
+ this.name = rawNode.name;
+ this.device = rawNode.device;
+ this.attr = rawNode.attr;
+ this.inputs = normalizedInputs;
+ // additional properties
+ this.type = NodeType.OP;
+ this.isGroupNode = false;
+ this.cardinality = 1;
+ this.inEmbeddings = [];
+ this.outEmbeddings = [];
+ this.parentNode = null;
+ }
+};
+
+export function createMetanode(name: string, opt = {}): Metanode {
+ return new MetanodeImpl(name, opt);
+}
+
+/**
+ * Joins the information from the stats file (memory, compute time) with the graph
+ * information.
+ */
+export function joinStatsInfoWithGraph(graph: SlimGraph,
+ statsJson: TFStats): void {
+ _.each(statsJson.devStats, stats => {
+ _.each(stats.nodeStats, nodeStats => {
+ // Lookup the node in the graph by its original name, e.g. A. If not
+ // found, lookup by the rewritten name A/(A) in case the name is both
+ // a namespace and a node name.
+ let nodeName = nodeStats.nodeName in graph.nodes ?
+ nodeStats.nodeName :
+ nodeStats.nodeName + NAMESPACE_DELIM + "(" + nodeStats.nodeName + ")";
+ if (nodeName in graph.nodes) {
+ // Compute the total bytes used.
+ let totalBytes = 0;
+ if (nodeStats.memory) {
+ _.each(nodeStats.memory, alloc => {
+ if (alloc.totalBytes) {
+ totalBytes += Number(alloc.totalBytes);
+ }
+ });
+ }
+ let outputSize: number[][] = null;
+ if (nodeStats.output) {
+ outputSize = _.map(nodeStats.output, output => {
+ return _.map(output.tensorDescription.shape.dim,
+ dim => Number(dim.size));
+ });
+ }
+ graph.nodes[nodeName].stats = new NodeStats(totalBytes,
+ Number(nodeStats.allEndRelMicros), outputSize);
+ }
+ });
+ });
+}
+
+/**
+ * Execution stats for the node.
+ */
+class NodeStats {
+ constructor(totalBytes: number, totalMicros: number, outputSize: number[][]) {
+ this.totalBytes = totalBytes;
+ this.totalMicros = totalMicros;
+ this.outputSize = outputSize;
+ }
+
+ /**
+ * Total number of bytes used for the node. Sum of all childen
+ * if it is a Group node.
+ */
+ totalBytes: number;
+ /**
+ * Total number of compute time in microseconds used for the node.
+ * Sum of all children if it is a Group node.
+ */
+ totalMicros: number;
+ /**
+ * The shape of each output tensors, if there are any.
+ * Empty if it is a Group node.
+ */
+ outputSize: number[][];
+
+ /**
+ * Combines the specified stats with the current stats.
+ * Modifies the current object. This methos is used to
+ * compute aggregate stats for group nodes.
+ */
+ combine(stats: NodeStats): void {
+ if (stats.totalBytes != null) {
+ this.totalBytes += stats.totalBytes;
+ }
+ if (stats.totalMicros != null) {
+ this.totalMicros += stats.totalMicros;
+ }
+ }
+}
+
+class MetanodeImpl implements Metanode {
+ name: string;
+ stats: NodeStats;
+ type: NodeType;
+ depth: number;
+ isGroupNode: boolean;
+ cardinality: number;
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ bridgegraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ templateId: string;
+ opHistogram: {[op: string]: number};
+ deviceHistogram: {[op: string]: number};
+ parentNode: Node;
+ hasNonControlEdges: boolean;
+
+ /** A label object for meta-nodes in the graph hierarchy */
+ constructor(name: string, opt = {}) {
+ this.name = name;
+ this.type = NodeType.META;
+ /** number of levels under this group */
+ this.depth = 1;
+ this.isGroupNode = true;
+ /** # of leaf nodes (including embedded ones) */
+ this.cardinality = 0;
+ /** graph contains metanodes, nodes, edges
+ * and metaedges for main items within this metanode
+ */
+ this.metagraph =
+ createGraph<GroupNode|OpNode, Metaedge>(name, GraphType.META, opt);
+ /** bridgegraph must be constructed lazily-see hierarchy.getBridgegraph() */
+ this.bridgegraph = null;
+ /**
+ * A dictionary that count ops type of nodes in this metanode
+ * (op type => count).
+ */
+ this.opHistogram = {};
+ this.deviceHistogram = {};
+ /** unique id for a metanode of similar subgraph */
+ this.templateId = null;
+ /** Metanode which contains this node, if any */
+ this.parentNode = null;
+ this.stats = new NodeStats(0, 0, null);
+ this.hasNonControlEdges = false;
+ }
+
+ getFirstChild(): GroupNode|OpNode {
+ return this.metagraph.node(this.metagraph.nodes()[0]);
+ }
+
+ /**
+ * Returns the op node associated with the metanode.
+ * For example, if the metanode is "sgd", the associated
+ * op node is sgd/(sgd).
+ */
+ getRootOp(): OpNode {
+ let nameSplit = this.name.split("/");
+ let rootOpName = this.name + "/(" + nameSplit[nameSplit.length - 1] + ")";
+ return <OpNode>this.metagraph.node(rootOpName);
+ }
+
+ /**
+ * Return an array of the names of all the leaves (non-GroupNodes) inside
+ * this metanode. This performs a breadth-first search of the tree, so
+ * immediate child leaves will appear earlier in the output array than
+ * descendant leaves.
+ */
+ leaves(): string[] {
+ let leaves = [];
+ let queue = [<Node> this];
+ let metagraph; // Defined here due to a limitation of ES6->5 compilation.
+ while (queue.length) {
+ let node = queue.shift();
+ if (node.isGroupNode) {
+ metagraph = (<GroupNode> node).metagraph;
+ _.each(metagraph.nodes(), name => queue.push(metagraph.node(name)));
+ } else {
+ leaves.push(node.name);
+ }
+ }
+ return leaves;
+ }
+};
+
+export interface Metaedge extends graphlib.EdgeObject {
+
+ /**
+ * Stores the original BaseEdges represented by this Metaedge.
+ */
+ baseEdgeList: BaseEdge[];
+
+ /**
+ * Whether this edge represents a relationship that is inbound (or outbound)
+ * to the object which contains this information. For example, in a Metanode's
+ * bridgegraph, each edge connects an immediate child to something outside
+ * the Metanode. If the destination of the edge is inside the Metanode, then
+ * its inbound property should be true. If the destination is outside the
+ * Metanode, then its inbound property should be false.
+ *
+ * The property is optional because not all edges can be described as
+ * inbound/outbound. For example, in a Metanode's metagraph, all of the edges
+ * connect immediate children of the Metanode. None should have an inbound
+ * property, or they should be null/undefined.
+ */
+ inbound?: boolean;
+
+ /**
+ * Number of regular edges (not control dependency edges).
+ */
+ numRegularEdges: number;
+
+ /**
+ * Number of control dependency edges.
+ */
+ numControlEdges: number;
+
+ /**
+ * Number of reference edges, which is an edge to an operation
+ * that takes a reference to its input and changes its value.
+ */
+ numRefEdges: number;
+
+ addBaseEdge(edge: BaseEdge): void;
+}
+
+export function createMetaedge(v: string, w: string): Metaedge {
+ return new MetaedgeImpl(v, w);
+}
+
+/**
+ * A label object for edges between metanodes of subgraphs in the render graph.
+ */
+class MetaedgeImpl implements Metaedge {
+ v: string;
+ w: string;
+ baseEdgeList: BaseEdge[];
+ inbound: boolean;
+ numRegularEdges: number;
+ numControlEdges: number;
+ numRefEdges: number;
+
+ constructor(v: string, w: string) {
+ this.v = v;
+ this.w = w;
+ this.baseEdgeList = [];
+ this.inbound = null;
+ this.numRegularEdges = 0;
+ this.numControlEdges = 0;
+ this.numRefEdges = 0;
+ }
+
+ addBaseEdge(edge: BaseEdge): void {
+ this.baseEdgeList.push(edge);
+ if (edge.isControlDependency) {
+ this.numControlEdges += 1;
+ } else {
+ this.numRegularEdges += 1;
+ }
+ if (edge.isReferenceEdge) {
+ this.numRefEdges += 1;
+ }
+ }
+}
+
+export function createSeriesNode(prefix: string, suffix: string,
+ parent: string, clusterId: number, name: string): SeriesNode {
+ return new SeriesNodeImpl(prefix, suffix, parent, clusterId, name);
+}
+
+export function getSeriesNodeName(prefix: string, suffix: string,
+ parent: string, startId?: number, endId?: number): string {
+ let numRepresentation =
+ (typeof startId !== "undefined" && typeof endId !== "undefined") ?
+ "[" + startId + "-" + endId + "]" : "#";
+ let pattern = prefix + numRepresentation + suffix;
+ return (parent ? parent + "/" : "") + pattern;
+}
+
+class SeriesNodeImpl implements SeriesNode {
+ name: string;
+ type: NodeType;
+ stats: NodeStats;
+ hasLoop: boolean;
+ prefix: string;
+ suffix: string;
+ clusterId: number;
+ ids: number[];
+ parent: string;
+ isGroupNode: boolean;
+ cardinality: number;
+ metagraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ bridgegraph: graphlib.Graph<GroupNode|OpNode, Metaedge>;
+ parentNode: Node;
+ deviceHistogram: {[op: string]: number};
+ hasNonControlEdges: boolean;
+
+ constructor(prefix: string, suffix: string, parent: string,
+ clusterId: number, name: string) {
+ this.name = name || getSeriesNodeName(prefix, suffix, parent);
+ this.type = NodeType.SERIES;
+ this.hasLoop = false;
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.clusterId = clusterId;
+ this.ids = [];
+ this.parent = parent;
+ this.isGroupNode = true;
+ this.cardinality = 0;
+ this.metagraph = createGraph<Metanode, Metaedge>(name, GraphType.SERIES);
+ // bridgegraph must be constructed lazily-see hierarchy.getBridgegraph()
+ this.bridgegraph = null;
+ this.parentNode = null;
+ this.deviceHistogram = {};
+ this.hasNonControlEdges = false;
+ this.stats = new NodeStats(0, 0, null);
+ }
+}
+
+/**
+ * Normalizes the inputs and extracts associated metadata:
+ * 1) Inputs can contain a colon followed by a number at the end
+ * (e.g. inputName:1) and we remove this from the input name, and take note
+ * that the input was numbered.
+ * 2) Control dependency inputs contain caret at the beginning and we
+ * remove this and annotate the edge as a control dependency.
+ * @param inputs Array of unnormalized names of input nodes.
+ */
+function normalizeInputs(inputs: string[]): NormalizedInput[] {
+ return _.reduce(inputs, function(normalizedInputs, inputName) {
+ let start = inputName[0] === "^";
+ let colon = inputName.lastIndexOf(":");
+ let end = colon !== -1 &&
+ inputName.length - colon > 1 &&
+ !(/\D/).test(inputName.substring(colon + 1)) ?
+ colon : inputName.length;
+ let name = inputName.substring(start ? 1 : 0, end);
+ if (normalizedInputs.length === 0 ||
+ name !== normalizedInputs[normalizedInputs.length - 1].name) {
+ normalizedInputs.push({
+ name: name,
+ hasNumberPart: end !== inputName.length,
+ isControlDependency: start
+ });
+ }
+ return normalizedInputs;
+ }, []);
+}
+
+export function build(rawNodes: tf.TFNode[], params: BuildParams,
+ tracker: ProgressTracker): Promise<SlimGraph|void> {
+ /**
+ * A dictionary that maps each in-embedding node name to its host node label
+ * object.
+ */
+ let inEmbedding: {[nodeName: string]: OpNode} = {};
+ /**
+ * A dictionary that maps each node name to an array of the node's
+ * out-embedding node label objects.
+ */
+ let outEmbeddings: {[inputName: string]: OpNode[]} = {};
+ let isInEmbeddedPred = getEmbedPredicate(params.inEmbeddingTypes);
+ let isOutEmbeddedPred = getEmbedPredicate(params.outEmbeddingTypes);
+ let embeddingNodeNames: string[] = [];
+ /**
+ * A list of all the non-embedding node names which appear in the processed
+ * list of raw nodes. Here we pre-allocate enough room for all the rawNodes,
+ * even though there will some number of embeddings. The excess array length
+ * is spliced off later.
+ *
+ * Experimentation shows that around 30% of the array will go unused, and
+ * even for very large networks that amounts to less than 10k spaces.
+ */
+ let nodeNames = new Array<string>(rawNodes.length);
+
+ return runAsyncTask("Normalizing names", 30, () => {
+ let opNodes = new Array<OpNode>(rawNodes.length);
+ let index = 0;
+ _.each(rawNodes, rawNode => {
+ let normalizedInputs = normalizeInputs(rawNode.input);
+ let opNode = new OpNodeImpl(rawNode, normalizedInputs);
+ if (isInEmbeddedPred(opNode)) {
+ embeddingNodeNames.push(opNode.name);
+ inEmbedding[opNode.name] = opNode;
+ return;
+ }
+
+ if (isOutEmbeddedPred(opNode)) {
+ embeddingNodeNames.push(opNode.name);
+ _.each(opNode.inputs, input => {
+ let inputName = input.name;
+ outEmbeddings[inputName] = outEmbeddings[inputName] || [];
+ outEmbeddings[inputName].push(opNode);
+ });
+ return;
+ }
+ // The node is not an embedding, so add it to the names and nodes lists.
+ opNodes[index] = opNode;
+ nodeNames[index] = opNode.name;
+ index++;
+ });
+ opNodes.splice(index);
+ nodeNames.splice(index);
+ return opNodes;
+ }, tracker)
+ .then((opNodes) => {
+ // Create the graph data structure from the graphlib library.
+ return runAsyncTask("Building the data structure", 70, () => {
+ let normalizedNameDict = mapStrictHierarchy(nodeNames,
+ embeddingNodeNames);
+ let graph = new SlimGraph;
+
+ // Add the nodes to the graph.
+ _.each(opNodes, opNode => {
+ let normalizedName = normalizedNameDict[opNode.name] || opNode.name;
+ graph.nodes[normalizedName] = opNode;
+ // Check if the node has out-embeddings. If yes, add them to to the
+ // node.
+ if (opNode.name in outEmbeddings) {
+ opNode.outEmbeddings = outEmbeddings[opNode.name];
+ // Normalize the names of the out-embeddings.
+ _.each(opNode.outEmbeddings, node => {
+ node.name = normalizedNameDict[node.name] || node.name;
+ });
+ }
+ // Update the name of the node.
+ opNode.name = normalizedName;
+ });
+
+ // Visit each node's inputs to add the edges to the graph. If the input
+ // is an in-embedding, then add it to the node's in-embeddings instead.
+ _.each(opNodes, opNode => {
+ _.each(opNode.inputs, (input, i) => {
+ let inputName = input.name;
+ if (inputName in inEmbedding) {
+ opNode.inEmbeddings.push(inEmbedding[inputName]);
+ } else {
+ graph.edges.push({
+ v: normalizedNameDict[inputName] || inputName,
+ w: opNode.name,
+ isControlDependency: input.isControlDependency,
+ // Check if this op type and input number corresponds to a
+ // reference edge using the refEdges dictionary in the params.
+ isReferenceEdge: (params.refEdges[opNode.op + " " + i] === true)
+ });
+ }
+ });
+ });
+
+ // Normalize the names of in-embeddings.
+ _.each(inEmbedding, (node, name) => {
+ node.name = normalizedNameDict[node.name] || node.name;
+ });
+
+ return graph;
+ }, tracker);
+ })
+ .catch(function(reason) {
+ throw new Error("Failure creating graph");
+ });
+};
+
+/**
+ * Create a new graphlib.Graph() instance with default parameters
+ */
+export function createGraph<N, E>(name: string, type, opt = {}):
+ graphlib.Graph<N, E> {
+ let graph = new graphlib.Graph<N, E>(opt);
+ graph.setGraph({
+ name: name,
+ rankdir: "BT", // BT,TB,LR,RL
+ type: type
+ });
+ return graph;
+};
+
+/**
+ * Create a predicate for checking whether a node should be embedded based on
+ * the specified types.
+ */
+function getEmbedPredicate(types: string[]) {
+ return function(node) {
+ // check types
+ for (let i = 0; i < types.length; i++) {
+ let regExp = new RegExp(types[i]);
+ if (node.op.match(regExp)) { return true; }
+ }
+ return false;
+ };
+};
+
+/**
+ * Returns a strict node name (name => name/(name)) to avoid conflicts
+ * where the node name is also a namespace.
+ */
+function getStrictName(name: string): string {
+ let parts = name.split(NAMESPACE_DELIM);
+ return name + NAMESPACE_DELIM + "(" + parts[parts.length - 1] + ")";
+}
+
+/**
+ * For each op node (embedding or non-embedding), rename it if there is a
+ * non-embedding node under its namespace. For example, assume node name "A".
+ * If there is a non-embedding node under its namespace (e.g. "A/B"), "A" will
+ * be renamed to "A/(A)". Then the namespace "A" will contain 2 nodes: "(A)"
+ * and "B". If all the nodes under "A" are embedding nodes (e.g. constant and
+ * summary), keep "A" as an Op node and don't create a namespace.
+ *
+ * @param nodeNames An array of regular (non-embedding) node names.
+ * @param embeddingNodeNames An array of embedding node names.
+ * @return Dictionary object mapping names that need to be renamed to
+ * new names.
+ */
+function mapStrictHierarchy(nodeNames: string[],
+ embeddingNodeNames: string[]): {[oldName: string]: string} {
+ /** Dictionary that maps the old new to the new name */
+ let newNameDictionary: {[oldName: string]: string} = {};
+ /** Set used to store all namespaces. */
+ let namespaceSet: {[namespace: string]: boolean} = {};
+ // sort the nodes to make prefix check faster
+ nodeNames.sort();
+ // look for nodes with a prefix a,a/b -> a/(a),a/b
+ for (let i = 0; i < nodeNames.length - 1; ++i) {
+ let a = nodeNames[i];
+ // Get all the parent namespaces of the current node
+ // and add them in the namespace set.
+ _.each(getHierarchicalPath(a).slice(0, -1), ns => {
+ namespaceSet[ns] = true;
+ });
+ let b = nodeNames[i + 1];
+ if (_.startsWith(b, a + NAMESPACE_DELIM)) {
+ newNameDictionary[a] = getStrictName(a);
+ }
+ }
+ // Go through all the embedding node names and rename them in case they
+ // collide with namespaces.
+ _.each(embeddingNodeNames, embeddingName => {
+ if (embeddingName in namespaceSet) {
+ // Rename to follow strict hierarchy.
+ newNameDictionary[embeddingName] = getStrictName(embeddingName);
+ }
+ });
+ return newNameDictionary;
+};
+
+/**
+ * Returns a list of the degrees of each node in the graph.
+ */
+function degreeSequence(graph: graphlib.Graph<any, any>): number[] {
+ let degrees = graph.nodes().map(function(name) {
+ return graph.neighbors(name).length;
+ });
+ degrees.sort();
+ return degrees;
+};
+
+/**
+ * Returns if the degree sequence of the two graphs is the same.
+ */
+export function hasSimilarDegreeSequence(graph1: graphlib.Graph<any, any>,
+ graph2: graphlib.Graph<any, any>): boolean {
+ let dg1 = degreeSequence(graph1);
+ let dg2 = degreeSequence(graph2);
+
+ for (let i = 0; i < dg1.length; i++) {
+ if (dg1[i] !== dg2[i]) {
+ return false;
+ }
+ }
+ return true;
+};
+
+/**
+ * Returns the hierarchical path of the current node, based on the node's name.
+ * For example, if the name is 'a/b/c', the returned path is ['a', 'a/b', 'a/b/c'].
+ */
+export function getHierarchicalPath(name: string,
+ seriesNames?: { [name: string]: string }): string[] {
+ let path: string[] = [];
+ let i = name.indexOf(NAMESPACE_DELIM);
+ // Push all parent portions of the path.
+ while (i >= 0) {
+ path.push(name.substring(0, i));
+ i = name.indexOf(NAMESPACE_DELIM, i + 1);
+ }
+ // If the node's path is under a series, then add the series node name to the
+ // hierarchical path as the parent of the leaf.
+ if (seriesNames) {
+ let seriesName = seriesNames[name];
+ if (seriesName) {
+ path.push(seriesName);
+ }
+ }
+ // Push the leaf of the path.
+ path.push(name);
+ return path;
+};
+
+} // close module tf.graph