Repository: networkx/networkx

Issue:
label in gml output should be quoted
The output of the `write_gml` function currently (1.11) does not quote the label values, but I do think it should according to the [GML spec.](http://www.fim.uni-passau.de/fileadmin/files/lehrstuhl/brandenburg/projekte/gml/gml-technical-report.pdf). Because of this, tools like Cytoscape, jhive and gml2gv fails to read the gml file.

A node from my output:
```
  node [
    id 0
    label 1203
  ]
```
that should be 

```
  node [
    id 0
    label "1203"
  ]
```

Discussion:
dschult: It seems to work in Python3.6   I get the error only for python2.7 though I haven't tested in all the versions. I'm assuming it is a python2 vs python3 issue. 

In the test you are processing all strings through ```literal_stringizer``` instead of letting str and unicode be unprocessed. I guess this fixes the int node name ```0``` to ```"0"``` but now the test fails on the attribute 'demo' having a unicode value because ```literal_stringizer``` in python2.7 doesn't convert the unicode symbol to HTML correctly.  In the old tests, strings were not processed by ```literal_stringizer```. So this wasn't a problem.

We might need a separate section just for labels.  Instead of calling literal_stringizer on all strings AND on labels, you would just call it on the labels. I think that will make this test work but is kind of a hack because its not clear how to handle labels that are unicode strings. Perhaps just send them through without processing and apply ```literal_stringizer``` to the numeric types.  You could hardcode the stringizer to use or make literal_stringizer the default.

Perhaps we should add a test where a node is a unicode string.  

Does this make any sense?

Patch:
diff --git a/CONTRIBUTE.rst b/CONTRIBUTE.rst
new file mode 100644
index 000000000..b8ef591d0
--- /dev/null
+++ b/CONTRIBUTE.rst
@@ -0,0 +1,161 @@
+Development process
+-------------------
+
+1. If you are a first-time contributor:
+
+   * Go to `https://github.com/networkx/networkx
+     <https://github.com/networkx/networkx>`_ and click the
+     "fork" button to create your own copy of the project.
+
+   * Clone the project to your local computer::
+
+      git clone git@github.com:your-username/networkx.git
+
+   * Add the upstream repository::
+
+      git remote add upstream git@github.com:networkx/networkx.git
+
+   * Now, you have remote repositories named:
+
+      - ``upstream``, which refers to the ``networkx`` repository
+      - ``origin``, which refers to your personal fork
+
+2. Develop your contribution:
+
+   * Pull the latest changes from upstream::
+
+      git checkout master
+      git pull upstream master
+
+   * Create a branch for the feature you want to work on. Since the
+     branch name will appear in the merge message, use a sensible name
+     such as 'bugfix-for-issue-1480'::
+
+      git checkout -b bugfix-for-issue-1480
+
+   * Commit locally as you progress (``git add`` and ``git commit``)
+
+3. To submit your contribution:
+
+   * Push your changes back to your fork on GitHub::
+
+      git push origin bugfix-for-issue-1480
+
+   * Go to GitHub. The new branch will show up with a green Pull Request
+     button---click it.
+
+   * If you want, post on the `mailing list
+     <http://groups.google.com/group/networkx-discuss>`_ to explain your changes or
+     to ask for review.
+
+For a more detailed discussion, read these :doc:`detailed documents
+<gitwash/index>` on how to use Git with ``networkx``
+(`<https://networkx.readthedocs.io/en/stable/developer/gitwash/index.html>`_).
+
+4. Review process:
+
+    * Reviewers (the other developers and interested community members) will
+      write inline and/or general comments on your Pull Request (PR) to help
+      you improve its implementation, documentation, and style.  Every single
+      developer working on the project has their code reviewed, and we've come
+      to see it as friendly conversation from which we all learn and the
+      overall code quality benefits.  Therefore, please don't let the review
+      discourage you from contributing: its only aim is to improve the quality
+      of project, not to criticize (we are, after all, very grateful for the
+      time you're donating!).
+
+    * To update your pull request, make your changes on your local repository
+      and commit. As soon as those changes are pushed up (to the same branch as
+      before) the pull request will update automatically.
+
+    * `Travis-CI <http://travis-ci.org/>`__, a continuous integration service,
+      is triggered after each Pull Request update to build the code and run unit
+      tests of your branch. The Travis tests must pass before your PR can be merged.
+      If Travis fails, you can find out why by clicking on the "failed" icon (red
+      cross) and inspecting the build and test log.
+
+    * `AppVeyor <http://ci.appveyor.com>`__, is another continuous integration
+      service, which we use.  You will also need to make sure that the AppVeyor
+      tests pass.
+
+.. note::
+
+   If closing a bug, also add "Fixes #1480" where 1480 is the issue number.
+
+Divergence between ``upstream master`` and your feature branch
+--------------------------------------------------------------
+
+Never merge the main branch into yours. If GitHub indicates that the
+branch of your Pull Request can no longer be merged automatically, rebase
+onto master::
+
+   git checkout master
+   git pull upstream master
+   git checkout bugfix-for-issue-1480
+   git rebase master
+
+If any conflicts occur, fix the according files and continue::
+
+   git add conflict-file1 conflict-file2
+   git rebase --continue
+
+However, you should only rebase your own branches and must generally not
+rebase any branch which you collaborate on with someone else.
+
+Finally, you must push your rebased branch::
+
+   git push --force origin bugfix-for-issue-1480
+
+(If you are curious, here's a further discussion on the
+`dangers of rebasing <http://tinyurl.com/lll385>`__.
+Also see this `LWN article <http://tinyurl.com/nqcbkj>`__.)
+
+Guidelines
+----------
+
+* All code should have tests.
+* All code should be documented, to the same
+  `standard <http://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#docstring-standard>`__
+  as NumPy and SciPy.
+* No changes are committed without review.  Ask on the
+  `mailing list <http://groups.google.com/group/networkx-discuss>`_ if
+  you get no response to your pull request.
+
+Stylistic Guidelines
+--------------------
+
+* Set up your editor to remove trailing whitespace.  Follow `PEP08
+  <www.python.org/dev/peps/pep-0008/>`__.  Check code with pyflakes / flake8.
+
+* Use the following import conventions::
+
+   import numpy as np
+   import scipy as sp
+   import matplotlib as mpl
+   import matplotlib.pyplot as plt
+   import networkx as nx
+
+   cimport numpy as cnp # in Cython code
+
+Pull request codes
+------------------
+
+When you submit a pull request to github, github will ask you for a summary.  If
+your code is not ready to merge, but you want to get feedback, please consider
+using ``WIP: experimental optimization`` or similar for the title of your pull
+request. That way we will all know that it's not yet ready to merge and that
+you may be interested in more fundamental comments about design.
+
+When you think the pull request is ready to merge, change the title (using the
+*Edit* button) to remove the ``WIP:``.
+
+Developer Notes
+---------------
+
+For additional information about contributing to NetworkX, please see
+the `Developer Notes <https://github.com/networkx/networkx/wiki>`_.
+
+Bugs
+----
+
+Please `report bugs on GitHub <https://github.com/networkx/networkx/issues>`_.
diff --git a/INSTALL.txt b/INSTALL.txt
index 0df6ae25f..bbfc5c632 100644
--- a/INSTALL.txt
+++ b/INSTALL.txt
@@ -1,3 +1,3 @@
 See doc/source/install.rst
 or
-http://networkx.github.io/documentation/latest/install.html
+http://networkx.readthedocs.io/en/stable/install.html
diff --git a/appveyor.yml b/appveyor.yml
index f53602bd5..cc5ffc826 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -11,11 +11,11 @@ environment:
 
   matrix:
     - PYTHON: "C:\\Python27"
-      PYTHON_VERSION: "2.7.9"
+      PYTHON_VERSION: "2.7.13"
       PYTHON_ARCH: "32"
 
     - PYTHON: "C:\\Python27-x64"
-      PYTHON_VERSION: "2.7.9"
+      PYTHON_VERSION: "2.7.13"
       PYTHON_ARCH: "64"
 
     - PYTHON: "C:\\Python33"
@@ -27,19 +27,27 @@ environment:
       PYTHON_ARCH: "64"
 
     - PYTHON: "C:\\Python34"
-      PYTHON_VERSION: "3.4.3"
+      PYTHON_VERSION: "3.4.4"
       PYTHON_ARCH: "32"
 
     - PYTHON: "C:\\Python34-x64"
-      PYTHON_VERSION: "3.4.3"
+      PYTHON_VERSION: "3.4.4"
       PYTHON_ARCH: "64"
 
     - PYTHON: "C:\\Python35"
-      PYTHON_VERSION: "3.5.0"
+      PYTHON_VERSION: "3.5.3"
       PYTHON_ARCH: "32"
 
     - PYTHON: "C:\\Python35-x64"
-      PYTHON_VERSION: "3.5.0"
+      PYTHON_VERSION: "3.5.3"
+      PYTHON_ARCH: "64"
+
+    - PYTHON: "C:\\Python36"
+      PYTHON_VERSION: "3.6.1"
+      PYTHON_ARCH: "32"
+
+    - PYTHON: "C:\\Python36-x64"
+      PYTHON_VERSION: "3.6.1"
       PYTHON_ARCH: "64"
 
 install:
diff --git a/doc/source/developer/contribute.rst b/doc/source/developer/contribute.rst
new file mode 100644
index 000000000..eccd2e4cc
--- /dev/null
+++ b/doc/source/developer/contribute.rst
@@ -0,0 +1,1 @@
+.. include:: ../../../CONTRIBUTE.rst
diff --git a/doc/source/developer/index.rst b/doc/source/developer/index.rst
index 515d32bcb..040b5a1c5 100644
--- a/doc/source/developer/index.rst
+++ b/doc/source/developer/index.rst
@@ -6,4 +6,5 @@ Developer Guide
 .. toctree::
    :maxdepth: 2
 
+   contribute
    gitwash/index
diff --git a/doc/source/reference/generators.rst b/doc/source/reference/generators.rst
index 2498ce10d..41e93ebce 100644
--- a/doc/source/reference/generators.rst
+++ b/doc/source/reference/generators.rst
@@ -31,9 +31,6 @@ Classic
    cycle_graph
    dorogovtsev_goltsev_mendes_graph
    empty_graph
-   grid_2d_graph
-   grid_graph
-   hypercube_graph
    ladder_graph
    lollipop_graph
    null_graph
@@ -54,6 +51,19 @@ Expanders
    chordal_cycle_graph
 
 
+Lattice
+-------
+.. automodule:: networkx.generators.lattice
+.. autosummary::
+   :toctree:generated/
+
+   grid_2d_graph
+   grid_graph
+   hexagonal_lattice_graph
+   hypercube_graph
+   triangular_lattice_graph
+
+
 Small
 -----
 .. automodule:: networkx.generators.small
diff --git a/networkx/algorithms/dag.py b/networkx/algorithms/dag.py
index 00d5a30cc..381090391 100644
--- a/networkx/algorithms/dag.py
+++ b/networkx/algorithms/dag.py
@@ -1,5 +1,10 @@
 # -*- coding: utf-8 -*-
-"""Algorithms for directed acyclic graphs (DAGs)."""
+"""Algorithms for directed acyclic graphs (DAGs).
+
+Note that most of these functions are only guaranteed to work for DAGs.
+In general, these functions do not check for acyclic-ness, so it is up
+to the user to check for that.
+"""
 #    Copyright (C) 2006-2016 by
 #    Aric Hagberg <hagberg@lanl.gov>
 #    Dan Schult <dschult@colgate.edu>
@@ -31,17 +36,18 @@ __all__ = ['descendants',
 
 
 def descendants(G, source):
-    """Return all nodes reachable from `source` in G.
+    """Return all nodes reachable from `source` in `G`.
 
     Parameters
     ----------
     G : NetworkX DiGraph
-    source : node in G
+        A directed acyclic graph (DAG)
+    source : node in `G`
 
     Returns
     -------
-    des : set()
-        The descendants of source in G
+    set()
+        The descendants of `source` in `G`
     """
     if not G.has_node(source):
         raise nx.NetworkXError("The node %s is not in the graph." % source)
@@ -50,16 +56,17 @@ def descendants(G, source):
 
 
 def ancestors(G, source):
-    """Return all nodes having a path to `source` in G.
+    """Return all nodes having a path to `source` in `G`.
 
     Parameters
     ----------
     G : NetworkX DiGraph
-    source : node in G
+        A directed acyclic graph (DAG)
+    source : node in `G`
 
     Returns
     -------
-    ancestors : set()
+    set()
         The ancestors of source in G
     """
     if not G.has_node(source):
@@ -69,18 +76,17 @@ def ancestors(G, source):
 
 
 def is_directed_acyclic_graph(G):
-    """Return True if the graph G is a directed acyclic graph (DAG) or
+    """Return True if the graph `G` is a directed acyclic graph (DAG) or
     False if not.
 
     Parameters
     ----------
     G : NetworkX graph
-        A graph
 
     Returns
     -------
-    is_dag : bool
-        True if G is a DAG, false otherwise
+    bool
+        True if `G` is a DAG, False otherwise
     """
     if not G.is_directed():
         return False
@@ -101,39 +107,39 @@ def topological_sort(G):
     Parameters
     ----------
     G : NetworkX digraph
-        A directed graph
+        A directed acyclic graph (DAG)
 
     Returns
     -------
-    topologically_sorted_nodes : iterable
+    iterable
         An iterable of node names in topological sorted order.
 
     Raises
     ------
     NetworkXError
-        Topological sort is defined for directed graphs only. If the graph G
+        Topological sort is defined for directed graphs only. If the graph `G`
         is undirected, a :exc:`NetworkXError` is raised.
 
     NetworkXUnfeasible
-        If G is not a directed acyclic graph (DAG) no topological sort exists
-        and a NetworkXUnfeasible exception is raised.  This can also be
-        raised if G is changed while the returned iterator is being processed.
+        If `G` is not a directed acyclic graph (DAG) no topological sort exists
+        and a :exc:`NetworkXUnfeasible` exception is raised.  This can also be
+        raised if `G` is changed while the returned iterator is being processed.
 
     RuntimeError
-        If G is changed while the returned iterator is being processed.
+        If `G` is changed while the returned iterator is being processed.
 
     Examples
     --------
-    To get the reverse order of the topological sort::
+    To get the reverse order of the topological sort:
 
-        >>> DG = nx.DiGraph([(1, 2), (2, 3)])
-        >>> list(reversed(list(nx.topological_sort(DG))))
-        [3, 2, 1]
+    >>> DG = nx.DiGraph([(1, 2), (2, 3)])
+    >>> list(reversed(list(nx.topological_sort(DG))))
+    [3, 2, 1]
 
     Notes
     -----
     This algorithm is based on a description and proof in
-    Introduction to algorithms - a creative approach [1]_ .
+    "Introduction to Algorithms: A Creative Approach" [1]_ .
 
     See also
     --------
@@ -141,8 +147,8 @@ def topological_sort(G):
 
     References
     ----------
-    .. [1] Manber, U. (1989). Introduction to algorithms - a creative approach. Addison-Wesley.
-        http://www.amazon.com/Introduction-Algorithms-A-Creative-Approach/dp/0201120372
+    .. [1] Manber, U. (1989). "Introduction to Algorithms: A Creative Approach."
+       Addison-Wesley.
     """
     if not G.is_directed():
         raise nx.NetworkXError(
@@ -183,7 +189,7 @@ def lexicographical_topological_sort(G, key=None):
     Parameters
     ----------
     G : NetworkX digraph
-        A directed graph
+        A directed acyclic graph (DAG)
 
     key : function, optional
         This function maps nodes to keys with which to resolve ambiguities in
@@ -191,27 +197,27 @@ def lexicographical_topological_sort(G, key=None):
 
     Returns
     -------
-    lexicographically_topologically_sorted_nodes : iterable
+    iterable
         An iterable of node names in lexicographical topological sort order.
 
     Raises
     ------
     NetworkXError
-        Topological sort is defined for directed graphs only. If the graph G
+        Topological sort is defined for directed graphs only. If the graph `G`
         is undirected, a :exc:`NetworkXError` is raised.
 
     NetworkXUnfeasible
-        If G is not a directed acyclic graph (DAG) no topological sort exists
-        and a NetworkXUnfeasible exception is raised.  This can also be
-        raised if G is changed while the returned iterator is being processed.
+        If `G` is not a directed acyclic graph (DAG) no topological sort exists
+        and a :exc:`NetworkXUnfeasible` exception is raised.  This can also be
+        raised if `G` is changed while the returned iterator is being processed.
 
     RuntimeError
-        If G is changed while the returned iterator is being processed.
+        If `G` is changed while the returned iterator is being processed.
 
     Notes
     -----
     This algorithm is based on a description and proof in
-    Introduction to algorithms - a creative approach [1]_ .
+    "Introduction to Algorithms: A Creative Approach" [1]_ .
 
     See also
     --------
@@ -219,8 +225,8 @@ def lexicographical_topological_sort(G, key=None):
 
     References
     ----------
-    .. [1] Manber, U. (1989). Introduction to algorithms - a creative approach. Addison-Wesley.
-        http://www.amazon.com/Introduction-Algorithms-A-Creative-Approach/dp/0201120372
+    .. [1] Manber, U. (1989). "Introduction to Algorithms: A Creative Approach."
+       Addison-Wesley.
     """
     if not G.is_directed():
         raise nx.NetworkXError(
@@ -259,7 +265,7 @@ def lexicographical_topological_sort(G, key=None):
 
 
 def is_aperiodic(G):
-    """Return True if G is aperiodic.
+    """Return True if `G` is aperiodic.
 
     A directed graph is aperiodic if there is no integer k > 1 that
     divides the length of every cycle in the graph.
@@ -267,28 +273,28 @@ def is_aperiodic(G):
     Parameters
     ----------
     G : NetworkX DiGraph
-        Graph
+        A directed graph
 
     Returns
     -------
-    aperiodic : boolean
+    bool
         True if the graph is aperiodic False otherwise
 
     Raises
     ------
     NetworkXError
-        If G is not directed
+        If `G` is not directed
 
     Notes
     -----
     This uses the method outlined in [1]_, which runs in O(m) time
-    given m edges in G. Note that a graph is not aperiodic if it is
+    given m edges in `G`. Note that a graph is not aperiodic if it is
     acyclic as every integer trivial divides length 0 cycles.
 
     References
     ----------
     .. [1] Jarvis, J. P.; Shier, D. R. (1996),
-       Graph-theoretic analysis of finite Markov chains,
+       "Graph-theoretic analysis of finite Markov chains,"
        in Shier, D. R.; Wallenius, K. T., Applied Mathematical Modeling:
        A Multidisciplinary Approach, CRC Press.
     """
@@ -329,17 +335,17 @@ def transitive_closure(G):
     Parameters
     ----------
     G : NetworkX DiGraph
-        Graph
+        A directed graph
 
     Returns
     -------
-    TC : NetworkX DiGraph
-        Graph
+    NetworkX DiGraph
+        The transitive closure of `G`
 
     Raises
     ------
     NetworkXNotImplemented
-        If G is not directed
+        If `G` is not directed
 
     References
     ----------
@@ -366,40 +372,40 @@ def transitive_reduction(G):
     Parameters
     ----------
     G : NetworkX DiGraph
-        Graph
+        A directed acyclic graph (DAG)
 
     Returns
     -------
-    TR : NetworkX DiGraph
-        Graph
+    NetworkX DiGraph
+        The transitive reduction of `G`
 
     Raises
     ------
     NetworkXError
-        If G is not a directed acyclic graph (DAG) transitive reduction is
-        not uniquely defined and a NetworkXError exception is raised.
+        If `G` is not a directed acyclic graph (DAG) transitive reduction is
+        not uniquely defined and a :exc:`NetworkXError` exception is raised.
 
-    References 
+    References
     ----------
     https://en.wikipedia.org/wiki/Transitive_reduction
 
-    """   
+    """
     if not is_directed_acyclic_graph(G):
         raise nx.NetworkXError(
-            "Transitive reduction only uniquely defined on directed acyclic graphs.") 
+            "Transitive reduction only uniquely defined on directed acyclic graphs.")
     TR = nx.DiGraph()
     TR.add_nodes_from(G.nodes())
     for u in G:
         u_edges = set(G[u])
         for v in G[u]:
             u_edges -= {y for x, y in nx.dfs_edges(G, v)}
-        TR.add_edges_from((u,v) for v in u_edges)
+        TR.add_edges_from((u, v) for v in u_edges)
     return TR
 
 
 @not_implemented_for('undirected')
 def antichains(G):
-    """Generates antichains from a DAG.
+    """Generates antichains from a directed acyclic graph (DAG).
 
     An antichain is a subset of a partially ordered set such that any
     two elements in the subset are incomparable.
@@ -407,19 +413,19 @@ def antichains(G):
     Parameters
     ----------
     G : NetworkX DiGraph
-        Graph
+        A directed acyclic graph (DAG)
 
     Returns
     -------
-    antichain : generator object
+    generator object
 
     Raises
     ------
     NetworkXNotImplemented
-        If G is not directed
+        If `G` is not directed
 
     NetworkXUnfeasible
-        If G contains a cycle
+        If `G` contains a cycle
 
     Notes
     -----
@@ -452,38 +458,39 @@ def antichains(G):
 
 @not_implemented_for('undirected')
 def dag_longest_path(G, weight='weight', default_weight=1):
-    """Returns the longest path in a DAG
-    If G has edges with 'weight' attribute the edge data are used as weight values.
+    """Returns the longest path in a directed acyclic graph (DAG).
+
+    If `G` has edges with `weight` attribute the edge data are used as weight values.
 
     Parameters
     ----------
     G : NetworkX DiGraph
-        Graph
+        A directed acyclic graph (DAG)
 
-    weight : string (default 'weight')
+    weight : str, optional
         Edge data key to use for weight
 
-    default_weight : integer (default 1)
+    default_weight : int, optional
         The weight of edges that do not have a weight attribute
 
     Returns
     -------
-    path : list
+    list
         Longest path
 
     Raises
     ------
     NetworkXNotImplemented
-        If G is not directed
+        If `G` is not directed
 
     See also
     --------
     dag_longest_path_length
     """
-    dist = {} # stores {v : (length, u)}
+    dist = {}  # stores {v : (length, u)}
     for v in nx.topological_sort(G):
         us = [(dist[u][0] + data.get(weight, default_weight), u)
-            for u, data in G.pred[v].items()]
+              for u, data in G.pred[v].items()]
         # Use the best predecessor if there is one and its distance is non-negative, otherwise terminate.
         maxu = max(us, key=lambda x: x[0]) if us else (0, v)
         dist[v] = maxu if maxu[0] >= 0 else (0, v)
@@ -505,23 +512,23 @@ def dag_longest_path_length(G, weight='weight', default_weight=1):
     Parameters
     ----------
     G : NetworkX DiGraph
-        Graph
+        A directed acyclic graph (DAG)
 
-    weight : string (default 'weight')
+    weight : string, optional
         Edge data key to use for weight
 
-    default_weight : integer (default 1)
+    default_weight : int, optional
         The weight of edges that do not have a weight attribute
 
     Returns
     -------
-    path_length : int
+    int
         Longest path length
 
     Raises
     ------
     NetworkXNotImplemented
-        If G is not directed
+        If `G` is not directed
 
     See also
     --------
diff --git a/networkx/generators/__init__.py b/networkx/generators/__init__.py
index f253d9c72..41e1bea04 100644
--- a/networkx/generators/__init__.py
+++ b/networkx/generators/__init__.py
@@ -13,6 +13,7 @@ from networkx.generators.expanders import *
 from networkx.generators.geometric import *
 from networkx.generators.intersection import *
 from networkx.generators.joint_degree_seq import *
+from networkx.generators.lattice import *
 from networkx.generators.line import *
 from networkx.generators.nonisomorphic_trees import *
 from networkx.generators.random_clustered import *
diff --git a/networkx/generators/classic.py b/networkx/generators/classic.py
index fbdfd4995..8fe146f8e 100644
--- a/networkx/generators/classic.py
+++ b/networkx/generators/classic.py
@@ -1,29 +1,31 @@
-"""
-Generators for some classic graphs.
+#    Copyright (C) 2004-2017 by
+#    Aric Hagberg <hagberg@lanl.gov>
+#    Dan Schult <dschult@colgate.edu>
+#    Pieter Swart <swart@lanl.gov>
+#    All rights reserved.
+#    BSD license.
+#
+# Authors: Aric Hagberg (hagberg@lanl.gov)
+#          Pieter Swart (swart@lanl.gov)
+"""Generators for some classic graphs.
 
 The typical graph generator is called as follows:
 
->>> G=nx.complete_graph(100)
+>>> G = nx.complete_graph(100)
 
 returning the complete graph on n nodes labeled 0, .., 99
 as a simple graph. Except for empty_graph, all the generators
 in this module return a Graph class (i.e. a simple, undirected graph).
 
 """
-# Authors: Aric Hagberg (hagberg@lanl.gov) and Pieter Swart (swart@lanl.gov)
-
-#    Copyright (C) 2004-2016 by
-#    Aric Hagberg <hagberg@lanl.gov>
-#    Dan Schult <dschult@colgate.edu>
-#    Pieter Swart <swart@lanl.gov>
-#    All rights reserved.
-#    BSD license.
 from __future__ import division
 
 import itertools
 
 import networkx as nx
 from networkx.algorithms.bipartite.generators import complete_bipartite_graph
+from networkx.classes import Graph
+from networkx.exception import NetworkXError
 from networkx.utils import accumulate
 from networkx.utils import flatten
 from networkx.utils import nodes_or_number
@@ -39,9 +41,6 @@ __all__ = ['balanced_tree',
            'dorogovtsev_goltsev_mendes_graph',
            'empty_graph',
            'full_rary_tree',
-           'grid_graph',
-           'grid_2d_graph',
-           'hypercube_graph',
            'ladder_graph',
            'lollipop_graph',
            'null_graph',
@@ -52,9 +51,9 @@ __all__ = ['balanced_tree',
            'wheel_graph']
 
 
-#-------------------------------------------------------------------
+# -------------------------------------------------------------------
 #   Some Classic Graphs
-#-------------------------------------------------------------------
+# -------------------------------------------------------------------
 
 def _tree_edges(n, r):
     # helper function for trees
@@ -101,7 +100,7 @@ def full_rary_tree(r, n, create_using=None):
     .. [1] An introduction to data structures and algorithms,
            James Andrew Storer,  Birkhauser Boston 2001, (page 225).
     """
-    G = nx.empty_graph(n, create_using)
+    G = empty_graph(n, create_using)
     G.add_edges_from(_tree_edges(n, r))
     return G
 
@@ -165,8 +164,8 @@ def barbell_graph(m1, m2, create_using=None):
         `m1, ..., m1+m2-1` for the path,
         and `m1+m2, ..., 2*m1+m2-1` for the right barbell.
 
-    The 3 subgraphs are joined via the edges `(m1-1, m1)` and 
-    `(m1+m2-1, m1+m2)`. If `m2=0`, this is merely two complete 
+    The 3 subgraphs are joined via the edges `(m1-1, m1)` and
+    `(m1+m2-1, m1+m2)`. If `m2=0`, this is merely two complete
     graphs joined together.
 
     This graph is an extremal example in David Aldous
@@ -174,12 +173,12 @@ def barbell_graph(m1, m2, create_using=None):
 
     """
     if create_using is not None and create_using.is_directed():
-        raise nx.NetworkXError("Directed Graph not supported")
+        raise NetworkXError("Directed Graph not supported")
     if m1 < 2:
-        raise nx.NetworkXError(
+        raise NetworkXError(
             "Invalid graph description, m1 should be >=2")
     if m2 < 0:
-        raise nx.NetworkXError(
+        raise NetworkXError(
             "Invalid graph description, m2 should be >=0")
 
     # left barbell
@@ -220,7 +219,7 @@ def complete_graph(n, create_using=None):
     9
     >>> G.size()
     36
-    >>> G = nx.complete_graph(range(11,14))
+    >>> G = nx.complete_graph(range(11, 14))
     >>> list(G.nodes())
     [11, 12, 13]
     >>> G = nx.complete_graph(4, nx.DiGraph())
@@ -277,9 +276,9 @@ def circulant_graph(n, offsets, create_using=None):
 
     Examples
     --------
-    Many well-known graph families are subfamilies of the circulant graphs; for
-    example, to generate the cycle graph on n points, we connect every vertex to
-    every other at offset plus or minus one. For n = 10,
+    Many well-known graph families are subfamilies of the circulant graphs;
+    for example, to generate the cycle graph on n points, we connect every
+    vertex to every other at offset plus or minus one. For n = 10,
 
     >>> import networkx
     >>> G = networkx.generators.classic.circulant_graph(10, [1])
@@ -335,7 +334,7 @@ def cycle_graph(n, create_using=None):
     n_orig, nodes = n
     G = empty_graph(nodes, create_using)
     G.name = "cycle_graph(%s)" % (n_orig,)
-    G.add_edges_from(nx.utils.pairwise(nodes))
+    G.add_edges_from(pairwise(nodes))
     G.add_edge(nodes[-1], nodes[0])
     return G
 
@@ -349,9 +348,9 @@ def dorogovtsev_goltsev_mendes_graph(n, create_using=None):
     """
     if create_using is not None:
         if create_using.is_directed():
-            raise nx.NetworkXError("Directed Graph not supported")
+            raise NetworkXError("Directed Graph not supported")
         if create_using.is_multigraph():
-            raise nx.NetworkXError("Multigraph not supported")
+            raise NetworkXError("Multigraph not supported")
     G = empty_graph(0, create_using)
     G.name = "Dorogovtsev-Goltsev-Mendes Graph"
     G.add_edge(0, 1)
@@ -382,12 +381,12 @@ def empty_graph(n=0, create_using=None):
         with the new graph. Usually used to set the type of the graph.
 
     For example:
-    >>> G=nx.empty_graph(10)
+    >>> G = nx.empty_graph(10)
     >>> G.number_of_nodes()
     10
     >>> G.number_of_edges()
     0
-    >>> G=nx.empty_graph("ABC")
+    >>> G = nx.empty_graph("ABC")
     >>> G.number_of_nodes()
     3
     >>> sorted(G)
@@ -405,8 +404,8 @@ def empty_graph(n=0, create_using=None):
     Firstly, the variable create_using can be used to create an
     empty digraph, multigraph, etc.  For example,
 
-    >>> n=10
-    >>> G=nx.empty_graph(n, create_using=nx.DiGraph())
+    >>> n = 10
+    >>> G = nx.empty_graph(n, create_using=nx.DiGraph())
 
     will create an empty digraph on n nodes.
 
@@ -421,7 +420,7 @@ def empty_graph(n=0, create_using=None):
     """
     if create_using is None:
         # default empty graph is a simple graph
-        G = nx.Graph()
+        G = Graph()
     else:
         G = create_using
         G.clear()
@@ -432,116 +431,17 @@ def empty_graph(n=0, create_using=None):
     return G
 
 
-@nodes_or_number([0, 1])
-def grid_2d_graph(m, n, periodic=False, create_using=None):
-    """ Return the 2d grid graph of mxn nodes
-    
-    The grid graph has each node connected to its four nearest neighbors.
-
-    Parameters
-    ==========
-    m, n : int or iterable container of nodes (default = 0)
-        If an integer, nodes are from `range(n)`.
-        If a container, those become the coordinate of the node.
-    periodic : bool (default = False)
-        If True will connect boundary nodes in periodic fashion.
-    create_using : Graph, optional (default Graph())
-        If provided this graph is cleared of nodes and edges and filled
-        with the new graph. Usually used to set the type of the graph.
-    """
-    G = empty_graph(0, create_using)
-    row_name, rows = m
-    col_name, columns = n
-    G.name = "grid_2d_graph(%s, %s)" % (row_name, col_name)
-    G.add_nodes_from((i, j) for i in rows for j in columns)
-    G.add_edges_from(((i, j), (pi, j))
-                     for pi, i in pairwise(rows) for j in columns)
-    G.add_edges_from(((i, j), (i, pj))
-                     for i in rows for pj, j in pairwise(columns))
-    if G.is_directed():
-        G.add_edges_from(((pi, j), (i, j))
-                         for pi, i in pairwise(rows) for j in columns)
-        G.add_edges_from(((i, pj), (i, j))
-                         for i in rows for pj, j in pairwise(columns))
-    if periodic:
-        if len(columns) > 2:
-            f = columns[0]
-            l = columns[-1]
-            G.add_edges_from(((i, f), (i, l)) for i in rows)
-            if G.is_directed():
-                G.add_edges_from(((i, l), (i, f)) for i in rows)
-        if len(rows) > 2:
-            f = rows[0]
-            l = rows[-1]
-            G.add_edges_from(((f, j), (l, j)) for j in columns)
-            if G.is_directed():
-                G.add_edges_from(((l, j), (f, j)) for j in columns)
-        G.name = "periodic_grid_2d_graph(%s,%s)" % (m, n)
-    return G
-
-
-def grid_graph(dim, periodic=False):
-    """ Return the n-dimensional grid graph.
-
-    'dim' is a tuple or list with the size in each dimension or an
-    iterable of nodes for each dimension. The dimension of
-    the grid_graph is the length of the tuple or list 'dim'.
-
-    E.g. G=grid_graph(dim=[2, 3]) produces a 2x3 grid graph.
-
-    E.g. G=grid_graph(dim=[range(7, 9), range(3, 6)]) produces a 2x3 grid graph.
-
-    If periodic=True then join grid edges with periodic boundary conditions.
-
-    """
-    dlabel = "%s" % str(dim)
-    if not dim:
-        G = empty_graph(0)
-        G.name = "grid_graph(%s)" % dlabel
-        return G
-    if periodic:
-        func = cycle_graph
-    else:
-        func = path_graph
-
-    G = func(dim[0])
-    for current_dim in dim[1:]:
-        # order matters: copy before it is cleared during the creation of Gnew
-        Gold = G.copy()
-        Gnew = func(current_dim)
-        # explicit: create_using=None
-        # This is so that we get a new graph of Gnew's class.
-        G = nx.cartesian_product(Gnew, Gold)
-    # graph G is done but has labels of the form (1, (2, (3, 1)))
-    # so relabel
-    H = nx.relabel_nodes(G, flatten)
-    H.name = "grid_graph(%s)" % dlabel
-    return H
-
-
-def hypercube_graph(n):
-    """Return the n-dimensional hypercube.
-
-    Node labels are the integers 0 to 2**n - 1.
-
-    """
-    dim = n * [2]
-    G = grid_graph(dim)
-    G.name = "hypercube_graph_(%d)" % n
-    return G
-
-
 def ladder_graph(n, create_using=None):
     """Return the Ladder graph of length n.
 
-    This is two rows of n nodes, with
+    This is two paths of n nodes, with
     each pair connected by a single edge.
 
     Node labels are the integers 0 to 2*n - 1.
 
     """
     if create_using is not None and create_using.is_directed():
-        raise nx.NetworkXError("Directed Graph not supported")
+        raise NetworkXError("Directed Graph not supported")
     G = empty_graph(2 * n, create_using)
     G.name = "ladder_graph_(%d)" % n
     G.add_edges_from(pairwise(range(n)))
@@ -570,7 +470,7 @@ def lollipop_graph(m, n, create_using=None):
 
     Notes
     =====
-    The 2 subgraphs are joined via an edge (m-1, m).  
+    The 2 subgraphs are joined via an edge (m-1, m).
     If n=0, this is merely a complete graph.
 
     (This graph is an extremal example in David Aldous and Jim
@@ -584,12 +484,12 @@ def lollipop_graph(m, n, create_using=None):
     if isinstance(m, int):
         n_nodes = [len(m_nodes) + i for i in n_nodes]
     if create_using is not None and create_using.is_directed():
-        raise nx.NetworkXError("Directed Graph not supported")
+        raise NetworkXError("Directed Graph not supported")
     if M < 2:
-        raise nx.NetworkXError(
+        raise NetworkXError(
             "Invalid graph description, m should be >=2")
     if N < 0:
-        raise nx.NetworkXError(
+        raise NetworkXError(
             "Invalid graph description, n should be >=0")
 
     # the ball
@@ -633,14 +533,14 @@ def path_graph(n, create_using=None):
     n_name, nodes = n
     G = empty_graph(nodes, create_using)
     G.name = "path_graph(%s)" % (n_name,)
-    G.add_edges_from(nx.utils.pairwise(nodes))
+    G.add_edges_from(pairwise(nodes))
     return G
 
 
 @nodes_or_number(0)
 def star_graph(n, create_using=None):
     """ Return the star graph
-    
+
     The star graph consists of one center node connected to n outer nodes.
 
     Parameters
@@ -663,7 +563,7 @@ def star_graph(n, create_using=None):
     first = nodes[0]
     G = empty_graph(nodes, create_using)
     if G.is_directed():
-        raise nx.NetworkXError("Directed Graph not supported")
+        raise NetworkXError("Directed Graph not supported")
     G.add_edges_from((first, v) for v in nodes[1:])
     G.name = "star_graph(%s)" % (n_name,)
     return G
@@ -704,7 +604,7 @@ def turan_graph(n, r):
     """
 
     if not 1 <= r <= n:
-        raise nx.NetworkXError("Must satisfy 1 <= r <= n")
+        raise NetworkXError("Must satisfy 1 <= r <= n")
 
     partitions = [n//r]*(r-(n%r))+[n//r+1]*(n%r)
     G = complete_multipartite_graph(*partitions)
@@ -715,7 +615,7 @@ def turan_graph(n, r):
 @nodes_or_number(0)
 def wheel_graph(n, create_using=None):
     """ Return the wheel graph
-    
+
     The wheel graph consists of a hub node connected to a cycle of (n-1) nodes.
 
     Parameters
@@ -731,7 +631,7 @@ def wheel_graph(n, create_using=None):
     """
     n_name, nodes = n
     if n_name == 0:
-        G = nx.empty_graph(0, create_using=create_using)
+        G = empty_graph(0, create_using=create_using)
         G.name = "wheel_graph(0)"
         return G
     G = star_graph(nodes, create_using)
@@ -798,7 +698,7 @@ def complete_multipartite_graph(*subset_sizes):
     complete_bipartite_graph
     """
     # The complete multipartite graph is an undirected simple graph.
-    G = nx.Graph()
+    G = Graph()
     G.name = 'complete_multiparite_graph{}'.format(subset_sizes)
 
     if len(subset_sizes) == 0:
@@ -817,7 +717,7 @@ def complete_multipartite_graph(*subset_sizes):
         for (i, subset) in enumerate(subsets):
             G.add_nodes_from(subset, subset=i)
     except TypeError:
-        raise nx.NetworkXError("Arguments must be all ints or all iterables")
+        raise NetworkXError("Arguments must be all ints or all iterables")
 
     # Across subsets, all vertices should be adjacent.
     # We can use itertools.combinations() because undirected.
diff --git a/networkx/generators/lattice.py b/networkx/generators/lattice.py
new file mode 100644
index 000000000..32e77a7ad
--- /dev/null
+++ b/networkx/generators/lattice.py
@@ -0,0 +1,391 @@
+# -*- coding: utf-8 -*-
+#    Copyright (C) 2004-2017 by
+#    Aric Hagberg <hagberg@lanl.gov>
+#    Dan Schult <dschult@colgate.edu>
+#    Pieter Swart <swart@lanl.gov>
+#    All rights reserved.
+#    BSD license.
+#
+# Authors: Aric Hagberg (hagberg@lanl.gov)
+#          Pieter Swart (swart@lanl.gov)
+#          Joel Miller (jmiller@lanl.gov)
+#          Dan Schult (dschult@lanl.gov)
+"""Functions for generating grid graphs and lattices
+
+The :func:`grid_2d_graph`, :func:`triangular_lattice_graph`, and
+:func:`hexagonal_lattice_graph` functions correspond to the three
+`regular tilings of the plane`_, the square, triangular, and hexagonal
+tilings, respectively. :func:`grid_graph` and :func:`hypercube_graph`
+are similar for arbitrary dimensions. Useful relevent discussion can
+be found about `Triangular Tiling`_, and `Square, Hex and Triangle Grids`_
+
+.. _regular tilings of the plane: https://en.wikipedia.org/wiki/List_of_regular_polytopes_and_compounds#Euclidean_tilings
+.. _Square, Hex and Triangle Grids: http://www-cs-students.stanford.edu/~amitp/game-programming/grids/
+.. _Triangular Tiling: https://en.wikipedia.org/wiki/Triangular_tiling
+
+"""
+from __future__ import division
+
+from math import sqrt
+
+from networkx.classes import Graph
+from networkx.classes import set_node_attributes
+from networkx.algorithms.minors import contracted_nodes
+from networkx.algorithms.operators.product import cartesian_product
+from networkx.exception import NetworkXError
+from networkx.relabel import relabel_nodes
+from networkx.utils import flatten
+from networkx.utils import is_list_of_ints
+from networkx.utils import nodes_or_number
+from networkx.utils import pairwise
+from networkx.generators.classic import cycle_graph
+from networkx.generators.classic import empty_graph
+from networkx.generators.classic import path_graph
+
+__all__ = ['grid_2d_graph', 'grid_graph', 'hypercube_graph',
+           'triangular_lattice_graph', 'hexagonal_lattice_graph']
+
+
+@nodes_or_number([0, 1])
+def grid_2d_graph(m, n, periodic=False, create_using=None):
+    """Returns the two-dimensional grid graph.
+
+    The grid graph has each node connected to its four nearest neighbors.
+
+    Parameters
+    ----------
+    m, n : int or iterable container of nodes
+        If an integer, nodes are from `range(n)`.
+        If a container, elements become the coordinate of the nodes.
+
+    periodic : bool (default: False)
+        If this is ``True`` the nodes on the grid boundaries are joined
+        to the corresponding nodes on the opposite grid boundaries.
+
+    create_using : NetworkX graph (default: Graph())
+        If provided this graph is cleared of nodes and edges and filled
+        with the new graph. Usually used to set the type of the graph.
+
+    Returns
+    -------
+    NetworkX graph
+        The (possibly periodic) grid graph of the specified dimensions.
+
+    """
+    G = empty_graph(0, create_using)
+    row_name, rows = m
+    col_name, cols = n
+    G.add_nodes_from((i, j) for i in rows for j in cols)
+    G.add_edges_from(((i, j), (pi, j))
+                     for pi, i in pairwise(rows) for j in cols)
+    G.add_edges_from(((i, j), (i, pj))
+                     for i in rows for pj, j in pairwise(cols))
+    if periodic is True:
+        if len(rows) > 2:
+            first = rows[0]
+            last = rows[-1]
+            G.add_edges_from(((first, j), (last, j)) for j in cols)
+        if len(cols) > 2:
+            first = cols[0]
+            last = cols[-1]
+            G.add_edges_from(((i, first), (i, last)) for i in rows)
+    # both directions for directed
+    if G.is_directed():
+        G.add_edges_from((v, u) for u, v in G.edges())
+
+    # set name
+    G.name = "grid_2d_graph(%s, %s)" % (row_name, col_name)
+    if periodic is True:
+        G.name = "periodic_" + G.name
+    return G
+
+
+def grid_graph(dim, periodic=False):
+    """Returns the *n*-dimensional grid graph.
+
+    The dimension *n* is the length of the list `dim` and the size in
+    each dimension is the value of the corresponding list element.
+
+    Parameters
+    ----------
+    dim : list or tuple of numbers or iterables of nodes
+        'dim' is a tuple or list with, for each dimension, either a number
+        that is the size of that dimension or an iterable of nodes for
+        that dimension. The dimension of the grid_graph is the length
+        of `dim`.
+
+    periodic : bool
+        If `periodic is True` the nodes on the grid boundaries are joined
+        to the corresponding nodes on the opposite grid boundaries.
+
+    Returns
+    -------
+    NetworkX graph
+        The (possibly periodic) grid graph of the specified dimensions.
+
+    Examples
+    --------
+    To produce a 2 by 3 by 4 grid graph, a graph on 24 nodes::
+
+        >>> G = grid_graph(dim=[2, 3, 4])
+        >>> len(G)
+        24
+        >>> G = grid_graph(dim=[range(7, 9), range(3, 6)])
+        >>> len(G)
+        6
+    """
+    dlabel = "%s" % dim
+    if not dim:
+        G = empty_graph(0)
+        G.name = "grid_graph(%s)" % dlabel
+        return G
+
+    func = cycle_graph if periodic else path_graph
+    G = func(dim[0])
+    for current_dim in dim[1:]:
+        # order matters: copy before it is cleared during the creation of Gnew
+        Gold = G.copy()
+        Gnew = func(current_dim)
+        # explicit: create_using = None
+        # This is so that we get a new graph of Gnew's class.
+        G = cartesian_product(Gnew, Gold)
+    # graph G is done but has labels of the form (1, (2, (3, 1))) so relabel
+    H = relabel_nodes(G, flatten)
+    H.name = "grid_graph(%s)" % dlabel
+    return H
+
+
+def hypercube_graph(n):
+    """Returns the *n*-dimensional hypercube graph.
+
+    The nodes are the integers between 0 and ``2 ** n - 1``, inclusive.
+
+    For more information on the hypercube graph, see the Wikipedia
+    article *`Hypercube graph`_*.
+
+    .. _Hypercube graph: https://en.wikipedia.org/wiki/Hypercube_graph
+
+    Parameters
+    ----------
+    n : int
+        The dimension of the hypercube.
+        The number of nodes in the graph will be ``2 ** n``.
+
+    Returns
+    -------
+    NetworkX graph
+        The hypercube graph of dimension *n*.
+    """
+    dim = n * [2]
+    G = grid_graph(dim)
+    G.name = "hypercube_graph_(%d)" % n
+    return G
+
+
+def triangular_lattice_graph(m, n, periodic=False, with_positions=True,
+                             create_using=None):
+    """Returns the *m* by *n* triangular lattice graph.
+
+    The *`triangular lattice graph`_* is a two-dimensional `grid graph`_ in
+    which each square unit has a diagonal edge (each grid unit has a chord).
+
+    The returned graph has `m` rows and `n` columns of triangles. Rows and
+    columns include both triangles pointing up and down. Rows form a strip
+    of constant height. Columns form a series of diamond shapes, staggered
+    with the columns on either side. Another way to state the size is that
+    the nodes form a grid of `m+1` rows and `(n + 1) // 2` columns.
+    The odd row nodes are shifted horizontally relative to the even rows.
+
+    Directed graph types have edges pointed up or right.
+
+    Positions of nodes are computed by default or `with_positions is True`.
+    The position of each node (embedded in a euclidean plane) is stored in
+    the graph using equilateral triangles with sidelength 1.
+    The height between rows of nodes is thus :math:`\sqrt(3)/2`.
+    Nodes lie in the first quadrant with the node `(0, 0)` at the origin.
+
+    .. _triangular lattice graph: http://mathworld.wolfram.com/TriangularGrid.html
+    .. _grid graph: http://www-cs-students.stanford.edu/~amitp/game-programming/grids/
+    .. _Triangular Tiling: https://en.wikipedia.org/wiki/Triangular_tiling
+
+    Parameters
+    ----------
+    m : int
+        The number of rows in the lattice.
+
+    n : int
+        The number of columns in the lattice.
+
+    periodic : bool (default: False)
+        If True, join the boundary vertices of the grid using periodic
+        boundary conditions. The join between boundaries is the final row
+        and column of triangles. This means there is one row and one column
+        fewer nodes for the periodic lattice. Periodic lattices require
+        `m >= 3`, `n >= 5` and are allowed but misaligned if `m` or `n` are odd
+
+    with_positions : bool (default: True)
+        Store the coordinates of each node in the graph node attribute 'pos'.
+        The coordinates provide a lattice with equilateral triangles.
+        Periodic positions shift the nodes vertically in a nonlinear way so
+        the edges don't overlap so much.
+
+    create_using : NetworkX graph
+        If specified, this must be an instance of a NetworkX graph
+        class. It will be cleared of nodes and edges and filled
+        with the new graph. Usually used to set the type of the graph.
+
+    Returns
+    -------
+    NetworkX graph
+        The *m* by *n* triangular lattice graph.
+    """
+    H = empty_graph(0, create_using)
+    if n == 0 or m == 0:
+        return H
+    if periodic:
+        if n < 5 or m < 3:
+            msg = "m > 2 and n > 4 required for periodic. m={}, n={}"
+            raise NetworkXError(msg.format(m, n))
+
+    N = (n + 1) // 2  # number of nodes in row
+    rows = range(m + 1)
+    cols = range(N + 1)
+    # Make grid
+    H.add_edges_from(((i, j), (i + 1, j)) for j in rows for i in cols[:N])
+    H.add_edges_from(((i, j), (i, j + 1)) for j in rows[:m] for i in cols)
+    # add diagonals
+    H.add_edges_from(((i, j), (i + 1, j + 1))
+                     for j in rows[1:m:2] for i in cols[:N])
+    H.add_edges_from(((i + 1, j), (i, j + 1))
+                     for j in rows[:m:2] for i in cols[:N])
+    # identify boundary nodes if periodic
+    if periodic is True:
+        for i in cols:
+            H = contracted_nodes(H, (i, 0), (i, m))
+        for j in rows[:m]:
+            H = contracted_nodes(H, (0, j), (N, j))
+    elif n % 2:
+        # remove extra nodes
+        H.remove_nodes_from(((N, j) for j in rows[1::2]))
+
+    # Add position node attributes
+    if with_positions:
+        ii = (i for i in cols for j in rows)
+        jj = (j for i in cols for j in rows)
+        xx = (0.5 * (j % 2) + i for i in cols for j in rows)
+        h = sqrt(3)/2
+        if periodic:
+            yy = (h * j + .01 * i * i for i in cols for j in rows)
+        else:
+            yy = (h * j for i in cols for j in rows)
+        pos = {(i, j): (x, y) for i, j, x, y in zip(ii, jj, xx, yy)
+               if (i, j) in H}
+        set_node_attributes(H, 'pos', pos)
+
+    # set the name
+    H.name = 'triangular_lattice_graph({}, {})'.format(m, n)
+    if periodic:
+        H.name = 'periodic_' + H.name
+    return H
+
+
+def hexagonal_lattice_graph(m, n, periodic=False, with_positions=True,
+                            create_using=None):
+    """Returns an `m` by `n` hexagonal lattice graph.
+
+    The *hexagonal lattice graph* is a graph whose nodes and edges are
+    the `hexagonal tiling`_ of the plane.
+
+    The returned graph will have `m` rows and `n` columns of hexagons.
+    `Odd numbered columns`_ are shifted up relative to even numbered columns.
+
+    Positions of nodes are computed by default or `with_positions is True`.
+    Node positions creating the standard embedding in the plane
+    with sidelength 1 and are stored in the node attribute 'pos'.
+    `pos = nx.get_node_attributes(G, 'pos')` creates a dict ready for drawing.
+
+    .. _hexagonal tiling: https://en.wikipedia.org/wiki/Hexagonal_tiling
+    .. _Odd numbered columns: http://www-cs-students.stanford.edu/~amitp/game-programming/grids/
+
+    Parameters
+    ----------
+    m : int
+        The number of rows of hexagons in the lattice.
+
+    n : int
+        The number of columns of hexagons in the lattice.
+
+    periodic : bool
+        Whether to make a periodic grid by joining the boundary vertices.
+        For this to work `n` must be odd and both `n > 1` and `m > 1`.
+        The periodic connections create another row and column of hexagons
+        so these graphs have fewer nodes as boundary nodes are identified.
+
+    with_positions : bool (default: True)
+        Store the coordinates of each node in the graph node attribute 'pos'.
+        The coordinates provide a lattice with vertical columns of hexagons
+        offset to interleave and cover the plane.
+        Periodic positions shift the nodes vertically in a nonlinear way so
+        the edges don't overlap so much.
+
+    create_using : NetworkX graph
+        If specified, this must be an instance of a NetworkX graph
+        class. It will be cleared of nodes and edges and filled
+        with the new graph. Usually used to set the type of the graph.
+        If graph is directed, edges will point up or right.
+
+    Returns
+    -------
+    NetworkX graph
+        The *m* by *n* hexagonal lattice graph.
+    """
+    G = create_using if create_using is not None else Graph()
+    G.clear()
+    if m == 0 or n == 0:
+        return G
+    if periodic and (n % 2 == 1 or m < 2 or n < 2):
+        msg = "periodic hexagonal lattice needs m > 1, n > 1 and even n"
+        raise NetworkXError(msg)
+
+    M = 2 * m    # twice as many nodes as hexagons vertically
+    rows = range(M + 2)
+    cols = range(n + 1)
+    # make lattice
+    col_edges = (((i, j), (i, j + 1)) for i in cols for j in rows[:M + 1])
+    row_edges = (((i, j), (i + 1, j)) for i in cols[:n] for j in rows
+                 if i % 2 == j % 2)
+    G.add_edges_from(col_edges)
+    G.add_edges_from(row_edges)
+    # Remove corner nodes with one edge
+    G.remove_node((0, M + 1))
+    G.remove_node((n, (M + 1) * (n % 2)))
+
+    # identify boundary nodes if periodic
+    if periodic:
+        for i in cols[:n]:
+            G = contracted_nodes(G, (i, 0), (i, M))
+        for i in cols[1:]:
+            G = contracted_nodes(G, (i, 1), (i, M + 1))
+        for j in rows[1:M]:
+            G = contracted_nodes(G, (0, j), (n, j))
+        G.remove_node((n, M))
+
+    # calc position in embedded space
+    ii = (i for i in cols for j in rows)
+    jj = (j for i in cols for j in rows)
+    xx = (0.5 + i + i // 2 + (j % 2) * ((i % 2) - .5)
+          for i in cols for j in rows)
+    h = sqrt(3)/2
+    if periodic:
+        yy = (h * j + .01*i*i for i in cols for j in rows)
+    else:
+        yy = (h * j for i in cols for j in rows)
+    # exclude nodes not in G
+    pos = {(i, j): (x, y) for i, j, x, y in zip(ii, jj, xx, yy) if (i, j) in G}
+    set_node_attributes(G, 'pos', pos)
+
+    # set the name
+    G.name = 'hexagonal_lattice_graph({}, {})'.format(m, n)
+    if periodic:
+        G.name = 'periodic_' + G.name
+    return G
diff --git a/networkx/readwrite/gml.py b/networkx/readwrite/gml.py
index af8db1d00..22c0afdf0 100644
--- a/networkx/readwrite/gml.py
+++ b/networkx/readwrite/gml.py
@@ -423,7 +423,9 @@ def parse_gml_lines(lines, label, destringizer):
                 G.add_edge(source, target, **edge)
             else:
                 raise nx.NetworkXError(
-                    'edge #%d (%r%s%r) is duplicated' %
+                    """edge #%d (%r%s%r) is duplicated
+
+Hint:  If this is a multigraph, add "multigraph 1" to the header of the file.""" %
                     (i, source, '->' if directed else '--', target))
         else:
             key = edge.pop('key', None)
@@ -565,7 +567,7 @@ def generate_gml(G, stringizer=None):
     Notes
     -----
     Graph attributes named 'directed', 'multigraph', 'node' or
-    'edge',node attributes named 'id' or 'label', edge attributes
+    'edge', node attributes named 'id' or 'label', edge attributes
     named 'source' or 'target' (or 'key' if `G` is a multigraph)
     are ignored because these attribute names are used to encode the graph
     structure.
@@ -581,7 +583,10 @@ def generate_gml(G, stringizer=None):
             key = str(key)
         if key not in ignored_keys:
             if isinstance(value, (int, long)):
-                yield indent + key + ' ' + str(value)
+                if key == 'label':
+                    yield indent + key + ' "' + str(value) + '"'
+                else:
+                    yield indent + key + ' ' + str(value)
             elif isinstance(value, float):
                 text = repr(value).upper()
                 # GML requires that a real literal contain a decimal point, but
@@ -590,7 +595,10 @@ def generate_gml(G, stringizer=None):
                 epos = text.rfind('E')
                 if epos != -1 and text.find('.', 0, epos) == -1:
                     text = text[:epos] + '.' + text[epos:]
-                yield indent + key + ' ' + text
+                if key == 'label':
+                    yield indent + key + ' "' + test + '"'
+                else:
+                    yield indent + key + ' ' + text
             elif isinstance(value, dict):
                 yield indent + key + ' ['
                 next_indent = indent + '  '


Test patch:
diff --git a/networkx/generators/tests/test_classic.py b/networkx/generators/tests/test_classic.py
index 72a38ead9..d7cb02721 100644
--- a/networkx/generators/tests/test_classic.py
+++ b/networkx/generators/tests/test_classic.py
@@ -257,82 +257,6 @@ class TestGeneratorClassic():
         assert_equal(G.name, 'empty_graph(42)')
         assert_true(isinstance(G,Graph))
 
-    def test_grid_2d_graph(self):
-        n=5;m=6
-        G=grid_2d_graph(n,m)
-        assert_equal(number_of_nodes(G), n*m)
-        assert_equal(degree_histogram(G), [0,0,4,2*(n+m)-8,(n-2)*(m-2)])
-        DG=grid_2d_graph(n,m, create_using=DiGraph())
-        assert_equal(DG.succ, G.adj)
-        assert_equal(DG.pred, G.adj)
-        MG=grid_2d_graph(n,m, create_using=MultiGraph())
-        assert_edges_equal(MG.edges(), G.edges())
-        g=grid_2d_graph(range(n), range(m))
-        assert_edges_equal(g.edges(), G.edges())
-
-    def test_grid_graph(self):
-        """grid_graph([n,m]) is a connected simple graph with the
-        following properties:
-        number_of_nodes=n*m
-        degree_histogram=[0,0,4,2*(n+m)-8,(n-2)*(m-2)]
-        """
-        for n, m in [(3, 5), (5, 3), (4, 5), (5, 4)]:
-            dim=[n,m]
-            g=grid_graph(dim)
-            assert_equal(number_of_nodes(g), n*m)
-            assert_equal(degree_histogram(g), [0,0,4,2*(n+m)-8,(n-2)*(m-2)])
-            assert_equal(dim,[n,m])
-
-        for n, m in [(1, 5), (5, 1)]:
-            dim=[n,m]
-            g=grid_graph(dim)
-            assert_equal(number_of_nodes(g), n*m)
-            assert_true(is_isomorphic(g,path_graph(5)))
-            assert_equal(dim,[n,m])
-
-#        mg=grid_graph([n,m], create_using=MultiGraph())
-#        assert_equal(mg.edges(), g.edges())
-
-        g=grid_graph([range(7,9), range(3,6)])
-        assert_equal(number_of_nodes(g), 2*3)
-        assert_true(is_isomorphic(g, grid_graph([2,3])))
-
-        """Tuple dim arguments of the above tests
-        """
-        for n, m in [(3, 5), (5, 3), (4, 5), (5, 4)]:
-            dim=(n,m)
-            g=grid_graph(dim)
-            assert_equal(number_of_nodes(g), n*m)
-            assert_equal(degree_histogram(g), [0,0,4,2*(n+m)-8,(n-2)*(m-2)])
-            assert_equal(dim,(n,m))
-
-        for n, m in [(1, 5), (5, 1)]:
-            dim=(n,m)
-            g=grid_graph(dim)
-            assert_equal(number_of_nodes(g), n*m)
-            assert_true(is_isomorphic(g,path_graph(5)))
-            assert_equal(dim,(n,m))
-
-        g=grid_graph((range(7,9), range(3,6)))
-        assert_equal(number_of_nodes(g), 2*3)
-        assert_true(is_isomorphic(g, grid_graph((2,3))))
-
-    def test_hypercube_graph(self):
-        for n, G in [(0, null_graph()), (1, path_graph(2)),
-                     (2, cycle_graph(4)), (3, cubical_graph())]:
-            g=hypercube_graph(n)
-            assert_true(is_isomorphic(g, G))
-
-        g=hypercube_graph(4)
-        assert_equal(degree_histogram(g), [0, 0, 0, 0, 16])
-        g=hypercube_graph(5)
-        assert_equal(degree_histogram(g), [0, 0, 0, 0, 0, 32])
-        g=hypercube_graph(6)
-        assert_equal(degree_histogram(g), [0, 0, 0, 0, 0, 0, 64])
-
-#        mg=hypercube_graph(6, create_using=MultiGraph())
-#        assert_equal(mg.edges(), g.edges())
-
     def test_ladder_graph(self):
         for i, G in [(0, empty_graph(0)), (1, path_graph(2)),
                      (2, hypercube_graph(2)), (10, grid_graph([2,10]))]:
@@ -411,28 +335,6 @@ class TestGeneratorClassic():
         assert_equal(g.size(), 2)
         assert_true(g.is_directed())
 
-    def test_periodic_grid_2d_graph(self):
-        g=grid_2d_graph(0,0, periodic=True)
-        assert_equal(dict(g.degree()), {})
-
-        for m, n, G in [(2, 2, cycle_graph(4)), (1, 7, cycle_graph(7)),
-                     (7, 1, cycle_graph(7)), (2, 5, circular_ladder_graph(5)),
-                     (5, 2, circular_ladder_graph(5)), (2, 4, cubical_graph()),
-                     (4, 2, cubical_graph())]:
-            g=grid_2d_graph(m,n, periodic=True)
-            assert_true(is_isomorphic(g, G))
-
-        DG=grid_2d_graph(4, 2, periodic=True, create_using=DiGraph())
-        assert_equal(DG.succ,g.adj)
-        assert_equal(DG.pred,g.adj)
-        MG=grid_2d_graph(4, 2, periodic=True, create_using=MultiGraph())
-        assert_edges_equal(MG.edges(), g.edges())
-
-        gg=grid_2d_graph(range(4), range(2), periodic=True)
-        assert_true(is_isomorphic(gg, g))
-        ggg=grid_2d_graph("abcd", "ef", periodic=True)
-        assert_true(is_isomorphic(ggg, g))
-
     def test_star_graph(self):
         assert_true(is_isomorphic(star_graph(0), empty_graph(1)))
         assert_true(is_isomorphic(star_graph(1), path_graph(2)))
@@ -520,4 +422,4 @@ class TestGeneratorClassic():
         for (block1, block2) in itertools.combinations(blocks, 2):
             for u, v in itertools.product(block1, block2):
                 assert_true(v in G[u])
-                assert_not_equal(G.node[u], G.node[v])
+                assert_not_equal(G.node[u], G.node[v])
\ No newline at end of file
diff --git a/networkx/generators/tests/test_lattice.py b/networkx/generators/tests/test_lattice.py
new file mode 100644
index 000000000..dab3cc4ee
--- /dev/null
+++ b/networkx/generators/tests/test_lattice.py
@@ -0,0 +1,209 @@
+"""Unit tests for the :mod:`networkx.generators.lattice` module."""
+import itertools
+
+from nose.tools import assert_equal
+from nose.tools import assert_true
+from nose.tools import assert_raises
+
+import networkx as nx
+from networkx.testing import assert_edges_equal
+
+
+class TestGrid2DGraph:
+    """Unit tests for :func:`networkx.generators.lattice.grid_2d_graph`"""
+    def test_number_of_vertices(self):
+        m, n = 5, 6
+        G = nx.grid_2d_graph(m, n)
+        assert_equal(len(G), m * n)
+
+    def test_degree_distribution(self):
+        m, n = 5, 6
+        G = nx.grid_2d_graph(m, n)
+        expected_histogram = [0, 0, 4, 2 * (m + n) - 8, (m - 2) * (n - 2)]
+        assert_equal(nx.degree_histogram(G), expected_histogram)
+
+    def test_directed(self):
+        m, n = 5, 6
+        G = nx.grid_2d_graph(m, n)
+        H = nx.grid_2d_graph(m, n, create_using=nx.DiGraph())
+        assert_equal(H.succ, G.adj)
+        assert_equal(H.pred, G.adj)
+
+    def test_multigraph(self):
+        m, n = 5, 6
+        G = nx.grid_2d_graph(m, n)
+        H = nx.grid_2d_graph(m, n, create_using=nx.MultiGraph())
+        assert_equal(list(H.edges()), list(G.edges()))
+
+    def test_periodic(self):
+        G = nx.grid_2d_graph(0, 0, periodic=True)
+        assert_equal(dict(G.degree()), {})
+
+        for m, n, H in [(2, 2, nx.cycle_graph(4)), (1, 7, nx.cycle_graph(7)),
+                        (7, 1, nx.cycle_graph(7)),
+                        (2, 5, nx.circular_ladder_graph(5)),
+                        (5, 2, nx.circular_ladder_graph(5)),
+                        (2, 4, nx.cubical_graph()),
+                        (4, 2, nx.cubical_graph())]:
+            G = nx.grid_2d_graph(m, n, periodic=True)
+            assert_true(nx.could_be_isomorphic(G, H))
+
+    def test_periodic_directed(self):
+        G = nx.grid_2d_graph(4, 2, periodic=True)
+        H = nx.grid_2d_graph(4, 2, periodic=True, create_using=nx.DiGraph())
+        assert_equal(H.succ, G.adj)
+        assert_equal(H.pred, G.adj)
+
+    def test_periodic_multigraph(self):
+        G = nx.grid_2d_graph(4, 2, periodic=True)
+        H = nx.grid_2d_graph(4, 2, periodic=True, create_using=nx.MultiGraph())
+        assert_equal(list(G.edges()), list(H.edges()))
+
+    def test_node_input(self):
+        G = nx.grid_2d_graph(4, 2, periodic=True)
+        H = nx.grid_2d_graph(range(4), range(2), periodic=True)
+        assert_true(nx.is_isomorphic(H, G))
+        H = nx.grid_2d_graph("abcd", "ef", periodic=True)
+        assert_true(nx.is_isomorphic(H, G))
+        G = nx.grid_2d_graph(5, 6)
+        H = nx.grid_2d_graph(range(5), range(6))
+        assert_edges_equal(H, G)
+
+
+class TestGridGraph:
+    """Unit tests for :func:`networkx.generators.lattice.grid_graph`"""
+    def test_grid_graph(self):
+        """grid_graph([n,m]) is a connected simple graph with the
+        following properties:
+        number_of_nodes = n*m
+        degree_histogram = [0,0,4,2*(n+m)-8,(n-2)*(m-2)]
+        """
+        for n, m in [(3, 5), (5, 3), (4, 5), (5, 4)]:
+            dim = [n, m]
+            g = nx.grid_graph(dim)
+            assert_equal(len(g), n*m)
+            assert_equal(nx.degree_histogram(g), [0, 0, 4, 2 * (n + m) - 8,
+                                                  (n - 2) * (m - 2)])
+
+        for n, m in [(1, 5), (5, 1)]:
+            dim = [n, m]
+            g = nx.grid_graph(dim)
+            assert_equal(len(g), n*m)
+            assert_true(nx.is_isomorphic(g, nx.path_graph(5)))
+
+#        mg = nx.grid_graph([n,m], create_using=MultiGraph())
+#        assert_equal(mg.edges(), g.edges())
+
+    def test_node_input(self):
+        G = nx.grid_graph([range(7, 9), range(3, 6)])
+        assert_equal(len(G), 2 * 3)
+        assert_true(nx.is_isomorphic(G, nx.grid_graph([2, 3])))
+
+
+class TestHypercubeGraph:
+    """Unit tests for :func:`networkx.generators.lattice.hypercube_graph`"""
+    def test_special_cases(self):
+        for n, H in [(0, nx.null_graph()), (1, nx.path_graph(2)),
+                     (2, nx.cycle_graph(4)), (3, nx.cubical_graph())]:
+            G = nx.hypercube_graph(n)
+            assert_true(nx.could_be_isomorphic(G, H))
+
+    def test_degree_distribution(self):
+        for n in range(1, 10):
+            G = nx.hypercube_graph(n)
+            expected_histogram = [0] * n + [2 ** n]
+            assert_equal(nx.degree_histogram(G), expected_histogram)
+
+
+class TestTriangularLatticeGraph:
+    "Tests for :func:`networkx.generators.lattice.triangular_lattice_graph`"
+    def test_lattice_points(self):
+        """Tests that the graph is really a triangular lattice."""
+        for m, n in [(2, 3), (2, 2), (2, 1), (3, 3), (3, 2), (3, 4)]:
+            G = nx.triangular_lattice_graph(m, n)
+            N = (n + 1) // 2
+            assert_equal(len(G), (m + 1) * (1 + N) - (n % 2) * ((m + 1) // 2))
+        for (i, j) in G.nodes():
+            nbrs = G[(i, j)]
+            if i < N:
+                assert_true((i + 1, j) in nbrs)
+            if j < m:
+                assert_true((i, j + 1) in nbrs)
+            if j < m and (i > 0 or j % 2) and (i < N or (j + 1) % 2):
+                assert_true((i + 1, j + 1) in nbrs or (i - 1, j + 1) in nbrs)
+
+    def test_directed(self):
+        """Tests for creating a directed triangular lattice."""
+        G = nx.triangular_lattice_graph(3, 4, create_using=nx.Graph())
+        H = nx.triangular_lattice_graph(3, 4, create_using=nx.DiGraph())
+        assert_true(H.is_directed())
+        for u, v in H.edges():
+            assert_true(v[1] >= u[1])
+            if v[1] == u[1]:
+                assert_true(v[0] > u[0])
+
+    def test_multigraph(self):
+        """Tests for creating a triangular lattice multigraph."""
+        G = nx.triangular_lattice_graph(3, 4, create_using=nx.Graph())
+        H = nx.triangular_lattice_graph(3, 4, create_using=nx.MultiGraph())
+        assert_equal(list(H.edges()), list(G.edges()))
+
+    def test_periodic(self):
+        G = nx.triangular_lattice_graph(4, 6, periodic=True)
+        assert_equal(len(G), 12)
+        assert_equal(G.size(), 36)
+        # all degrees are 6
+        assert_equal(len([n for n, d in G.degree() if d != 6]), 0)
+        G = nx.triangular_lattice_graph(5, 7, periodic=True)
+        TLG = nx.triangular_lattice_graph
+        assert_raises(nx.NetworkXError, TLG, 2, 4, periodic=True)
+        assert_raises(nx.NetworkXError, TLG, 4, 4, periodic=True)
+        assert_raises(nx.NetworkXError, TLG, 2, 6, periodic=True)
+
+
+class TestHexagonalLatticeGraph:
+    "Tests for :func:`networkx.generators.lattice.hexagonal_lattice_graph`"
+    def test_lattice_points(self):
+        """Tests that the graph is really a hexagonal lattice."""
+        for m, n in [(4, 5), (4, 4), (4, 3), (3, 2), (3, 3), (3, 5)]:
+            G = nx.hexagonal_lattice_graph(m, n)
+            assert_equal(len(G), 2 * (m + 1) * (n + 1) - 2)
+        C_6 = nx.cycle_graph(6)
+        hexagons = [
+            [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)],
+            [(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)],
+            [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)],
+            [(2, 0), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2)],
+            [(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4)],
+            ]
+        for hexagon in hexagons:
+            assert_true(nx.is_isomorphic(G.subgraph(hexagon), C_6))
+
+    def test_directed(self):
+        """Tests for creating a directed hexagonal lattice."""
+        G = nx.hexagonal_lattice_graph(3, 5, create_using=nx.Graph())
+        H = nx.hexagonal_lattice_graph(3, 5, create_using=nx.DiGraph())
+        assert_true(H.is_directed())
+        pos = nx.get_node_attributes(H, 'pos')
+        for u, v in H.edges():
+            assert_true(pos[v][1] >= pos[u][1])
+            if pos[v][1] == pos[u][1]:
+                assert_true(pos[v][0] > pos[u][0])
+
+    def test_multigraph(self):
+        """Tests for creating a hexagonal lattice multigraph."""
+        G = nx.hexagonal_lattice_graph(3, 5, create_using=nx.Graph())
+        H = nx.hexagonal_lattice_graph(3, 5, create_using=nx.MultiGraph())
+        assert_equal(list(H.edges()), list(G.edges()))
+
+    def test_periodic(self):
+        G = nx.hexagonal_lattice_graph(4, 6, periodic=True)
+        assert_equal(len(G), 48)
+        assert_equal(G.size(), 72)
+        # all degrees are 3
+        assert_equal(len([n for n, d in G.degree() if d != 3]), 0)
+        G = nx.hexagonal_lattice_graph(5, 8, periodic=True)
+        HLG = nx.hexagonal_lattice_graph
+        assert_raises(nx.NetworkXError, HLG, 2, 7, periodic=True)
+        assert_raises(nx.NetworkXError, HLG, 1, 4, periodic=True)
+        assert_raises(nx.NetworkXError, HLG, 2, 1, periodic=True)
diff --git a/networkx/readwrite/tests/test_gml.py b/networkx/readwrite/tests/test_gml.py
index cd9d61b97..42052d7d7 100644
--- a/networkx/readwrite/tests/test_gml.py
+++ b/networkx/readwrite/tests/test_gml.py
@@ -180,6 +180,19 @@ graph   [
         os.close(fd)
         os.unlink(fname)
 
+    def test_labels_are_strings(self):
+        # GML requires labels to be strings (i.e., in quotes)
+        answer = """graph [
+  node [
+    id 0
+    label "1203"
+  ]
+]"""
+        G = nx.Graph()
+        G.add_node(1203)
+        data = '\n'.join(nx.generate_gml(G, stringizer=literal_stringizer))
+        assert_equal(data, answer)
+
     def test_relabel_duplicate(self):
         data = """
 graph
@@ -240,12 +253,29 @@ graph
   name "path_graph(1)"
   node [
     id 0
-    label 0
+    label "0"
     demo "This is &#34;quoted&#34; and this is a copyright: &#169;"
   ]
 ]"""
         assert_equal(data, answer)
 
+    def test_unicode_node(self):
+        node = 'node' + unichr(169)
+        G = nx.Graph()
+        G.add_node(node)
+        fobj = tempfile.NamedTemporaryFile()
+        nx.write_gml(G, fobj)
+        fobj.seek(0)
+        # Should be bytes in 2.x and 3.x
+        data = fobj.read().strip().decode('ascii')
+        answer = """graph [
+  node [
+    id 0
+    label "node&#169;"
+  ]
+]"""
+        assert_equal(data, answer)
+
     def test_name(self):
         G = nx.parse_gml('graph [ name "x" node [ id 0 label "x" ] ]')
         assert_equal('x', G.graph['name'])
@@ -261,7 +291,7 @@ graph
                     gml += ' directed ' + str(int(directed))
                 if multigraph is not None:
                     gml += ' multigraph ' + str(int(multigraph))
-                gml += ' node [ id 0 label 0 ]'
+                gml += ' node [ id 0 label "0" ]'
                 gml += ' edge [ source 0 target 0 ]'
                 gml += ' ]'
                 G = nx.parse_gml(gml)
@@ -274,7 +304,7 @@ graph
                     gml += '  multigraph 1\n'
                 gml += """  node [
     id 0
-    label 0
+    label "0"
   ]
   edge [
     source 0