/** * Copyright (C) 2014-2016 Triumph LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; /** * Generic graph routines. * * @name graph * @namespace * @exports exports as graph */ b4w.module["__graph"] = function(exports, require) { var m_print = require("__print"); var m_util = require("__util"); var NULL_NODE = -1; var NULL = 0; var FORWARD_DIR = 10; var BACKWARD_DIR = 20; var TWO_WAY = 30; var _next_pair_cache = [NULL_NODE, NULL_NODE]; exports.NULL_NODE = NULL_NODE; exports.FORWARD_DIR = FORWARD_DIR; exports.BACKWARD_DIR = BACKWARD_DIR; exports.TWO_WAY = TWO_WAY; /** * Create graph using constructor pattern. * @param node_or_edge1 Node [ID, ATTR] or Edge [ID1, ID2, ATTR] * @param node_or_edge2 ... */ exports.create = function() { var node_edge_arr = arguments; var nodes = []; var edges = []; for (var i = 0; i < node_edge_arr.length; i++) { var node_edge = node_edge_arr[i]; switch(node_edge.length) { case 2: // node index, edge attribute nodes.push(node_edge[0], node_edge[1]); break; case 3: // node index 1, node index 2, edge attrubute edges.push(node_edge[0], node_edge[1], node_edge[2]); break; default: m_util.panic("Wrong graph constructor params"); break; } } var graph = { nodes: nodes, edges: edges }; return graph; } exports.clone = function(graph, nodes_cb, edges_cb) { if (nodes_cb) { var nodes = new Array(graph.nodes.length); for (var i = 0; i < graph.nodes.length; i+=2) { nodes[i] = graph.nodes[i]; nodes[i+1] = nodes_cb(graph.nodes[i+1]); } } else var nodes = m_util.clone_object_r(graph.nodes); if (edges_cb) { var edges = new Array(graph.edges.length); for (var i = 0; i < graph.edges.length; i+=3) { edges[i] = graph.edges[i]; edges[i+1] = graph.edges[i+1]; edges[i+2] = edges_cb(graph.edges[i+2]); } } else var edges = m_util.clone_object_r(graph.edges); var graph = { nodes: nodes, edges: edges }; return graph; } /** * Create graph using separate node and edge arrays. */ exports.create_node_edge_arr = function(nodes_arr, edges_arr) { var nodes = []; var edges = []; for (var i = 0; i < nodes_arr.length; i++) nodes.push(nodes_arr[i][0], nodes_arr[i][1]); for (var i = 0; i < edges_arr.length; i++) edges.push(edges_arr[i][0], edges_arr[i][1], edges_arr[i][2]); var graph = { nodes: nodes, edges: edges }; return graph; } exports.append_node = append_node; function append_node(graph, id, attr) { if (!attr) attr = null; if (has_node(graph, id)) m_util.panic("Graph already has node with given ID"); else graph.nodes.push(id, attr); } exports.has_node = has_node; function has_node(graph, id) { var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) if (nodes[i] == id) return true; return false; } exports.append_edge = append_edge; /** * NOTE: check multiple edges case */ function append_edge(graph, id1, id2, attr) { if (!attr) attr = null; if (!has_node(graph, id1) || !has_node(graph, id2)) m_util.panic("Wrong node IDs"); else graph.edges.push(id1, id2, attr); } exports.remove_node = remove_node; function remove_node(graph, id) { if (!has_node(graph, id)) m_util.panic("Node not found"); var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) { if (nodes[i] == id) { nodes.splice(i, 2); i-=2; } } } exports.remove_edge = remove_edge; function remove_edge(graph, id1, id2, edge_num) { if (!has_edge(graph, id1, id2)) m_util.panic("Edge not found"); var edges = graph.edges; var count = 0; for (var i = 0; i < edges.length; i+=3) { if (edges[i] == id1 && edges[i+1] == id2) { if (edge_num == -1) edges.splice(i, 3); else { if (edge_num == count) { edges.splice(i, 3); break; } count++; } i-=3; } } } exports.remove_edge_by_attr = remove_edge_by_attr; function remove_edge_by_attr(graph, id1, id2, attr) { if (!has_edge(graph, id1, id2)) m_util.panic("Edge not found"); var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) { if (edges[i] == id1 && edges[i+1] == id2 && edges[i+2][0] == attr[0] && edges[i+2][1] == attr[1]) { edges.splice(i, 3); break; } } } /** * Append node by attribute. * Perform attribute uniqueness test and append newly allocated node to graph * with that unique attribute. * @returns New node ID */ exports.append_node_attr = function(graph, attr) { if (node_by_attr(graph, attr) == NULL_NODE) { var node_id = gen_node_id(graph); append_node(graph, node_id, attr); return node_id; } else m_util.panic("Non-unique attribute"); } /** * For edges connecting two node IDs with given attribute replace it by the new one. */ exports.replace_edge_attr = function(graph, id1, id2, attr_old, attr_new) { var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) if (edges[i] == id1 && edges[i+1] == id2 && edges[i+2] == attr_old) edges[i+2] = attr_new; } /** * Append the subgraph to the given graph. * @param {Graph} subgraph Subgraph to append * @param {Graph} graph Graph to append to * @param {Edge[]} subgraph_graph_edges subgraph->graph inter-graph edges * @param {Edge[]} graph_subgraph_edges graph->subgraph inter-graph edges */ exports.append_subgraph = function(subgraph, graph, subgraph_graph_edges, graph_subgraph_edges) { subgraph_graph_edges = subgraph_graph_edges || []; graph_subgraph_edges = graph_subgraph_edges || []; var ids_new = {}; for (var i = 0; i < subgraph.nodes.length; i+=2) { var id_sub = subgraph.nodes[i]; var attr = subgraph.nodes[i+1]; // subgraph new node id (inside graph) var id_sub_new = gen_node_id(graph); append_node(graph, id_sub_new, attr); ids_new[id_sub] = id_sub_new; } for (var i = 0; i < subgraph.edges.length; i+=3) { var id1_sub_new = ids_new[subgraph.edges[i]]; var id2_sub_new = ids_new[subgraph.edges[i+1]]; var attr_edge = subgraph.edges[i+2]; append_edge(graph, id1_sub_new, id2_sub_new, attr_edge); } for (var i = 0; i < subgraph_graph_edges.length; i+=3) { var id1_sub_new = ids_new[subgraph_graph_edges[i]]; var id2 = subgraph_graph_edges[i+1]; var attr_edge = subgraph_graph_edges[i+2]; append_edge(graph, id1_sub_new, id2, attr_edge); } for (var i = 0; i < graph_subgraph_edges.length; i+=3) { var id1 = graph_subgraph_edges[i]; var id2_sub_new = ids_new[graph_subgraph_edges[i+1]]; var attr_edge = graph_subgraph_edges[i+2]; append_edge(graph, id1, id2_sub_new, attr_edge); } } exports.gen_node_id = gen_node_id; function gen_node_id(graph) { var nodes = graph.nodes; var counter = -1; for (var i = 0; i < nodes.length; i+=2) counter = Math.max(counter, nodes[i]); return (++counter); } exports.node_by_attr = node_by_attr; /** * Find first node by attribute. */ function node_by_attr(graph, attr) { var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) if (nodes[i+1] == attr) return nodes[i]; // not found return NULL_NODE; } /** * Append new edge by two node attributes. * All node attributes must be unique, because the edge is appended only ones. */ exports.append_edge_attr = function(graph, attr_node1, attr_node2, attr_edge) { var id1 = node_by_attr(graph, attr_node1); var id2 = node_by_attr(graph, attr_node2); if (id1 != NULL_NODE && id2 != NULL_NODE) append_edge(graph, id1, id2, attr_edge); else m_util.panic("Attributes not found"); } /** * Traverse graph and exec callback per node. * return any positive value from callback to interrupt traversal * do not try to modify graph structure in callback */ exports.traverse = function(graph, callback) { var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) if (callback(nodes[i], nodes[i+1])) break; } /** * Traverse graph and exec callback per edge. * return any positive value from callback to interrupt traversal * do not try to modify graph structure in callback */ exports.traverse_edges = function(graph, callback) { var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) if (callback(edges[i], edges[i+1], edges[i+2])) break; } /** * Traverse node inputs and exec callback per input. * return any positive value from callback to interrupt traversal * do not try to modify graph structure in callback */ exports.traverse_inputs = function(graph, node, callback) { var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) if (edges[i+1] == node) { var node_in = edges[i]; if (callback(node_in, get_node_attr(graph, node_in), edges[i+2])) return; } } /** * Traverse node outputs and exec callback per output. * return any positive value from callback to interrupt traversal * do not try to modify graph structure in callback */ exports.traverse_outputs = function(graph, node, callback) { var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) if (edges[i] == node) { var node_out = edges[i+1]; if (callback(node_out, get_node_attr(graph, node_out), edges[i+2])) return; } } exports.topsort = topsort; /** * Topological sorting based on depth-first search algorithm. * @param graph Graph * @returns New graph with sorted nodes */ function topsort(graph) { var new_nodes = []; var visit_state = {}; set_unvisited(graph, visit_state); var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) if (in_edge_count(graph, nodes[i]) == 0) topsort_iter(graph, nodes[i], visit_state, new_nodes); var new_graph = { nodes: new_nodes, edges: graph.edges.slice(0) }; return new_graph; } function set_unvisited(graph, visit_state) { var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) visit_state[nodes[i]] = false; } /** * topsort visit function */ function topsort_iter(graph, node_id, visit_state, new_nodes) { if (!visit_state[node_id]) { visit_state[node_id] = true; for (var i = 0; i < out_edge_count(graph, node_id); i++) { var other = get_out_edge(graph, node_id, i); topsort_iter(graph, other, visit_state, new_nodes); } new_nodes.unshift(node_id, get_node_attr(graph, node_id)); } } /** * Topological sorting based on depth-first search algorithm. * @param graph Graph * @returns {Array} Array of node attributes */ exports.topsort_attr = function(graph) { var nodes = topsort(graph).nodes; var result = []; for (var i = 0; i < nodes.length; i+=2) result.push(nodes[i+1]); return result; } exports.is_connected = function(graph) { // TODO: implement when needed } /** * Compose a new subgraph with the nodes connected to a given node. */ exports.subgraph_node_conn = function(graph, node_id, dir) { if (!has_node(graph, node_id)) m_util.panic("No such node"); var visit_state = {}; set_unvisited(graph, visit_state); subgraph_node_conn_iter(graph, node_id, visit_state, dir); var new_nodes = []; for (var id in visit_state) { if (visit_state[id]) { // String->Number conversion var id = Number(id); new_nodes.push(id, get_node_attr(graph, id)); } } var new_graph = { nodes: new_nodes, edges: graph.edges.slice(0) }; cleanup_loose_edges(new_graph); return new_graph; } function subgraph_node_conn_iter(graph, node_id, visit_state, dir) { if (!visit_state[node_id]) { visit_state[node_id] = true; if (dir == FORWARD_DIR || dir == TWO_WAY) { for (var i = 0; i < out_edge_count(graph, node_id); i++) { var other = get_out_edge(graph, node_id, i); subgraph_node_conn_iter(graph, other, visit_state, dir); } } if (dir == BACKWARD_DIR || dir == TWO_WAY) { for (var i = 0; i < in_edge_count(graph, node_id); i++) { var other = get_in_edge(graph, node_id, i); subgraph_node_conn_iter(graph, other, visit_state, dir); } } } } exports.cleanup_loose_edges = cleanup_loose_edges; function cleanup_loose_edges(graph) { var nodes = graph.nodes; var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) { var node1 = edges[i]; var node2 = edges[i+1]; // remove half-edges too if (!has_node(graph, node1) || !has_node(graph, node2)) { edges.splice(i, 3); i-=3; } } } /** * Find array of nodes with 0 in-degree. */ exports.get_source_nodes = function(graph) { var result = []; var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) { var node = nodes[i]; if (!in_edge_count(graph, node)) result.push(node); } return result; } /** * Find array of nodes with 0 out-degree. */ exports.get_sink_nodes = get_sink_nodes; function get_sink_nodes(graph) { var result = []; var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) { var node = nodes[i]; if (!out_edge_count(graph, node)) result.push(node); } return result; } /** * Search for graph2 subgraph isomorphic to graph1 using VF2 algorithm. * graph1 <= graph2 * @see VFLIB implementation * @param graph1 Graph 1 (small one) * @param graph2 Graph 2 (big one) * @param [node_comp] Node attribute comparator function * @param [edge_comp] Edge attribute comparator function * @returns Pair [nodes_in_1, nodes_in_2] or null */ exports.match = function(graph1, graph2, node_comp, edge_comp) { var state = {}; // NOTE: current VFLIB implementation require strict node order: // ID1, ID2, ID3 -> 0, 1, 2 state.g1 = gen_ordered_graph(graph1); state.g2 = gen_ordered_graph(graph2); state.node_comp = node_comp || function(attr1, attr2) { return (attr1 == attr2); } state.edge_comp = edge_comp || function(attr1, attr2) { return (attr1 == attr2); } var n1 = node_count(graph1); var n2 = node_count(graph2); state.n1 = n1; state.n2 = n2; // NOTE: for compatibility state.order = NULL; state.core_len = state.orig_core_len = 0; state.t1both_len = state.t1in_len = state.t1out_len = 0; state.t2both_len = state.t2in_len = state.t2out_len = 0; state.added_node1 = NULL_NODE; state.core_1 = Array(n1); state.core_2 = Array(n2); state.in_1 = new Array(n1); state.in_2 = new Array(n2); state.out_1 = new Array(n1); state.out_2 = new Array(n2); // NOTE: simulate *var = 1 pattern state.share_count = [1]; for (var i = 0; i < n1; i++) { state.core_1[i] = NULL_NODE; state.in_1[i] = 0; state.out_1[i] = 0; } for (var i = 0; i < n2; i++) { state.core_2[i] = NULL_NODE; state.in_2[i] = 0; state.out_2[i] = 0; } var c1 = new Array(n1); var c2 = new Array(n1); var res = match_iter(c1, c2, state); if (res) { // calculate original node IDs for (var i = 0; i < c1.length; i++) { c1[i] = graph1.nodes[2*c1[i]]; c2[i] = graph2.nodes[2*c2[i]]; } return [c1, c2]; } else return null; } function node_count(graph) { return graph.nodes.length / 2; } /** * Place node IDs in strict succession, so graph.nodes[2*i] == i */ function gen_ordered_graph(graph) { var nodes = graph.nodes; var edges = graph.edges; var new_nodes = []; var new_edges = []; // old graph node ID -> new graph node ID var map = []; for (var i = 0; i < nodes.length; i++) { map[nodes[2*i]] = i; new_nodes.push(i, nodes[2*i+1]); } for (var i = 0; i < edges.length; i+=3) { new_edges.push(map[edges[i]], map[edges[i+1]], edges[i+2]); } var new_graph = { nodes: new_nodes, edges: new_edges } return new_graph; } function match_iter(c1, c2, state) { if (state_is_goal(state)) { state_get_core_set(state, c1, c2); return true; } if (state_is_dead(state)) return false; var n1 = NULL_NODE; var n2 = NULL_NODE; var found = false; while (!found && state_next_pair(state, _next_pair_cache, n1, n2)) { var n1 = _next_pair_cache[0]; var n2 = _next_pair_cache[1]; if (state_is_feasible_pair(state, n1, n2)) { var new_state = state_clone(state); state_add_pair(new_state, n1, n2); found = match_iter(c1, c2, new_state); state_back_track(new_state); } } return found; } function state_is_goal(state) { return (state.core_len == state.n1); } function state_core_len(state) { return state.core_len; } function state_get_core_set(state, c1, c2) { for (var i = 0, j = 0; i < state.n1; i++) if (state.core_1[i] != NULL_NODE) { c1[j] = i; c2[j] = state.core_1[i]; j++; } } function state_is_dead(state) { return (state.n1 > state.n2 || state.t1both_len > state.t2both_len || state.t1out_len > state.t2out_len || state.t1in_len > state.t2in_len); } function state_next_pair(state, next_pair, prev_n1, prev_n2) { if (prev_n1 == NULL_NODE) prev_n1 = 0; if (prev_n2 == NULL_NODE) prev_n2 = 0; else prev_n2++; var t1both_len = state.t1both_len; var t2both_len = state.t2both_len; var t1out_len = state.t1out_len; var t2out_len = state.t2out_len; var t1in_len = state.t1in_len; var t2in_len = state.t2in_len; var core_len = state.core_len; var n1 = state.n1; var n2 = state.n2; var core_1 = state.core_1; var core_2 = state.core_2; var in_1 = state.in_1; var in_2 = state.in_2; var out_1 = state.out_1; var out_2 = state.out_2; if (t1both_len > core_len && t2both_len > core_len) { while (prev_n1 < n1 && (core_1[prev_n1] != NULL_NODE || out_1[prev_n1] == 0 || in_1[prev_n1] == 0)) { prev_n1++; prev_n2 = 0; } } else if (t1out_len > core_len && t2out_len > core_len) { while (prev_n1 < n1 && (core_1[prev_n1] != NULL_NODE || out_1[prev_n1] == 0)) { prev_n1++; prev_n2 = 0; } } else if (t1in_len > core_len && t2in_len > core_len) { while (prev_n1 < n1 && (core_1[prev_n1] != NULL_NODE || in_1[prev_n1] == 0)) { prev_n1++; prev_n2 = 0; } // NOTE: order is not supported } else if (prev_n1 == 0 && state.order != NULL) { var i = 0; while (i < n1 && core_1[prev_n1 = state.order[i]] != NULL_NODE) i++; if (i == n1) prev_n1 = n1; } else { while (prev_n1 < n1 && core_1[prev_n1] != NULL_NODE) { prev_n1++; prev_n2 = 0; } } if (t1both_len > core_len && t2both_len > core_len) { while (prev_n2 < n2 && (core_2[prev_n2] != NULL_NODE || out_2[prev_n2] == 0 || in_2[prev_n2] == 0)) { prev_n2++; } } else if (t1out_len > core_len && t2out_len > core_len) { while (prev_n2 < n2 && (core_2[prev_n2] != NULL_NODE || out_2[prev_n2] == 0)) { prev_n2++; } } else if (t1in_len > core_len && t2in_len > core_len) { while (prev_n2 < n2 && (core_2[prev_n2] != NULL_NODE || in_2[prev_n2] == 0)) { prev_n2++; } } else { while (prev_n2 < n2 && core_2[prev_n2] != NULL_NODE) { prev_n2++; } } if (prev_n1 < n1 && prev_n2 < n2) { // *pn1, *pn2 next_pair[0] = prev_n1; next_pair[1] = prev_n2; return true; } return false; } function state_is_feasible_pair(state, node1, node2) { var g1 = state.g1; var g2 = state.g2; var n1 = state.n1; var n2 = state.n2; var core_1 = state.core_1; var core_2 = state.core_2; var in_1 = state.in_1; var in_2 = state.in_2; var out_1 = state.out_1; var out_2 = state.out_2; assert(node1 < n1); assert(node2 < n2); assert(core_1[node1] == NULL_NODE); assert(core_2[node2] == NULL_NODE); if (!compatible_node(state.node_comp, g1, node1, g2, node2)) return false; var termout1=0, termout2=0, termin1=0, termin2=0, new1=0, new2=0; // Check the 'out' edges of node1 for (var i = 0; i < out_edge_count(g1, node1); i++) { var other1 = get_out_edge(g1, node1, i); if (core_1[other1] != NULL_NODE) { var other2 = core_1[other1]; if (!has_edge(g2, node2, other2) || !compatible_edge(state.edge_comp, g1, node1, other1, g2, node2, other2)) return false; } else { if (in_1[other1]) termin1++; if (out_1[other1]) termout1++; if (!in_1[other1] && !out_1[other1]) new1++; } } // Check the 'in' edges of node1 for (var i = 0; i < in_edge_count(g1, node1); i++) { var other1 = get_in_edge(g1, node1, i); if (core_1[other1] != NULL_NODE) { var other2 = core_1[other1]; if (!has_edge(g2, other2, node2) || !compatible_edge(state.edge_comp, g1, other1, node1, g2, other2, node2)) return false; } else { if (in_1[other1]) termin1++; if (out_1[other1]) termout1++; if (!in_1[other1] && !out_1[other1]) new1++; } } // Check the 'out' edges of node2 for (var i = 0; i < out_edge_count(g2, node2); i++) { var other2 = get_out_edge(g2, node2, i); if (core_2[other2] != NULL_NODE) { var other1 = core_2[other2]; if (!has_edge(g1, node1, other1)) return false; } else { if (in_2[other2]) termin2++; if (out_2[other2]) termout2++; if (!in_2[other2] && !out_2[other2]) new2++; } } // Check the 'in' edges of node2 for (var i = 0; i < in_edge_count(g2, node2); i++) { var other2 = get_in_edge(g2, node2, i); if (core_2[other2] != NULL_NODE) { var other1 = core_2[other2]; if (!has_edge(g1, other1, node1)) return false; } else { if (in_2[other2]) termin2++; if (out_2[other2]) termout2++; if (!in_2[other2] && !out_2[other2]) new2++; } } return (termin1<=termin2 && termout1<=termout2 && new1<=new2); } // NOTE: temporary debug solution function assert(expr) { if (!expr) m_util.panic("Assertion failed"); } /** * Compare node attributes */ function compatible_node(node_comp, graph1, node1, graph2, node2) { if (node_comp(get_node_attr(graph1, node1), get_node_attr(graph2, node2))) return true; else return false; } exports.get_node_id = function(graph, attr) { var nodes = graph.nodes; for (var i = 1; i < nodes.length; i+=2) { if (nodes[i] == attr) return nodes[i-1]; } return null; } exports.get_node_attr = get_node_attr; function get_node_attr(graph, node) { var nodes = graph.nodes; for (var i = 0; i < nodes.length; i+=2) { if (nodes[i] == node) return nodes[i+1]; } return null; } exports.out_edge_count = out_edge_count; function out_edge_count(graph, node) { var edges = graph.edges; var count = 0; for (var i = 0; i < edges.length; i+=3) if (edges[i] == node) count++; return count; } exports.in_edge_count = in_edge_count; function in_edge_count(graph, node) { var edges = graph.edges; var count = 0; for (var i = 0; i < edges.length; i+=3) if (edges[i+1] == node) count++; return count; } exports.get_out_edge = get_out_edge; function get_out_edge(graph, node, num) { var edges = graph.edges; var count = 0; for (var i = 0; i < edges.length; i+=3) { if (edges[i] == node) { if (count == num) return edges[i+1]; count++; } } return NULL_NODE; } exports.get_in_edge = get_in_edge; function get_in_edge(graph, node, num) { var edges = graph.edges; var count = 0; for (var i = 0; i < edges.length; i+=3) { if (edges[i+1] == node) { if (count == num) return edges[i]; count++; } } return NULL_NODE; } function has_edge(graph, node1, node2) { var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) if (edges[i] == node1 && edges[i+1] == node2) return true; return false; } function compatible_edge(edge_comp, graph1, node11, node12, graph2, node21, node22) { var graph1_edge_count = get_edge_count(graph1, node11, node12); var graph2_edge_count = get_edge_count(graph2, node21, node22); // NOTE: for each edge in graph1 find compatible in graph2 for (var i = 0; i < graph1_edge_count; i++) { var edge_match = false; for (var j = 0; j < graph2_edge_count; j++) { if (edge_comp(get_edge_attr(graph1, node11, node12, i), get_edge_attr(graph2, node21, node22, j))) { edge_match = true; break; } } if (!edge_match) return false; } return true; } exports.get_edge_count = get_edge_count; function get_edge_count(graph, node1, node2) { var count = 0; var edges = graph.edges; for (var i = 0; i < edges.length; i+=3) { if (edges[i] == node1 && edges[i+1] == node2) count++; } return count; } exports.get_edge_attr = get_edge_attr; function get_edge_attr(graph, node1, node2, num) { var edges = graph.edges; var count = 0; for (var i = 0; i < edges.length; i+=3) { if (edges[i] == node1 && edges[i+1] == node2) { if (count == num) return edges[i+2]; count++; } } return null; } function state_add_pair(state, node1, node2) { var g1 = state.g1; var g2 = state.g2; var n1 = state.n1; var n2 = state.n2; var core_1 = state.core_1; var core_2 = state.core_2; var in_1 = state.in_1; var in_2 = state.in_2; var out_1 = state.out_1; var out_2 = state.out_2; assert(node1 " + String(node2); if (edge_label_cb) dot_str += " [label=\"" + edge_label_cb(node1, node2, attr) + "\"]"; dot_str += ";\n"; } dot_str += "}"; return dot_str; } }