code: def octagonal_tiling_graph(M: int, N: int) -> nx.Graph:
      
  
      grp = nx.Graph()
      octogon = [[(1, 0), (0, 1)],
                 [(0, 1), (0, 2)],
                 [(0, 2), (1, 3)],
                 [(1, 3), (2, 3)],
                 [(2, 3), (3, 2)],
                 [(3, 2), (3, 1)],
                 [(3, 1), (2, 0)],
                 [(2, 0), (1, 0)]]
      left = [[(1, 0), (1, -1)], [(2, 0), (2, -1)]]
      up = [[(0, 1), (-1, 1)], [(0, 2), (-1, 2)]]
  
      for m in range(M):
          for n in range(N):
              edges = octogon
              if n != 0:
                  edges = edges + left
              if m != 0:
                  edges = edges + up
  
              for (x0, y0), (x1, y1) in edges:
                  grp.add_edge((m*4+x0, n*4+y0), (m*4+x1, n*4+y1))
  
      positions = {node: node for node in grp}
      nx.set_node_attributes(grp, positions, 'positions')
  
      return grp
docs: Return the octagonal tiling graph (4.8.8, truncated square tiling,
      truncated quadrille) of MxNx8 nodes
  
      The 'positions' node attribute gives node coordinates for the octagonal
      tiling. (Nodes are located on a square lattice, and edge lengths are
      uneven)