**** For now, call this UGRAPH since we have no other graph implementations. A basic undirected graph implemented with a hash table from nodes to neighbors. This implementation mainly specifies the access routines.

Flattened version is here

Ancestors
 \$UGRAPH{_} \$RO_UGRAPH{_} \$GRAPH{_,_} \$STR \$ELT{_} \$ELT UGRAPH_INCL{_} COMPARE{_}

Public

Features
 **** Add a node "n". _ Technical detail: Since the node index for "n" is the same as the node for this particular implementation, there is no need to return a value. Note that this function is not in the graph abstraction
 **** Replaces an existing node that is the same as "n" This function is guaranteed to return the same node, "n" though this is not true of graph implementations in general
 **** Add a new node and return the index
 **** Connect n1 and n2. Add the nodes if they do not exist
copy: SAME
 **** Create a new ugraph. Store "node_gen" as a generator of nodes, so that when "add_node: NTP" is called it can generate unique new nodes.
 **** All the data structures can be initialized with void
 **** Delete a node from the graph, and all its accompanying edges
 **** Deletes the edge between n1 and n2 if it exists
 **** Check whether the two graphs have the same nodes, edges in the same order
n_nodes: INT
new_edge(n1,n2: NTP): UEDGE{NTP}
 **** Permute the nodes of the graph so that they will be yielded in the order expressed by "new_positions"
 **** Reduce the graph to a canonical form based on the sorting order of nodes and edges
test_edge(s,t: NTP): BOOL
test_node(n: NTP): BOOL

Iters
 **** Return all edges in the graph. A problem, since we represent edges in both directions. We might want to either maintain a hash table of edges already seen or generate a matching or something of the sort. Or can use some arbitrary test to choose one or the other. such as lt For now, use a set which holds all nodes for which all edges have been yielded edges yielded
node!: NTP

Private