Update: obviously incorrect statements are removed.
I think WM doesn't include DualGraph-function because it is usually multigraph and non unique.
As far as I have read in Wikipedia a dual graph $G^*$ depends on planar embedding of $G$. If you ask for a some dual graph for a given graph, then here is my brute force heuristic algorithm. It is based on algorithm (given below) which yields the faces (and I do not guarantee that it is correct):
The pseudo-code is as follows:
pseudofaces={} graph0 = spanning tree of graph edges0 = all edges of graph which are not in graph0 - While
edges0 is non empty - find
edge from edges0 such that its vertices have shortest path in graph0 - Append
path to pseudofaces - Remove
edge from edges0 - Add
edge to graph0
- return
pseudofaces
Here is Mathematica implementation.
(* simple Kruskal's algorithm without sorting *) SpanningTree[graph_] := Module[{label, edges = EdgeList[graph]}, Pick[edges, Table[If[UnsameQ[#1, #2], #2 = #1; True, False] & @@ Sort[label /@ edge], {edge, edges}]]]
It yields a spanning tree:
graph = GridGraph[{5, 9}]; HighlightGraph[graph, SpanningTree[graph]]

makeEges[list_] := Sort /@ UndirectedEdge @@@ Partition[list, 2, 1] (* FindFace : finds the smallest cycle. This cycle is a pseudo-face. *) FindFace[graph_, edges_] := MapAt[makeEges, First@SortBy[Transpose[{FindShortestPath[graph, Sequence @@ #] & /@ edges, edges}], Composition[Length, First]], 1] (* Append analyzed edge to a graph *) appendEge[graph_, edge_] := Graph@Append[EdgeList[graph], edge] iteration[graph_, {}] := $faces; iteration[graph_, edges_] := iteration[AppendTo[$faces, Flatten[#]]; appendEge[graph, Last@#], DeleteCases[edges, Last@#]] &[FindFace[graph, edges]] (* Faces : returns all pseudo-faces of a graph *) Faces[graph_] := Block[{$faces = {}, tree = SpanningTree[graph]}, iteration[Graph[tree], Complement[EdgeList[graph], tree]]]
It works as follows:
faces = Faces[graph]; Manipulate[HighlightGraph[graph, faces[[n]]], {n, 1, Length@faces, 1}]

The last step is to construct the dual graph from the faces:
shareQ[set1_, set2_] := Length@Intersection[set1, set2] > 0; (* connecting all faces *) innerDualEdges[faces_] := Join @@ Table[ Table[If[shareQ @@ faces[[{i, j}]], UndirectedEdge[i, j], Unevaluated@Sequence[]], {j, i + 1, Length[faces]}], {i, 1, Length[faces] - 1}] (* next two functions find the faces on the boundary *) boundaryEdges[faces_] := Module[{edges = Join @@ faces}, Select[Union[edges], (Count[edges, #] == 1) &]] boundaryFaces[faces_, outerEdges_] := Select[Range@Length@faces, shareQ[faces[[#]], outerEdges] &]; (* altogether *) DualGraph[faces_] := Graph[DeleteDuplicates@Join[innerDualEdges[faces], Thread@UndirectedEdge[0, boundaryFaces[faces, boundaryEdges[faces]]]]]
That's all.
DualGraph[faces]

If you do not consider the outer space as a face, then you can ask:
Graph[innerDualEdges[faces]]

You can also compare it with Mathematica internals:
IsomorphicGraphQ[GraphData["IcosahedralGraph", "DualGraph"], DualGraph[Faces[GraphData["IcosahedralGraph"]]]] (* True *)
Full notebook is available here.