Group Abstract Group Abstract

Message Boards Message Boards

The Sierpinski triangle page to end most Sierpinski triangle pages (Part 1)

Posted 2 months ago

Part 2: https://community.wolfram.com/groups/-/m/t/3546261

Constructing the Sierpinski triangle

Throughout my years playing around with fractals, the Sierpinski triangle has been a consistent staple. The triangle is named after Wacław Sierpiński and as fractals are wont the pattern appears in many places, so there are many different ways of constructing the triangle on a computer.

All of the methods are fundamentally iterative. The most obvious method is probably the triangle-in-triangle approach. We start with one triangle, and at every step we replace each triangle with 3 subtriangles:

enter image description here
enter image description here

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

draw[n_] := Graphics[{EdgeForm[Black],
    Nest[next, N@axiom, n]}];

This triangle-in-triangle method strikes me as a disguised Lindenmayer system. L-systems are iterative symbol-based replacement mechanisms. There are a variety of more explicit L-system constructions for the triangle, such as the 'arrowhead' L-system (also see my L-systems program):

enter image description here

axiom = {A};
rules = {A -> {B, R, A, R, B}, B -> {A, L, B, L, A}};
conversions = {A -> forward, B -> forward, L -> left, R -> right};

(* state transformations *)
forward[{z_, a_}] := {z + E^(I a), a};
left[{z_, a_}] := {z, a + 2 Pi/6};
right[{z_, a_}] := {z, a - 2 Pi/6};

draw[n_] := Module[{program, zs},
  program = Flatten[Nest[# /. rules &, axiom, n]] /. conversions;
  zs = First /@ ComposeList[program, N@{0, 0}];
  Graphics[Line[{Re[#], Im[#]} & /@ First /@ Split[zs]]]];

There's the cellular automata approach, where the 'world' is a single array of bits and at each "instant" we alter a bit based on the state of it and its neighbors. If we plot the evolution of Rule 22 (and others), we get these patterns:

enter image description here

draw[n_] := ArrayPlot[CellularAutomaton[22, {{1}, 0}, n]];

There are bound to be many elementary number-theoretic constructions of the Sierpinski triangle given that it looks like a percolation pattern (as in the cellular automata above). The Wikipedia article mentions that it appears in Pascal's Triangle when differentiating between even and odd numbers. Sure enough:

enter image description here

enter image description here

enter image description here

draw[n_] := Module[{t},
    t = Table[Binomial[m, k], {m, 0, n}, {k, 0, m}];

    Column[Row[#, " "] & /@ t, Center] /. {
        x_?EvenQ :> Style[Framed[x], LightGray],
        x_?OddQ :> Framed[x]}];

If we look at these Pascal forms and reverse engineer the parity rules, we get Rule 22. Though it might depend on what exactly you're reverse engineering. We can generalize from even/odd to other moduli:

Pascal's triangle mod 4:

enter image description here

Pascal's triangle x≡2 (mod 4):

enter image description here

draw[n_] := Module[{t},
   t = Table[Mod[Binomial[m, k], 4], {m, 0, n}, {k, 0, m}];

   Column[Row[#, " "] & /@ t, Center] /. x_?NumberQ :>
     Style[Framed["  ", FrameStyle -> None],
      Background -> ColorData[3][2 + x]]];

The Wikipedia article for Pascal's triangle mentions that we can construct a 'Pascal matrix' using the matrix exponential:

enter image description here

"Ah, that makes sense." You say. Indeed, but what's cool is that we then have a pedantic way of specifying the Sierpinski triangle:

enter image description here

This equation is in what's called "straight ballin'" form, and it gives us a fancy way of producing the triangle:

enter image description here

draw[n_] := ArrayPlot[Mod[MatrixExp[DiagonalMatrix[Range[n], -1]], 2]];

Heawt deaowg /drawl. It's not very performant though. The following is faster and arguably more elegant:

enter image description here

draw[n_] := ArrayPlot[Mod[Array[Binomial, {n, n}, 0], 2]];

Along these lines, it shouldn't be surprising that the Sierpinski pattern appears in other combinatorial expressions, such as the Stirling numbers:

enter image description here

draw[n_] := Grid[Partition[#, 2]] &@
   Table[ArrayPlot[Mod[Array[f, {n, n}], 2],
     PlotLabel -> f, FrameStyle -> LightGray],
    {f, {Binomial, StirlingS1, StirlingS2, Multinomial}}];

If we treat the rows produced by these combinatorial functions as arrays of bits, what sequence of numbers do the bits represent? There's a variety of ways to interpret this question, but here's one assortment:

enter image description here

draw[n_] := With[{dropZeros = # /. {x__, 0 ..} :> {x} &},
   MatrixForm[Table[Flatten[
      {f, FromDigits[dropZeros[#], 2] & /@ Mod[Array[f, {n, n}, 0], 2], "\[Ellipsis]"}],
     {f, {Binomial, StirlingS1, StirlingS2, Multinomial}}]]];

The first, second, and fourth sequences are versions of each other, tautologically described in OEIS as A001317. The sequence for the Stirling numbers of the second kind doesn't seem to have any fame, but if you shift its bits around you can find A099901 and A099902.

The Wikipedia article for the Sierpinski triangle mentions its appearance in logic tables such as this one. If you stare blankly at that image long enough you'll notice it's a set-inclusion table. Take the subsets of a set and pair them against each other under set-inclusion (is subset A a subset of subset B?) and you will get that table.

Personally that's a more interesting interpretation than the binary logic one, though the apparent distinction between these subjects is likely just a matter of perspective. Another set-related Sierpinski pattern I found is set disjunction (when sets have no common elements):

enter image description here

isSubset[a_, b_] := Union[a, b] == b;
areDisjoint[a_, b_] := Intersection[a, b] == {};

subs[0] = {{}};
subs[n_] := Module[{s = subs[n - 1]},
   Join[s, Append[#, n] & /@ s]];

draw[n_] := Grid[List[Table[
     ArrayPlot[Boole[Outer[f, subs[n], subs[n], 1]],
      PlotLabel -> f, FrameStyle -> LightGray],
     {f, {isSubset, areDisjoint}}]]]

One thing I noticed is that these set patterns depend on the order in which you place the subsets. It has to be the same order that you would get if you were constructing the subsets iteratively. I also wasn't able to find a straightforward ranking function that would order the sets into this iterative sequence. Mathematica's Combinatorica package refers to it as the binary ordering. I think I'm starting to understand what Gandalf meant when he said

"The Sierpinski triangle cannot-be wrought without heed to the creeping tendrils of recursion. Even the binomial coefficient has factorials which are recursively defined."

MathWorld mentions a broader context for why binary logic can be used in the construction of the Sierpinski triangle. Namely the Lucas correspondence theorem which states that given two numbers written in a prime base,

enter image description here

We can get their binomial coefficient modulo that prime by performing binomial coefficients digit-wise and multiplying the results.

enter image description here

enter image description here

TraditionalForm[Grid[Outer[
    HoldForm[Binomial[##]] == Binomial[##] &,
  {0, 1}, {0, 1}]]]

The factorial definition is interesting in this case.

enter image description here

enter image description here

binaryBinomial[a_, b_] := Module[{bits},
   bits = IntegerDigits[{a, b}, 2];
   bits = PadLeft[#, Max[Length /@ bits]] & /@ bits;

   Boole[FreeQ[Transpose[bits], {0, 1}]]];

draw[n_] := MatrixPlot[
   Array[binaryBinomial, {2^n, 2^n}, 0],
   Frame -> None];

There's a lot of related patterns:

enter image description here

binaryWhoKnows[a_, b_] :=
  DigitCount[BitOr[a, BitNot[b]], 3, 1];

draw[n_] := MatrixPlot[
   Array[binaryWhoKnows, {2^n, 2^n}, 0],
   Frame -> False];

And look what I found!

enter image description here

If we're looking for a one- or two-liner that's one- or two-linear in languages beside Mathematica, we'd have trouble doing better than the chaos game algorithm, which goes like this:

1 start at any point. call it p
2 pick one of the three vertices at random
3 find the point halfway between p and that vertex
4 call that point p and draw it
5 goto 2

enter image description here

vertices = {{0, 0}, {1, Sqrt[3]}/2, {1, 0}};

draw[numPoints_] := Graphics[{
    PointSize[0], Opacity[.1],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0},
      RandomChoice[N@vertices, numPoints]]]}];

vertices = {{0, 0}, {1, Sqrt[3]}/2, {1, 0}};

draw[numPoints_] := Graphics[{
    PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0},
      RandomChoice[N@vertices, numPoints]]]},
   ImageSize -> 2 1280];

draw[50000000] // ImageAdjust // ImageResize[#, Scaled[1/2]] &

The chaos game doesn't render as crisply as a lot of the other methods, especially without transparency effects, but it has the advantage of being highly performant. It runs about one million points per second on my laptop. Mind you this is with Mathematica's RNG, which is not your everyday math.rand().

One thing I realized is that the randomness isn't actually a necessary aspect of the general algorithm. It's used as an approximating force (or perhaps something a bit more subtle than that). Otherwise with enough spacetime on your computer you can just perform all possible half-distancings:

enter image description here

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> { 
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

draw[n_] := Graphics[{PointSize[0], Opacity[.1],
    Nest[next, N@axiom, n] /. Polygon -> Point}];

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

points[n_] := DeleteDuplicates[Flatten[
    Nest[next, axiom, n] /. Polygon -> Sequence, n]];

points[5]

These images look basically the same. Not surprising since they're both point-based. But I gander the distinction between these two algorithms may have been more than just an issue of curiousity 20 years ago. I still remember my first computer, the alien-processored TI-85, chugging away furiously for a good half a minute before the triangle became clear.

Notice that this specific algorithm is actually just a minor modification of the triangle-in-triangle algorithm. The difference is that polygon vertices are here rendered as points. This modification is possible because of Mathematica's symbolic semantics. The symbol Polygon is meaningless until it's processed by the Graphics function. Until then, we can perform structural operations such as replacing it by the Point symbol. In fact the following is completely valid:

axiom = triangle[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. triangle[{p1_, p2_, p3_}] :> {
     triangle[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     triangle[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     triangle[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

draw[n_] := Graphics[Nest[next, N@axiom, n] /.  triangle :> Polygon ];

triangle here doesn't have any meaning, ever, until we replace it:

triangle :> Polygon

:

enter image description here

triangle :> Line

:

enter image description here

triangle[pts_] :> Line[RandomChoice[pts, RandomInteger[{2, 3}]]]

:

enter image description here

triangle[pts_] :> Disk[Mean[pts], 1/2^(n + 1)]

:

enter image description here

triangle[pts_] :> Sphere[Append[Mean[pts], 0], 1/2^(n + 1)]

:

enter image description here

Sidenote. What do you get when you methodically build a Lisp on top of symbolic replacement semantics? You get the Mathematica language, of which Mathematica and Mathics appear to be the only incarnations.

Let's say you forgot how to multiply matrices. Well, just type in some symbols and see the results empirically:

{{a, b}, {c, d}} . {{e, f}, {g, h}} // MatrixForm

enter image description here

If that's still confusing, you can use strings, colored text, graphics, images, etc. instead of symbols. In fact if you have a Tron zapper you can even zap your cat into Mathematica and have him fill up one of those matrix slots, for the advancement of science.

enter image description here

kitty = WolframAlpha["cat picture", "PodImages"][[2]];

(* see http://mathematica.stackexchange.com/a/8291/950 *)
text = First[First[ImportString[ExportString[
      Style["IM IN UR MATRIX...", FontFamily -> "Impact"], "PDF"]]]];

sym = Framed[Overlay[{kitty,
     Graphics[{EdgeForm[Black], White, text}, ImageSize -> 150,
     PlotRangePadding -> 0]}], FrameStyle -> LightGray];

{{a, b}, {Magnify[sym, 1/2], d}} . {{e, f}, {g, h}} // MatrixForm

There's poor Mr. Scruples. Our neighbor will miss him.

The exponential identity for the Pascal matrix is not difficult to understand based on the series definition of the exponential function:

enter image description here

You could work out the matrix arithmetic by hand, or you could do this:

power[n_, p_] := MatrixPower[
    DiagonalMatrix[ToString /@ Range[n], -1], p] // MatrixForm;

Grid[Partition[Table[power[6, p], {p, 1, 6}], 3]] /. 0 -> "\[CenterDot]"

enter image description here

enter image description here

Powers of matrices have a well-known interpretation in terms of graph walks/probabilities. I didn't find anything interesting along this line though. What about graphs represented by the Sierpinski matrix itself?

enter image description here

Those were more interesting:

enter image description here

graph[n_] := GraphPlot3D[
   Mod[Array[Binomial, {n, n}, 0], 2],
   Method -> "HighDimensionalEmbedding", Boxed -> False,
   VertexRenderingFunction -> ({White, Sphere[#, .05]} &),
   PlotStyle -> {Thick, Hue[2/3, 2/3, 2/3]}];

Note this is a 3D graph layout. It has some pretty symmetries. I did some tiresome work trying to figure out what polyhedron it might be.

Tooltip[PolyhedronData[#], #] & /@ Select[
  PolyhedronData[], PolyhedronData[#, "VertexCount"] == 14 &]

After much time, I find. It's the tetrakis hexahedron:

enter image description here

Graphics3D[{Opacity[.94], FaceForm[Gray],
  PolyhedronData["TetrakisHexahedron", "Faces"]},
 Lighting -> "Neutral", Boxed -> False]

I'm certain it's this particular figure because we can just build a graph from its vertex data and then do a graph isomorphism check. And look, we can run this polyhedron grapherizer willy-nilly allabouts, like on the Archimedean solids:

enter image description here

polyGraph[poly_, options___] :=
  Graph[UndirectedEdge @@@ PolyhedronData[poly, "EdgeIndices"], options];

sierpinskiMatrixGraph[n_, options___] := Module[{a},
   (*symmetrize and remove self-loops to allow general isomorphism
   comparison. note that we remove the "inner" vertex since
   we're comparing the "external" geometry as rendered*)
   a = Mod[Array[Binomial, {n, n}, 1], 2];
   AdjacencyGraph[a + Transpose[a] /. 2 -> 0, options]];

IsomorphicGraphQ[polyGraph["TetrakisHexahedron"], sierpinskiMatrixGraph[14]]
IsomorphicGraphQ[polyGraph["CumulatedCube"], sierpinskiMatrixGraph[14]]

Grid[Partition[#, 4]] &[
  polyGraph[#, PlotLabel -> PolyhedronData[#, "Name"]] & /@
   PolyhedronData["Archimedean"]]

Here are the first few powers of the Sierpinski matrix:

enter image description here

power[n_, p_] := MatrixPower[
   Mod[Array[Binomial, {n, n}, 0], 2], p];

Grid[Partition[#, 2]] &@
 Table[
  MatrixForm[power[16, p] /. 0 -> "\[CenterDot]"],
  {p, 1, 4}]

There's a lot of patterns here. For one, the powers of the Sierpinski matrix are Sierpinski matrices! This isn't necessarily interesting though. The powers of a triangular matrix are going to be triangular. But the numbers follow a curious sequence of powers. For example, in the third power we have the sequence {1, 3, 3, 9, 3, 9, 9, 27, 3, ... }. And this sequence occurs in every column and every row of the matrix, if you hop over the zeros. We can normalize the powers to find:

enter image description here

power[n_, p_] := MatrixPower[
   Mod[Array[Binomial, {n, n}, 0], 2], p];

MatrixForm /@ Table[
  IntegerExponent[power[16, p], p] /. Infinity -> "\[CenterDot]",
  {p, 2, 4}]

This is the sequence in terms of the exponent, and it applies to each power of the Sierpinski matrix, including the first power. For example, 3 to the power of each of {0, 1, 1, 2, 1, 2, 2, 3, 1, ...} is {1, 3, 3, 9, 3, 9, 9, 27, 3, ...}. This power sequence appears in OEIS as the number of ones in the binary representation of n, among other descriptions.

Here is a totally practical application of all of this. A pretty array of buttons:

enter image description here

power[n_, p_] := MatrixPower[Transpose@Reverse@
     Mod[Array[Binomial, {n, n}, 0], 2], p];

Grid[Partition[#, 4]] &@
 With[{m = "you, are now infused, with, the power of, dot, dot, dot... "},
  Array[Function[p,
    Button[Rotate[#, -Pi/4], Speak[m <> ToString[p]]] &@
     Rasterize@MatrixPlot[IntegerExponent[power[2^p, 4], 10],
       ImageSize -> 94, Frame -> None, PlotRangePadding -> 0]],
   8]] 

The Towers of Hanoi is a variation on the sticks-in-holes game where instead of putting sticks in holes, you put holes around sticks. Thus the game is ultimately a quaint philosophical remark on the roles of the sexes. But for our purposes there is a claim on the internets that the states of the game form Sierpinski triangle-like graphs:

enter image description here

enter image description here

enter image description here

enter image description here

validQ[s_state] := And @@ Less @@@ s;

(*do all physically possible moves. remove invalid moves afterward.*)
neighbors[states : {__state}] := Select[#, validQ] &@
   DeleteDuplicates@Flatten@
     Table[Module[{st2 = st},

       If[Length[st2[[from]]] > 0,
        PrependTo[st2[[to]], st2[[from, 1]]];
        st2[[from]] = Rest[st2[[from]]]];

       If[st2 =!= st && validQ[st2],
        Sow@UndirectedEdge[st, st2]];

       st2], {st, states}, {to, Length[st]}, {from, Length[st]}];

toStyle[expr_] := expr /. s_state :> (
     Property[Tooltip[s, MatrixForm /@ List @@ s],
      VertexStyle -> {EdgeForm[None],
        ColorData[3][1 + Length[s] - Count[s, {}]]}]);

hanoiGraph[s_, options___] := Module[{vertices, edges, n},
   n = Count[s, _Integer, Infinity];
   {vertices, {edges}} = Reap[Nest[neighbors, {s}, 2^n]];

   SetAttributes[UndirectedEdge, Orderless];
   Graph[toStyle[vertices], DeleteDuplicates[edges],
    options(*,GraphLayout->"SpringEmbedding"*)(*,
    VertexShapeFunction->(Style[#,7,Black]&@
    Text[Row[MatrixForm/@List@@#2],#1]&)*)]];

hanoiGraph[state[{}, {}, Range[4]],
 Epilog -> Inset[Rotate[Style["F-", 300, Bold, Red, Opacity[.65]], Pi/7]]]

Which, as you can see, is a lie if I've ever seen one (internets, you are now on notice). Then again, if you fiddle with the layout and you squint a bit, you can kinda see it, but it's the sort of Sierpinski triangle that Maddox would stamp a huge red F over. To be clear, each vertex represents a single state of the game, and vertices are connected if there is a legal move between those states.

The nice thing about this algorithm is that at each step it just blindly constructs all possibilities, which is easy, and then afterwards removes the ones that aren't valid, which is also easy. Point being it works in broad strokes. And at the end of it we have a map to follow if we ever get stuck. You can do this sort of thing for all sorts of things, like say Rubik's cube. Though I don't know if the combinatorics are favorable in its case. The Towers of Hanoi can be played with more than three sticks:

hanoiGraph[state[{}, {}, {}, Range[4]]]

:

enter image description here

hanoiGraph[state[{}, {}, {}, {}, Range[4]]]

:

enter image description here

hanoiGraph[state[{}, {}, Range[3], Range[3]]]

:

enter image description here

hanoiGraph[state[{}, {}, {1}, Range[3]]]

:

enter image description here

hanoiGraph[state[{}, {}, {2}, Range[3]]]

enter image description here

hanoiGraph[state[{}, {}, {3}, Range[3]]]

:

enter image description here

validQ[s_state] := And @@ Equal @@@ s;
hanoiGraph[state[{}, {}, ConstantArray[1, 5]]]

:

enter image description here

validQ[s_state] := And @@ LessEqual @@@ s;
hanoiGraph[state[{}, ConstantArray[2, 3], ConstantArray[1, 3]]]

:

enter image description here

validQ[s_state] := And @@ Equal @@@ s;
hanoiGraph[state[{}, ConstantArray[2, 3], ConstantArray[1, 3]]]

:

enter image description here

"WHAT THE HELL IS THAT", you say. Indeed, it's messy because it's a low-D rendering. We can also play variations of the game that allow multiple holes of the same diameter, or variations where we adjust the rules a bit. In higher dimensions you can see the structure better:

enter image description here

enter image description here

validQ[s_state] := And @@ Less @@@ s;

(*do all physically possible moves. remove invalid moves afterward.*)
neighbors[states : {__state}] := Select[#, validQ] &@
   DeleteDuplicates@Flatten@
     Table[Module[{st2 = st},

       If[Length[st2[[from]]] > 0,
        PrependTo[st2[[to]], st2[[from, 1]]];
        st2[[from]] = Rest[st2[[from]]]];

       If[st2 =!= st && validQ[st2],
        Sow@UndirectedEdge[st, st2]];

       st2], {st, states}, {to, Length[st]}, {from, Length[st]}];

hanoiGraph[s_, options___] := Module[{vertices, edges, n},
    n = Count[s, _Integer, Infinity];
    {vertices, {edges}} = Reap[Nest[neighbors, {s}, 2^n]];

    SetAttributes[UndirectedEdge, Orderless];
    Graph[DeleteDuplicates[edges]]];

toStyle3D[g_] := Module[{st = VertexList[g][[#2]]},
    Tooltip[{ColorData[3][1 + Length[st] - Count[st, {}]],
      Opacity[1], Sphere[#1, .045]}, MatrixForm /@ List @@ st]] &;

hanoiGraph3D[s_, options___] := Module[{g = hanoiGraph[s]},
   GraphPlot3D[g,
    Method -> "SpringElectricalEmbedding",
    VertexRenderingFunction -> toStyle3D[g], options, Boxed -> False,
    PlotStyle -> {Lighter[Blue](*,Opacity[.5]*)}]];

{vv, vp} = {{0, 0, 1}, {2, 0, 0}};
Animate[
 hanoiGraph3D[state[{}, {}, {}, Range[4]],
  Lighting -> "Neutral", SphericalRegion -> True,
  ViewVertical -> Dynamic[vv], Boxed -> False,
  ViewPoint -> Dynamic[RotationTransform[\[Theta], vv][vp], (vp = #1) &]],
 {\[Theta], 2 Pi, 0}, SynchronousUpdating -> False]

Although the 3-stick Hanoi graphs merely resemble Sierpinski graphs, it would be folly to ignore that resemblance given the thread of recursion that runs through both. We can create Sierpinski graphs easily, by once again reusing our polygon-in-polygon approach and this time replacing the Polygon[{p1, p2, p3}] expression with {p1 <-> p2, p2 <-> p3, p3 <-> p1}:

enter image description here

enter image description here

enter image description here

enter image description here

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

draw[n_] := Graph@Flatten@
    Nest[next, N@axiom, n] /. Polygon[pts_] :>
     Sequence @@ UndirectedEdge @@@ Partition[pts, 2, 1, 1];

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

(*Orderless attribute not necessary here. but it causes the particular permutation
 of the edge list that results in the particular layout*)

(*triple-click on "DynamicSetting" below, Right-click -> Evaluate in Place*)
DynamicSetting[SetterBar[1, {SetAttributes, ClearAttributes}]][UndirectedEdge, Orderless];
draw[n_] := Graph[#, VertexSize -> .05, GraphLayout -> "SpringEmbedding"] &@
    Flatten@Nest[next, N@axiom, n] /. Polygon[pts_] :>
     Sequence @@ UndirectedEdge @@@ Partition[pts, 2, 1, 1];

g = draw[5]
GraphPlot3D[g, VertexRenderingFunction -> None,
   PlotStyle -> Hue[2/3, 2/3, 2/3(*,1/2*)],
   Method -> "SpringEmbedding", Boxed -> False]

There's the Sierpinski triangle I know and love; the graph of. You might think it doesn't look good. But you don't realize it's a Sierpinski triangle wearing a cape made of Sierpinski triangles. Not only does it not not look good, it looks completely badass. Because we're using the coordinates of the points as vertices, we can straightforwardly recover the regular Sierpinski layout:

enter image description here

enter image description here

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

draw[n_] := Module[{edges},
   edges = Flatten@Nest[next, N@axiom, n] /. Polygon[pts_] :>
      Sequence @@ UndirectedEdge @@@ Partition[pts, 2, 1, 1];

   Graph[edges, VertexCoordinates -> VertexList[Graph[edges]],
    VertexSize -> .25]];

The point of spending 1 or 2 LOC's worth of developer time to convert our geometric Sierpinski triangle into a graph is so that we can ask questions about the graph. Like for example, what are its Hamiltonicness and Eulerity quotients? What is the average degree of the graph, in Celsius? In Kelvin? Frankly most of these questions are boring, and I don't really know anything about graphs. But here is a picture of the line graphs of the first few Sierpinski iterations:

enter image description here

enter image description here

enter image description here

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

draw[n_] := Module[{edges},
   edges = Flatten@Nest[next, N@axiom, n] /. Polygon[pts_] :>
      Sequence @@ UndirectedEdge @@@ Partition[pts, 2, 1, 1];

   Graph[edges, VertexCoordinates -> VertexList[Graph[edges]],
    VertexSize -> .25]];

g = draw[2];
LineGraph[g]

cycle = RandomChoice[{FindHamiltonianCycle, FindEulerianCycle}][g][[1]];

Animate[
 HighlightGraph[g, Graph[cycle[[1 ;; n]]],
  EdgeShapeFunction -> (Line[#1] &),
  VertexShapeFunction -> None,
  GraphHighlightStyle -> "DehighlightHide"],
 {n, 1, Length[cycle], 1}, AnimationRate -> 1]

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

sierpinskiGraph[n_] := Graph@Flatten@
     Nest[next, N@axiom, n] /. Polygon[pts_] :>
    Sequence @@ UndirectedEdge @@@ Partition[pts, 2, 1, 1];

dg = Mean[VertexDegree[sierpinskiGraph[8]]];

(*if live in us, above comes out in farenheit, so have to convert*)
us = Graphics[CountryData["UnitedStates", "Polygon"], ImageSize -> 8000];
If[Rasterize[us] === Rasterize[Show[us, Graphics[{PointSize[0], Point[Reverse@FindGeoLocation[]]}]]],
 WolframAlpha[ToString[dg, InputForm] <> " degrees f in celcius", {{"Result", 1}, "NumberData"}],
 dg]

Also the minimal zig zag of the triangle, notable because it looks like a bunch of resistors (no doubt the inspiration for certain papers). And its minimal criss cross. I don't really see anything though. Do you see anything? I don't see anything. These graphs just vertices and edges to me.

They do raise a question though. What game (or what anything) does the Sierpinski graph represent? I wasn't able to produce the Sierpinski triangle from any variation of the Hanoi game beyond the first couple of trivial iterations. In any case, through the extensive research I've done here I've found that layered graph layouts are pretty:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

validQ[s_state] := And @@ Less @@@ s;
neighbors[states : {__state}] := Select[#, validQ] &@
   DeleteDuplicates@Flatten@
     Table[Module[{st2 = st},
       If[Length[st2[[from]]] > 0,
        PrependTo[st2[[to]], st2[[from, 1]]];
        st2[[from]] = Rest[st2[[from]]]];
       If[st2 =!= st && validQ[st2],
        Sow@UndirectedEdge[st, st2]];
       st2], {st, states}, {to, Length[st]}, {from, Length[st]}];

hanoiGraph[s_] := Module[{vertices, edges, n},
   n = Count[s, _Integer, Infinity];
   {vertices, {edges}} = Reap[Nest[neighbors, {s}, 2^n]];
   SetAttributes[UndirectedEdge, Orderless];
   Graph[DeleteDuplicates[edges]]];

axiom = Polygon[{{0, 0}, {1, Sqrt[3]}/2, {1, 0}}];
next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

(*certain layout depends on this ordering*)
(*next[prev_]:=prev/.Polygon[pts_]:>(
Polygon[ScalingTransform[1/2{1,1},#][pts]]&/@pts);*)

sierpinskiGraph[n_] := Graph@Flatten@
    Nest[next, N@axiom, n] /. Polygon[pts_] :>
     Sequence @@ UndirectedEdge @@@ Partition[pts, 2, 1, 1];

draw[g_] := LayeredGraphPlot[g,
   EdgeRenderingFunction -> ({CapForm["Round"], Line[#]} &),
   VertexRenderingFunction -> None,
   PlotStyle -> {Thickness[.01], Black}];

draw /@ {hanoiGraph[state[{}, {}, Range[3]]], sierpinskiGraph[3]}

Chaos

One of the nice things about the chaos game algorithm is that we can easily generalize it to more than three points. To begin with, we can place equiangular points on a circle using cos and sin (see also my screwing around with polygons).

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

draw[v_, numPoints_] := Module[{vertices},
  vertices = {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

  Graphics[{PointSize[0], Opacity[.1],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0},
      RandomChoice[N@vertices, numPoints]]]}]];

These are drawn with 10 million points. The last two are drawn with 50 million points. The key to the quality here is giving the points transparency so that varying degrees of overlap/nearness form different shades. Higher vertex counts clearly have some structure, but it becomes blurry for one reason or another. You might be able to pull out the structure better with a more methodical approach and some image trickery.

If you play around with pentagons in a vector editor (Mathematica itself has basic vector editing capabilities), you will find this figure:

enter image description here

I've highlighted one of the inner pentagons. You can see that this figure reproduces the faded stellation pattern in the center of the chaos game rendition. So the chaos game algorithm remains consistent in this geometric fashion: At each vertex of the figure, attach a copy of the larger figure, but with sidelength one-half of the original (note the red edge in the above image).

This also explains why the 4-vertex rendering is a block. And since we now have the geometric rule, we can turn to an explicit geometric construction to see if we can make the structure of these chaos games clearer. After some hiccups, I was able to get something working:

enter image description here

SetAttributes[toXY, Listable];
toXY[z_] := {Re[z], Im[z]};

ring[c_, r_, 0] := c;
ring[c_, r_, depth_] := Module[{zs},
   zs = c + r E^(I 2 Pi Range[3]/3);
   ring[c + # Normalize[# - c], r/2., depth - 1] & /@ zs];

Graphics[Rotate[{Opacity[.95], LightGray, EdgeForm[Black],
   Polygon /@ toXY /@ Level[ring[0, 1, 5], {-2}]}, Pi]]

enter image description here

enter image description here

enter image description here

enter image description here

draw[v_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (2. Pi Range[v]/v);

     If[depth == 0, Polygon[ps],
      ring[(c + #)/2, r/2, depth - 1] & /@ ps]];

   Graphics[{Transparent,
     EdgeForm[{Opacity[.28], Black}],
     ring[0., 1., n]}]];

The snowflake has all sorts of symmetries, probably because 6 = 2 × 3. It even has 3D grids and cubes. It's an infinite cubic matryoshka snowflake. And there is a lot of amazing detail in these drawings.

At this point I should mention that all of the code snippets on this page are self-contained. If you have Mathematica you can copy-paste this and start producing these figures.

The chaos game has another generalization. Instead of moving halfway between the active point and the randomly-chosen vertex, we can move 1/3rd of the way, or 3/2 of the way, etc:

enter image description here

draw[v_, df_, numPoints_: 10000] := Module[{vertices},
   vertices = {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

   Graphics[{PointSize[0], Opacity[.5],
     Point[FoldList[df, N@{0, 0},
       RandomChoice[N@vertices, numPoints]]]}]];

functions = Function[r, (#1 + #2) r &] /@ {1, .96, .7, .6, .5, .2};

Grid[Join[
  {TraditionalForm[#[a, b]] & /@ functions},
  Table[draw[v, df], {v, 3, 6}, {df, functions}]]]

In the case where we're just adding the numbers, we get a normal n-directional random walk. Of course, the geometric approach has its own similar generalization:

enter image description here

draw[v_, df_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (2. Pi Range[v]/v);

     If[depth == 0, Polygon[ps],
      ring[(c + #)/2, df[0, r], depth - 1] & /@ ps]];

   Graphics[{Transparent,
     EdgeForm[{Opacity[.26], Black}],
     ring[0., 1., n]}]];

functions = Function[r, (#1 + #2) r &] /@ {1, .7, .6, .5, .35, .2};

Grid[Join[
  {TraditionalForm[#[a, b]] & /@ functions},
  Table[draw[v, df, 4], {v, 3, 6}, {df, functions}]]]

One of the things you might try to do, if you're me, is adjust the ratio until the corners match up:

enter image description here

drawGeom[v_, ratio_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (2. Pi Range[v]/v);

     If[depth == 0, Polygon[ps],
      ring[(c + #)/2, r ratio, depth - 1] & /@ ps]];

   Graphics[{Transparent,
     EdgeForm[{Opacity[.28], Black}],
     ring[0., 1., n]}]];

drawChaos[v_, ratio_, numPoints_] := Module[{vertices},
   vertices = {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

   Graphics[{PointSize[0], Opacity[.5],
     Point[FoldList[(#1 + #2) ratio &, N@{0, 0},
       RandomChoice[N@vertices, numPoints]]]}]];

With[{
  verticesC = Control[{vertices, 3, 8, 1, ImageSize -> Tiny}],
  iterationsC = Control[{iterations, 0, 8, 1, ImageSize -> Tiny}],
  numPointsC = Control[{numpoints, 0, 100000, 1, ImageSize -> Tiny}]},

 Manipulate[Overlay[{
    drawGeom[vertices, ratio, iterations],
    drawChaos[vertices, ratio, numpoints]}],
  Row[{verticesC, iterationsC, numPointsC}, " "],
  {{ratio, .5}, 0, 1, Appearance -> "Labeled"},
  Alignment -> Center]]

Look! There are Koch snowflake figures that form in the negative space. The boundary becomes snowflaked. A Koch snowflake can easily be made with an L-system construction:

enter image description here

axiom = {F, right[2], F, right[2], F};
rules = F -> {F, left[1], F, right[2], F, left[1], F};
conversions = {F -> forward, dir_[n_] :> ConstantArray[dir, n]};

(*state transformations*)
forward[{z_, theta_}] := {z + E^(I theta), theta};
left[{z_, theta_}] := {z, theta + 2 Pi/6};
right[{z_, theta_}] := {z, theta - 2 Pi/6};

draw[n_] :=
  Module[{program, zs},
   program = Flatten[Nest[# /. rules &, axiom, n] /. conversions];
   zs = First /@ ComposeList[program, {0., 0.}];
   Graphics[{Thin, Line[{Re[#], Im[#]} & /@ zs]}]];

Grid[Partition[#, 3]] &[draw /@ Range[0, 5]]

enter image description here

axiom = {F, right[1], F, right[1], F, right[1], F, right[1], F};
rules = F -> {F, left[1], F, right[2], F, left[1], F};
conversions = {F -> forward, dir_[n_] :> ConstantArray[dir, n]};

(*state transformations*)
forward[{z_, theta_}] := {z + E^(I theta), theta};
left[{z_, theta_}] := {z, theta + 2 Pi/5};
right[{z_, theta_}] := {z, theta - 2 Pi/5};

draw[n_] :=
  Module[{program, zs},
   program = Flatten[Nest[# /. rules &, axiom, n] /. conversions];
   zs = First /@ ComposeList[program, {0., -Pi/10.}];
   Graphics[{Thin, Line[{Re[#], Im[#]} & /@ zs]}]];

Grid[Partition[#, 3]] &[draw /@ Range[0, 5]]

With some minoradjustments we get our pentagonal snowflake. If we do the same procedure for the hexagonal chaos game we get the familiar triangular snowflake. All of the geometries seem to create Koch snowflakes, which makes sense given that indentations are triangles.

Of course, there are much more interesting generalizations we can come up with than simple ratios:

enter image description here

enter image description here

enter image description here

draw[v_, df_, numPoints_: 1000] := Module[{vertices},
   vertices = {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

   Graphics[{PointSize[0], Opacity[.15],
     Point[FoldList[df, N@{0, 0},
       RandomChoice[N@vertices, numPoints]]]}]];

rotate = RotationTransform;
functions = {
   (#1 + #2)/RandomChoice[Prime[Range[3]]] &,
   (#1 + #2)/RandomChoice[Prime[Range[3]]!] &,
   (#1 + #2)/RandomChoice[Prime[Range[10]]] &,
   #1 + .5 rotate[10. Degree, #1][#2 - #1] &};

Grid[Join[
  {TraditionalForm[Trace[#[a, b]][[2]]] & /@ functions},
  ParallelTable[draw[v, df], {v, 3, 5}, {df, functions}]]]

Some of these drawings remind me of the kind of fractal scattering found in the more deterministic algorithms. I wonder what kind of relation there is. The best distance function I found was logarithm-based:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

game = Compile[{{v, _Integer}, {wowzerz, _Real}, {numPoints, _Integer}},
   Module[{diff, vertices},
    vertices = {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

    (*distance function is Clip[(a+b)Log[EuclideanDistance[a,b]+wowzerz]*)
    FoldList[(
       diff = #2 - #1;(*note each of these is an x-y pair*)
       Clip[(#1 + #2) Log[Sqrt[diff.diff] + wowzerz], 1.1 {-2, 2}]) &,
     {0, 0}, RandomChoice[vertices, numPoints]]]];

Graphics[{PointSize[0], Opacity[.08],
  Point[game[5, .8, 300000]]}(*,PlotRange->1.15*)]

All of these images are from the same distance function. The 'holes' on the inward-folded leaves of this one are interesting. It's like a fractal Klein bottle thing goin on there. If my computer was worth more than my car, as it some day will be, I would burn a lot of lightning-sequestered power in my mad scientist laboratory in the process of rendering different distance functions. There's a lot of pretty pictures in these simple chaos games. As it stands all this lightning is going to waste.

The geometric approach, not one to have been served, decides to go Tron:

enter image description here

draw[v_, df_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{zs},
     zs = c + r E^(I 2. Pi Range[v]/v);

     If[depth == 0, Polygon[{Re[#], Im[#]} & /@ zs],
      ring[(c + #)/2, df[0, r], depth - 1] & /@ zs]];

   Framed[Graphics[{Transparent,
      EdgeForm[{Thick, LightBlue}],
      ring[0., 1., n]}], FrameStyle -> LightBlue]];

functions = {
   (#1 + #2)/RandomChoice[Prime[Range[3]]] &,
   (#1 + #2)/RandomChoice[Prime[Range[3]]!] &,
   (#1 + #2)/RandomChoice[Prime[Range[10]]] &,
   #1 + (1/2) (#2 - #1) E^(I 10. Degree) &};

Framed[Grid[Join[
   {TraditionalForm[Trace[#[a, b]][[2]]] & /@ functions},
   Table[draw[v, df, 3], {v, 3, 5}, {df, functions}]]],
 Background -> Black, BaseStyle -> LightBlue]

enter image description here

enter image description here

enter image description here

enter image description here

draw[v_, df_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (2. Pi Range[v]/v);

     If[depth == 0, Polygon[ps],
      ring[(c + #)/2, df[0, r], depth - 1] & /@ ps]];

   Graphics[{EdgeForm[White], Opacity[.4],
     RGBColor[.4, 1, 1], ring[0., 1., n]}]];

Show[(*repeatedly draw to cover more possibilities*)
 draw[4, (#1 + #2)/RandomChoice[Prime[Range[4]]] &,
    RandomChoice[{.1, 1.5} -> {2, 3}]] & /@ Range[20],
 Background -> Black, ImageSize -> 600]

Or Asteroids. Same thing.

The most interesting place I've seen the chaos game is in genetics. The idea is that instead of randomly picking the vertex at each step, you let the letters of the genetic code pick for you. There are 4 letters in DNA: A, T, G, C. So you run a chaos game with 4 vertices. If some sequence of DNA is AAAATC, your active point will approach the point labeled A 4 times, then it will approach the T point, then the C point.

If the DNA sequence is completely random, you will just recreate our beautiful block, which I have named the Charcoal Diamond:

enter image description here

But what you get is not random, as this chaos plot shows:

enter image description here

coords = N@{"A" -> {1, -1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, 1}};
dat = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]] /. coords;

draw[data_] := Graphics[{PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, data]]},
   Epilog -> Style[Text @@@ coords, Red, Background -> White]];

draw[dat]

This is a chaos game plot of an arbitrarily-chosen 8 million basepair sequence from our chromosome X (for scale, a typical protein is encoded in only a few hundred basepairs). You might insensibly think this happens because the letters occur with different frequencies, but that's not the case. The following is a chaos game plot of a sequence that was randomly generated over the same statistical frequencies as the above sequence:

enter image description here

enter image description here

chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]];
tallies = Tally[chars];
BarChart[Last /@ tallies, ChartLabels -> First /@ tallies]

enter image description here

enter image description here

coords = N@{"A" -> {1, 1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, -1}};
dat = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]] /. coords;

scale = 2;
grid = Tuples[Range[{-1, -1}, {1, 1} - 2^-scale, 2^-scale]];
color = If[Mod[Plus @@ #*2^scale, 2] == 1,
    Blend[{Lighter@Purple, Yellow}],
    Blend[{Lighter@Blue, Red}]] &;

overunder = Graphics[{Opacity[.25],
    {color[#], Rectangle[#, # + 2^-scale]} & /@ grid}];

draw[data_] := Show[overunder,
   Graphics[{PointSize[0], Opacity[.06],
     Point[FoldList[(#1 + #2)/2 &, N@{0, 0},
       RandomChoice[data, Length[data]]]]}],
   overunder, PlotRange -> 1, ImageSize -> {600, 600}];

draw[dat] // Rasterize

coords = N@{"A" -> {1, -1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, 1}};
dat = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]] /. coords;

draw[data_] := Graphics[{PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, RandomChoice[data, Length[data]]]]},
   Epilog -> Style[Text @@@ coords, Red, Background -> White]];

draw[dat]

The letters do occur in different frequencies, but that doesn't make any interesting patterns. If you move the letters around you get a pattern related in this case to the fact that the frequencies are bilateral, but otherwise it's just a glorified chessboard. And look what happens when we do the same vertex movearounding for our genetic code:

enter image description here

coords = N@{"A" -> {1, 1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, -1}};
dat = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]] /. coords;

draw[data_] := Graphics[{PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, data]]},
   Epilog -> Style[Text @@@ coords, Red, Background -> White]];

draw[dat]

The original paper does a good job explaining how the chaos plot is something of a "fractal subsequence histogram." Assume your active point is anywhere in the entire square, and the next move is toward the bottom-left corner. Because you move halfway toward that corner (instead of, say, only one third or one fifth of the way), you will land inside that corner's quadrant regardless of where your point was to begin with.

Furthermore, you can apply this argument to subquadrants. It's easy to see this if you "work backwards." The formula for going from the current active point toward the next vertex is

enter image description here

By the DeLorean transform, we can go backwards like this:

enter image description here

So, reversing all the points in a particular subquadrant:

enter image description here

enter image description here

enter image description here

Manipulate[Module[{delorean, preimage, pt1, pt2},
  pt1 = ptc - width;
  pt2 = ptc + width;

  delorean[x_] := 2 x - coord;
  preimage = delorean /@ Tuples[Range[pt1, pt2, .025]];

  Graphics[{
    EdgeForm[{Thickness[.01], Darker[Gray, .4]}],
    {Transparent, Rectangle[{-1, -1}, {1, 1}]},
    {LightGray, Rectangle[pt1, pt2]},
    {Gray, Point[preimage]}},
   PlotRange -> 1.2, GridLines -> Automatic,
   GridLinesStyle -> Lighter[Gray, .8]]],

 {{width, .25}, 0, .5, 2.^-3},
 {{ptc, {-.75, -.25}}, Locator},
 {{coord, {-1, -1}}, Locator,
  Appearance -> Style["\[FilledSquare]", Red]}]

You can see here that for all the points which were just ordered to move toward the little red dot in the bottom-left corner, those that landed in the gray square had to have come from the top-left quadrant of the main square (the region with gray dots). So for the points in that gray region, we not only know that they were ordered to move toward the bottom-left vertex in the last step, but also that they were ordered to move toward the top-left vertex in the step before that.

And so on. The points in this little gray region were ordered to move previously toward the bottom-left, and before that the top-left, and before that the bottom-right. All points in that square have that history. So going back to our genetic chaos plot:

enter image description here

enter image description here

enter image description here

enter image description here

What those big holes mean is that CG is a rare sequence. As we just saw, a point can only get to that big empty square by coming from the bottom-right quadrant and going toward the top-left vertex. And since that square is so empty, there are rarely any points that are available to go toward other subsquares, such as this one, and so on.

This accounts for the texture of the first chaos plot as well. It just looks more wacked out because the CG vertices are adjacent, so the empty squares touch each other and create those staggered serrations. A simple histogram confirms our suspicions of CG Paucity — a.k.a. biology's Dark Energy:

enter image description here

chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]];
tallies = Sort[Tally[Partition[chars, 2, 1]]];
BarChart[Last /@ tallies, ChartLabels -> CenterDot @@@ First /@ tallies]

If you sample subsequences instead of individual letters, and use those samples to simulate a genetic sequence, what's the smallest subsequence-sampling size you can get away with while still faithfully reproducing the texture of the chaos plot?

Asked differently, what length of subsequence is it that accounts for the texture of the chaos plot? Here is a graph of our DNA letters with pair-wise sequences labeled by probability:

enter image description here

chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]];
tallies = Sort[Tally[Partition[chars, 2, 1]]];
sum = Total[Last /@ tallies];
stats = {Rule @@ #1, #2/sum} & @@@ tallies;

With[{r = .05},
 edgeF[pts_List, e_] := Arrow[pts, r];
 edgeF[pts_List, h_[a_, a_]] := Scale[Arrow[pts, r/.3], .3, pts[[1]]];
 vertexF = {EdgeForm[Opacity[.5]], Disk[#1, r], Darker[Gray, .7],
    Style[Text[#2, #1], 13, Bold, FontFamily -> "Comic Sans MS"]} &;
 edgeLabels = #1 -> Style[Round[#2, .01], 12, Bold] & @@@ stats;]

Graph[First /@ stats, EdgeLabels -> edgeLabels,
 EdgeStyle -> Directive[{Thick, Opacity[.56]}],
 VertexShapeFunction -> vertexF, VertexStyle -> Orange,
 EdgeShapeFunction -> edgeF, PlotRangePadding -> .1]

This is a graph of what's called a Markov chain, but don't quote me on the formalities. (Mathematica 9 has built-in Markov whatitswhats, but I'm using version 8). The point is we can generate a sequence whose letter-to-letter statistics are the same as those of our original DNA by following the graph probaballistically:

enter image description here

enter image description here

getStats[data_] := Module[{tallies, sum},
   tallies = Tally[Partition[data, 2, 1]];
   sum = Total[Last /@ tallies];
   {Rule @@ #1, #2/sum} & @@@ tallies];

draw[data_, options___] := Graphics[{PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, data]]}, options,
   Epilog -> Style[Text @@@ coords, Red, Background -> White]];

chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]];
stats = getStats[chars];

Do[With[{weights =
    Rule @@ Transpose@Cases[stats, {letter -> to_, p_} :> {N[p], to}]},
  next[letter] := RandomChoice[weights]],
 {letter, DeleteDuplicates[stats[[All, 1, 1]]]}]

coords = N@{"A" -> {1, 1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, -1}};
pseudoDat = NestList[next, "A", Length[chars]] /. coords;

draw[pseudoDat]

You can see that, while similar, the fake plot immediately stands out as too Hollywood compared to the verisimilous beauty of the real data. The most notable distinction between them, besides the grain, is the dark diagonal that crosses A and T in the real plot, presumably because those two letters have a lot of interplay. That it's not replicated by our pseudosequence may mean there are a relatively large amount of ATA, TAT subsequences.

So it looks like subsequences of length 2 aren't sufficient. We could generalize our Markovizer, but what I think is actually interesting here is the grain. We can do some image processing to see if we can bring it out:

enter image description here

enter image description here

enter image description here

enter image description here

ListPlot[FoldList[(#1 + #2)/2 &, 1,
  Mod[Range[1000], 2]], PlotRange -> 1,
 Ticks -> {Automatic, Range[0, 1, 1/3]}]

getStats[data_] := Module[{tallies, sum},
   tallies = Tally[Partition[data, 2, 1]];
   sum = Total[Last /@ tallies];
   {Rule @@ #1, #2/sum} & @@@ tallies];

draw[data_, options___] := Graphics[{PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, data]]}, options];

chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]];
stats = getStats[chars];

Do[With[{weights =
    Rule @@ Transpose@Cases[stats, {letter -> to_, p_} :> {N[p], to}]},
  next[letter] := RandomChoice[weights]],
 {letter, DeleteDuplicates[stats[[All, 1, 1]]]}]

coords = N@{"A" -> {1, 1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, -1}};
pseudoDat = NestList[next, "A", Length[chars] - 1] /. coords;
realDat = chars /. coords;

With[{upsc = 2},
 pseudo = draw[pseudoDat, ImageSize -> upsc 600] // Rasterize;
 real = draw[realDat, ImageSize -> upsc 600] // Rasterize;

 With[{\[Theta] = ColorNegate},
   (ImageSubtract[\[Theta][real], \[Theta][pseudo]] // \[Theta])
     ~MinFilter~1
     ~ImageMultiply~1.1
    (*~ImageAdjust~(9!)*)
    // ImageAdjust]
  ~ImageResize~Scaled[1/upsc]]

enter image description here

enter image description here

chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]]; 
tallies = Sort[Tally[Partition[chars, 3, 1]]];
BarChart[Last /@ tallies, ChartLabels -> Column /@ First /@ tallies]

enter image description here

chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]];
tallies = Sort[Tally[Partition[chars, 5, 1]]];
ListPlot[Cases[tallies, {seq_, count_} :> Tooltip[count, Column[seq]]],
 Axes -> None, Filling -> Axis, PlotRange -> Full]

Which actually just shows a lot of TTT and AAA. Longer subsequence statistics show a similar picture. And of course we can always just do this:

enter image description here

string = GenomeData[{"ChromosomeX", {28000000, 36000000}}];
tallies = Sort[Tally[StringCases[string, ("A" .. | "T" .. | "C" .. | "G" ..)]]];

Grid[Join[
   Sequence @@ Reverse@Sort@
      SplitBy[tallies, StringTake[First[#], 1] &], 2],
  Alignment -> Left] // Magnify[#, 1/2] &

string = GenomeData[{"ChromosomeX", {28000000, 36000000}}];
strings = ParallelTable[Module[{cases =
      StringCases[string, Alternatives @@ cs .., Overlaps -> True]},
    Last@SortBy[cases, StringLength]],
   {cs, Subsets[{"A", "C", "T", "G"}, {2}]}];

Grid[{Tooltip[Short[#], #], StringLength[#]} & /@
  Reverse@SortBy[strings, StringLength], Alignment -> Left]

The longest single-letter run length in this section of DNA is 48 As. The longest string of A-or-T is 222 basepairs long. Quite long, but the longest pairing is actually T/C which has a sequence of length 231. C/G's longest sequence is 34 basepairs long. I wonder what it is about CG. Maybe an unusually (un)useful amino acid or some hydrophobilia issue. I wonder too if these are blanket statistical patterns or if certain quirks are only present, say, in non-coding regions.

You might be wondering why we don't just ask a biologist about these mysteries. The reason is because you're inside a car right now, I'm driving, we're lost, both of us are tourists, and I'm one of those people that would sooner burn hours of gasoline/diesel than ask for directions. You also suspect I might be some kind of criminal, so you're afraid of bringing up the issue. All around it's pretty awkward in here.

We can do a lot better than these static diagrams by giving ourselves the ability to manually movearound the vertices to see if we can find interesting patterns:

enter image description here

enter image description here

enter image description here

coords = N@{"A" -> {1, 1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, -1}};
chars = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]];

draw[data_, options___] := Graphics[{PointSize[0], Opacity[.1],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, data]]}, options];

Manipulate[
 draw[
  chars[[1 ;; 100000]] /. Thread[First /@ coords -> pts],
  PlotRange -> 1.1],
 {{pts, Last /@ coords}, Locator,
  Appearance -> (Framed[#, BaseStyle -> Red,
       FrameStyle -> None, FrameMargins -> 0,
       Background -> White] & /@ First /@ coords)}]

And a tool that repeatedly applies the DeLorean transform to rebuild the sequence leading up to a region:

enter image description here

enter image description here

enter image description here

coords = N@{"A" -> {1, 1}, "T" -> {-1, -1}, "G" -> {-1, 1}, "C" -> {1, -1}};
dat = Characters[GenomeData[{"ChromosomeX", {28000000, 36000000}}]] /. coords;
nfLetter = Module[{nf = Nearest[Reverse /@ coords]}, nf[#, 1][[1]] &];

draw[data_, options___] := Graphics[{PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, data]]}, options];

background = Raster[Reverse@ImageData@Rasterize@
      draw[dat, PlotRange -> 1, ImageSize -> 600 {1, 1}],
   {{-1, -1}, {1, 1}}];

delorean[p_] := 2 p - (nfLetter[p] /. coords);
Manipulate[Module[{seq, r = 2^radius},
  seq = Most[NestList[delorean, pt, Floor[1/(2 r)]]];

  Graphics[{background, {Darker@Gray, Point[seq]},
    {Orange, Thick, Opacity[.8], Arrow[Reverse[seq]],
     MapThread[Circle, {seq, 2^(radius + 1) r 2^Range[Length[seq]]}]}},
   PlotRange -> 1.015, PlotLabel -> (nfLetter /@ Reverse[seq]),
   Epilog -> Style[Text @@@ coords, Red, Background -> White]]],

 {{radius, -2}, -4, -2, 1},
 {{pt, {-.75, -.25}}, Locator}]

I'm not actually sure how legit the maths of the program are, but there it be. Let's return to our charcoal diamond, here rotated:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

drawDiamond[numPoints_] := Graphics[{PointSize[0], Opacity[.01],
    Point[FoldList[(#1 + #2)/2 &, N@{0, 0}, RandomInteger[{0, 1}, {numPoints, 2}]]]},
   ImageSize -> 600, PlotRangePadding -> 0];

diamond = drawDiamond[15000000] // Rasterize;

axiom = {{Transparent, Rectangle[Scaled[{0, 0}], Scaled[{2, 2}]]}, White,
   If[hl, EdgeForm[{Opacity[.1], Green}]], Rectangle[Scaled[{1, 1}], Scaled[{2, 2}]]};

next[prev_] := Translate[Scale[prev, .5], {{-1, 1}, {-1, -1}, {1, -1}}];

Control[{hl, {True, False}}] Control[{n, 0, 10, 1}]
Dynamic[Overlay[{diamond, Graphics[NestList[next, axiom, n],
    ImageSize -> (ImageSize /. AbsoluteOptions[diamond]), PlotRange -> 1]}]]

Imagine we suddenly removed one vertex. That would mean that points can no longer land in that quadrant. Which would mean that no points could go from that quadrant to these subquadrants. Which would mean no points going to these subquadrants. And soon and soforth, until.

So that explains the holes in the Sierpinski triangle. I call this the "Sierpinski triangle by infinite quadrilateral descent" method of construction. It seems very natural to me, but it raises the question of what these regions in the various deterministic constructions have to do with each other:

enter image description here

draw[n_] := Array[Tooltip[Mod[Binomial[##], 2],
     TraditionalForm[HoldForm[Binomial[##]] == Binomial[##]]] &, {2^n, 2^n}, 0];
proc[a_ /; Length[a] == 2] := a;
proc[arr_] := Module[{l = Length[arr]/2},
   ArrayFlatten@Map[Function[square,
      If[FreeQ[square, Tooltip[1, _]],
       (**)Map[Style[#, Bold, ColorData[3][l]] &, square, {2}],
       (**)proc[square]]], Partition[arr, {l, l}], {2}]];
Style[MatrixForm[proc[draw[5]]], Background -> GrayLevel[.98]]

(To be clear, the chaos game is just an algorithmic tradeoff vs the geometric approach. It is not necessarily doing anything non-deterministic in the larger scheme.) In this case I think the parity/binary explanations are going to be the simplest, though I'm a math noob and I don't see an immediately obvious way of approaching this, if the question even makes sense in the way I seem to be implying. However with some inspiration we can find an iterative angle that seems to me like a kind of multiplication:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

(* minimal *)

iterate[matrix_, power_] := Nest[ArrayFlatten[
     ConstantArray[#, Dimensions[matrix]] matrix] &, 1, power];

draw[matrix_, power_] :=
  ArrayPlot[iterate[matrix, power],
   Frame -> False, PixelConstrained -> 1];

draw[{{1, 0}, {1, 1}}, 10]

matrixInput[Dynamic[m_], Dynamic[rot_]] :=
  Dynamic[Rotate[Deploy[MatrixForm[#, TableSpacing -> {0, 0}]], rot] &@
    Array[(*(*better performance*)Rotate[#,-rot]&@*)
     Checkbox[Dynamic[m[[##]]], {0, 1}] &, Dimensions[m]]];

bg = White;
dims = # -> If[# > 4, Style[#, Red], #] & /@ Range[7];

iterate[matrix_, power_] := Nest[ArrayFlatten[
     ConstantArray[#, Dimensions[matrix]] matrix] &, 1, power];

controls = With[{
    mC = Control[{{m, 2, ""}, dims, ControlType -> PopupMenu}],
    nC = Control[{{n, 2, ""}, dims, ControlType -> PopupMenu}],
    matrixInputC = matrixInput[Dynamic[matrix], Dynamic[rot]],
    colorC = Control[{{color, Black}, ColorSlider}],
    rotC = Control[{{rot, 0, "\[Theta]"}, Pi, -Pi, Pi/16}],
    powerC = Control[{{power, 3}, 1, 4, 1, Appearance -> "Labeled"}],
    opacityC = Control[{{opacity, 1}, 0, 1, ImageSize -> Small}],
    primitiveC = Control[{{primitive, Rectangle[]},
       (# -> Graphics[{color, #}, ImageSize -> 20] &) /@ {
         {PointSize[Tiny], Point[N@{0, 0}]},
         {EdgeForm[None], Disk[N@{0, 0}, .5]},
         Rotate[Scale[Rectangle[], 1./Sqrt[2]], Pi/4],
         Rectangle[]}, SetterBar}],
    backgroundC = Row[{"background   ",
       Framed[ColorSlider[Dynamic[background, (bg = background = #) &],
         AppearanceElements -> "Swatch"], FrameStyle -> Darker[Gray]],
        " ",
       ColorSlider[Dynamic[background, (bg = background = #) &],
        AppearanceElements -> "Spectrum", ImageSize -> Small]}]},

   Row[{
     Column[{
       Row[{mC, "   \[Times]", nC}],
       Row[{"    ", matrixInputC}]}],
     Spacer[40],
     Column[{colorC, rotC, powerC}],
     Column[{backgroundC, opacityC, primitiveC}]}]];

Panel[#, Background -> Dynamic[bg]] &@
 Manipulate[
  If[{m, n} =!= Dimensions[matrix], matrix = PadRight[matrix, {m, n}]];

  With[{primitives = Rotate[#, rot - Pi/2] &@
      Translate[primitive, Position[
        iterate[matrix /. 0 matrix -> {{1}}, power], 1]]},

   Graphics[{Dynamic[EdgeForm[{Opacity[opacity], color}]],
     Dynamic[color], Dynamic[Opacity[opacity]], primitives},
    ImageSize -> {{400, Large}, {400, Large}},
    Background -> Dynamic[background]]],

  Evaluate[controls],

  (*declare variables here for persistence*)
  {{background, bg = White}, ControlType -> None},
  {{matrix, {{1, 0}, {1, 1}}}, ControlType -> None},

  Bookmarks :> {
    "Random" :> (matrix = RandomChoice[{.4, .6} -> {0, 1}, Dimensions[matrix]]),
    "Invert" :> (matrix = BitXor[matrix, 1]),
    "Array Print" :> (With[{p = power, m = matrix, c = color, o = opacity, bg = background},
      CellPrint[ExpressionCell[Defer[
         ArrayPlot[iterate[m, p], Frame -> False, PixelConstrained -> 1,
          ColorRules -> {0 -> bg, 1 -> c /. RGBColor[r_, g_, b_] :> RGBColor[r, g, b, o]}]],
        "Input"]]]),
    "Clear" :> (matrix = 0 matrix)},

  Paneled -> False, SynchronousUpdating -> Automatic,
  SaveDefinitions -> True, LabelStyle -> Darker[Gray], Alignment -> Center]

I know what this image reminds you of. Those little candle chandoliers that you hit in Castlevania to make hearts and morning stars come out. I also found The Sierpinski Scream, a letter H that would definitely beat you up if it was human, the up arrow and its Hot Topic-donning offspring, pink infinities made of pink infinities, even the vaunted Sierpinski Chronobracket.

Essentially what we have here in these little matrices is a notation for specifying translations. It's yet another algorithm with different tradeoffs for doing more or less the same thing that our chaos game and geometric algorithms are doing. We can bring this characteristic out by allowing arbitrary rules:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

(* minimal *)

iterate[matrix_, power_, matrix1_: {{1}}] := Module[{rules =
     {0 -> (0 # &), 1 -> (# &), T -> Transpose,
      R -> (Transpose[Reverse[#]] &), L -> (Reverse[Transpose[#]] &)}},

   Nest[Function[prev,
     ArrayFlatten[Map[#[prev] &, matrix /. rules, {2}]]],
    matrix1, power]];

draw[matrix_, power_] :=
  ArrayPlot[iterate[matrix, power],
   Frame -> False, PixelConstrained -> 1];

draw[{{1, 0}, {T, R}}, 10]

matrixInput1[Dynamic[m_], Dynamic[rot_]] :=
  Dynamic[Rotate[Deploy[MatrixForm[#, TableSpacing -> {0, 0}]], rot] &@
    Array[(*(*better performance*)Rotate[#,-rot]&@*)
     EventHandler[Checkbox[Dynamic[m[[##]]], {0, 1}],
       {"MouseDown", 2} :> (m[[##]] = 0)] &,
     Dimensions[m]], 0];

matrixInput2[Dynamic[m_], Dynamic[rules_], Dynamic[color_],
   Dynamic[rot_]] :=
  With[{
    tooltip = Tooltip[#, "Click to cycle\nRight-click to zero", TooltipDelay -> .8] &,
    eatRightClick = EventHandler[#, {"MouseDown", 2} :> {}] &,
    matrixForm = MatrixForm[#, TableSpacing -> {1, 1}] &},

   Dynamic[
    eatRightClick@Style[#, color] &@
       Rotate[#, rot] &@tooltip@Deploy@matrixForm@
        Array[
         EventHandler[Toggler[Dynamic[m[[##]]], First /@ rules],
           {"MouseDown", 2} :> (m[[##]] = 0)] &,
         Dimensions[m]]]];

bg = White;
dims = # -> If[# > 4, Style[#, Red], #] & /@ Range[4];
defaultRules = {0 -> (0 # &), 1 -> (# &), T -> Transpose,
   R -> (Transpose[Reverse[#]] &), L -> (Reverse[Transpose[#]] &)};

iterate[matrix_, matrix1_, rules_, power_] :=
  Nest[
   Function[prev, ArrayFlatten[Map[#[prev] &, matrix /. rules, {2}]]],
   matrix1, power];

controls = With[{
    m1C = Control[{{m1, 2, ""}, dims, ControlType -> PopupMenu}],
    m2C = Control[{{m2, 2, ""}, dims, ControlType -> PopupMenu}],
    matrixInput1C = matrixInput1[Dynamic[matrix1], Dynamic[rot]],
    matrixInput2C = matrixInput2[Dynamic[matrix], Dynamic[rules], Dynamic[color], Dynamic[rot]],
    rulesC = OpenerView[{"Rules", Control[{{rules, defaultRules, ""}, InputField,
         Background -> Dynamic[Lighter[background, .65]], FieldSize -> {45, 5}}]}],
    colorC = Control[{{color, Black}, ColorSlider}],
    rotC = Control[{{rot, 0, "\[Theta]"}, Pi, -Pi, Pi/16}],
    powerC = Control[{{power, 3}, 1, 4, 1, Appearance -> "Labeled"}],
    backgroundC = Row[{"background   ",
       Framed[ColorSlider[Dynamic[background, (bg = background = #) &],
         AppearanceElements -> "Swatch"], FrameStyle -> Darker[Gray]], " ",
       ColorSlider[Dynamic[background, (bg = background = #) &],
        AppearanceElements -> "Spectrum", ImageSize -> Small]}],
    opacityC = Control[{{opacity, 1}, 0, 1, ImageSize -> Small}],
    primitiveC = Control[{{primitive, Rectangle[]},
       (# -> Graphics[{color, #}, ImageSize -> 20] &) /@ {
         {PointSize[Tiny], Point[{0, 0}]},
         {EdgeForm[None], Disk[{0, 0}, .5]},
         Rotate[Scale[Rectangle[], 1/Sqrt[2]], Pi/4],
         Rectangle[]}, SetterBar}]},

   Row[{
     Column[{
       Row[{m1C, "   |", m2C}],
       Row[{"    ", matrixInput1C, "  ", matrixInput2C}]}],
     Spacer[40],
     Column[{rulesC, OpenerView[#, True] &@
        {"Style", Row[{Column[{colorC, rotC, powerC}],
           Column[{backgroundC, opacityC, primitiveC}]}]}}]}]];

bookmarks = {
   "Random" :> (
     matrix1 = RandomChoice[{0, 1}, Dimensions[matrix1]];
     matrix = RandomChoice[First /@ rules, Dimensions[matrix]]),

   "Array Print" :> With[
     {m1 = matrix1, m = matrix, r = rules, p = power, c = color, o = opacity, bg = background},
     CellPrint[ExpressionCell[Defer[
        ArrayPlot[
         iterate[m /. 0 m -> {{1}}, m1 /. 0 m1 -> {{1}}, r, p], PixelConstrained -> 1, Frame -> False,
         ColorRules -> {0 -> bg, 1 -> c /. RGBColor[r_, g_, b_] :> RGBColor[r, g, b, o]}]],
       "Input"]]],

   "Clear" :> (matrix = 0 matrix)};

Panel[#, Background -> Dynamic[bg]] &@
 Manipulate[
  If[{m1, m1} =!= Dimensions[matrix1], matrix1 = PadRight[matrix1, {m1, m1}]];
  If[{m2, m2} =!= Dimensions[matrix], matrix = PadRight[matrix, {m2, m2}]];

  (*remove rules from matrix that no longer exist*)
  Module[{matrixP, default = rules[[1, 1]]},
   matrixP = Replace[matrix, a_ /; ! MemberQ[First /@ rules, a] -> default, {2}];
   If[matrix =!= matrixP, matrix = matrixP]];

  With[{primitives =
     Rotate[Translate[primitive, Position[#, 1]], rot - Pi/2] &@
      iterate[
       matrix /. 0 matrix -> {{1}},
       matrix1 /. 0 matrix -> {{1}}, rules,
       ControlActive[Max[power - 2, 2], power]]},

   Graphics[{Dynamic[EdgeForm[{Opacity[opacity], color}]],
     Dynamic[color], Dynamic[Opacity[opacity]], primitives},
    ImageSize -> {{400, Large}, {400, Large}},
    Background -> Dynamic[background]]],

  (*declare variables here for persistence*)
  {{background, bg = White}, ControlType -> None},
  {{matrix1, {{1, 0}, {1, 1}}}, ControlType -> None},
  {{matrix, {{1, 0}, {1, 1}}}, ControlType -> None},

  Evaluate[controls],
  Bookmarks :> Evaluate[bookmarks],

  LabelStyle -> Darker[Gray], SynchronousUpdating -> Automatic,
  Paneled -> False, SaveDefinitions -> True, Alignment -> Center]

Cha-ching baby. If Snoop Dogg ever used Mathematica, that's what square brackets in his custom font would look like. And I know what this one reminds you of. The folds of the brain. And check out the Black Riddler's Question Mark.

Inversion

enter image description here

enter image description here

enter image description here

enter image description here

axiom = Polygon[{Cos[#], Sin[#]} & /@ (Pi/2 + 2 Pi Range[3]/3)];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

invert[p_] := p/Norm[p]^2;

draw[n_] := Graphics[{EdgeForm[Black], Nest[next, N@axiom, n]}];

g = draw[2];
Show[g, g /. Polygon[pts_] :> Polygon[invert /@ pts]]

The radius of inversion is right at the corners of the triangle, and I've left the univerted triangle in the center. Here's what the first few construction steps of the triangle look like if we invert them:

enter image description here

axiom = Polygon[{Cos[#], Sin[#]} & /@ (Pi/2 + 2 Pi Range[3]/3)];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

invert[p_] := p/Norm[p]^2;

draw[n_] := Module[{ps = Nest[next, N@axiom, n]},
   Graphics[{EdgeForm[Black], Transparent,
     ps, ps /. Polygon[pts_] :> Polygon[invert /@ pts]}]];

Grid[Partition[draw /@ Range[0, 8], 3]]

Notice that we're just inverting the endpoints of the lines, not the lines-as-curves. Visually this doesn't make a difference at higher iterations:

enter image description here

axiom = Polygon[{Cos[#], Sin[#]} & /@ (Pi/2 + 2 Pi Range[3]/3)];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

invert[p_] := p/Norm[p]^2;

draw[n_] := Graphics[{
    EdgeForm[Black], Transparent,
    Nest[next, N@axiom, n]}];

Show[draw[10], draw[12] /. Polygon[pts_] :> Polygon[invert /@ pts],
   Method -> {"ShrinkWrap" -> True}, ImageSize -> 4 750] //
  Rasterize // ImageResize[#, Scaled[1/4]] &

What about varying the radius of inversion? You first perform the same inversion as before, but with respect to the radius:

enter image description here

It took me a while, but eventually I realized that the edges of the triangle were being mapped to curves, and that if you continued those curves they would form circles that intersected the origin, like this:

enter image description here

enter image description here

enter image description here

enter image description here

Let's not forget we have a bountiful cornucopia from which to invert:

enter image description here

enter image description here

enter image description here

draw[v_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

     If[depth == 0, Polygon[ps],
      ring[(c + #)/2, r/2, depth - 1] & /@ ps]];

   Graphics[{Transparent,
     EdgeForm[Black],
     ring[0., 1., n]}]];

Clear[invert];
(*invert[p_/;Norm[p]<.0001]:={0,0};*)
invert[p_] := p/Norm[p]^2;

Column[Panel[Row[#]] & /@
  Table[
   draw[v, n] /. Polygon[pts_] :> Polygon[invert /@ pts],
   {v, 3, 6}, {n, 0, 4}]]

draw[v, n] := Module[{ring}, ring[c, r, depth_] := Module[{ps}, ps = c + r {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

 If[depth == 0, Polygon[ps],
  ring[(c + #)/2, r/2, depth - 1] & /@ ps]];

Graphics[ring[0., 1., n]]];

invert[p_] := p/Norm[p]^2;

Column[Panel[Row[#]] & /@ Table[ draw[v, n] /. Polygon[pts_] :> Line /@ Quiet@Partition[invert /@ pts, 2, 1, 1] /. l_Line /; MemberQ[l, Indeterminate, Infinity] :> {}, {v, 3, 6}, {n, 0, 4}]]

enter image description here

Sidenote. Notice that in this program we aren't even touching our original uninverted geometric renderer, because we don't need to. Our original renderer returns a Graphics structure. This structure (which you might call an M-expression) is to us a set of straightforward vector graphics directives, but is to Mathematica meaningless until the frontend gets ahold of it. Until then (and even afterwards) we can perform the same kinds of structural slicing and dicing that we can perform on any other structure. In this case, replacing points by their inverses.

A more complete solution to our point at infinity/division by 0 problem is to put the inverse of (0, 0) not at infinity, but really far. This doesn't come out of the algebra, but we can do it in a well-behaved way because we know from which direction our lines are coming from since we're defining things as polygons:

enter image description here

invert[p_] := p/Norm[p]^2;

(*you ever see that show Long Ago and Far Away? that show was awesome*)
invertPoly[Polygon[pts_], farAway_: 20000] :=
  With[{indQ = MemberQ[#, Indeterminate] &},
   Line /@ Quiet@Partition[invert /@ pts, 2, 1, 1] /.
    {_?indQ, p_} | {p_, _?indQ} :> {p, farAway Normalize[p]}];

plotRangeInv[g_Graphics] := PlotRange /.
   AbsoluteOptions[g /. Polygon[pts_] :> Quiet@Polygon[invert /@ pts]];

draw[v_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (2 Pi Range[v]/v);

     If[depth == 0, Polygon[ps],
      ring[(c + #)/2, r/2, depth - 1] & /@ ps]];

   Graphics[ring[0., 1., n]]];

(*if it ever came out on DVD i'd buy it like 100 times*)
drawInv[v_, n_] := Module[{g = draw[v, n]},
   Show[g /. poly_Polygon :> invertPoly[poly],
    PlotRange -> 1.1 plotRangeInv[g]]];

(*lines=Cases[drawInv[6,4],Line[ps_]/;
EuclideanDistance@@ps<10000:>Line[Sort[ps]],Infinity];
Graphics[DeleteDuplicates@lines]*)

What if, for no particular reason, we vary the exponents of the inversion formula?

enter image description here

enter image description here

enter image description here

axiom = Polygon[{Cos[#], Sin[#]} & /@ (Pi/2 + 2 Pi Range[3]/3)];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

invert[p_] := p^3/Norm[p]^2;

draw[n_] := Graphics[{EdgeForm[Black],
    Nest[next, N@axiom, n]}];

Grid[Partition[draw /@ Range[0, 8], 3]] /.
 Polygon[pts_] :> Polygon[invert /@ pts]

See this one. One day you're going to be driving home from work. It's going to be dark. Pitch black. All a sudden out the corner your eye you're gonna see a flash in your rear view mirror. And when you look, you're gonna see that same Black Cobra Grill on my car speeding towards you at some unspeakable number of kilometers per hour. And then I'll disappear into the night. Like an episode off an MJ's Thriller×Knight Rider mashup.

If we mangle the formula every which way we can find a lot of interesting effects:

enter image description here

enter image description here

axiom = Polygon[{Cos[#], Sin[#]} & /@ (Pi/4 + 2 Pi Range[3]/3)];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

transform[p_] := (Reverse[p].p) p^2/Norm[p]^2;

drawFishie1[n_] := Graphics[{Red,
    EdgeForm[{Thickness[.01], Opacity[.3],
      JoinForm["Round"], Lighter[Blue, .6]}],
    Rotate[Nest[next, N@axiom, n] /.
      Polygon[pts_] :> Polygon[transform /@ pts], 3 Pi/4]},
   PlotRange -> .8 {{-.85, 1.51}, .4 {-1, 1.1}}];

drawFishie2[n_] := Graphics[{
    Transparent, EdgeForm[Black],
    Rotate[Nest[next, N@axiom, n] /.
      Polygon[pts_] :> Polygon[transform /@ pts], -Pi/4]}];

The self-crossings form hexagonal figures. And American iconography? Here's another nifty one:

enter image description here

enter image description here

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

transform[p_] := (Reverse[p].p) p/Norm[p]^2;

notFishieAwwwSadFace[n_, a1_: 0, a2_: 0, options___] :=
  Module[{axiom},
   axiom = Polygon[{Cos[#], Sin[#]} & /@ (a1 + 2 Pi Range[3]/3)];

   Graphics[{Transparent, EdgeForm[Black],
     Rotate[Nest[next, N@axiom, n] /.
       Polygon[pts_] :> Polygon[transform /@ pts], a2]},
    options]];

notFishieAwwwSadFace[6, Pi/4]

I call it the Sierpinski Stiletto of Triangular Destruction. Hell yea. Also pay heed to the Sierpinski Butterfly of Poisonous Death, lest yee regret it. We can also move the circle of inversion around. I was going to write a program to do only that, but before I realized it I had accidentally built this:

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

polys[v_, n_, offset_: {0, 0}, size_: 1, rot_: 0] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (rot + 2 Pi Range[v]/v);

     If[depth == 0, Polygon[(offset + # &) /@ ps],
      ring[(c + #)/2, r/2, depth - 1] & /@ ps]];

   ring[0., size, n]];

(*Polygon in, Lines out /statictyping*)
invertPoly[Polygon[pts_], tf_, farAway_: 20000] :=
  With[{indQ = MemberQ[#, Indeterminate] &},
   Line /@ Quiet@Partition[tf /@ pts, 2, 1, 1] /.
    {_?indQ, p_} | {p_, _?indQ} :> {p, farAway Normalize[p]}];

d = Norm;
(*d=ChessboardDistance[{0, 0},#]&;*)
transformationFunctions = {
   #/d[#]^2 &,
   #^3/d[#]^2 &,
   (Reverse[#].#) #/d[#] &,
   (# + #^3/d[#]^2)/2 &,
   (# + #/d[#]^2)/2 &,
   Round[#, .05] &};

With[{
  verticesC = Control[{{vertices, 3}, 3, 8, 1, Appearance -> "Labeled", ImageSize -> Small}],
  iterationsC = Control[{{iterations, 5}, 0, 10, 1, Appearance -> "Labeled", ImageSize -> Small}],
  rangeC = Control[{{range, 4}, 1, 12, Appearance -> "Labeled"}],
  rotC = Control[{{rot, -Pi/6}, -Pi, Pi, Appearance -> "Labeled", ImageSize -> Tiny}],
  functionC = {{tf, transformationFunctions[[1]], "function"},
    (# -> TraditionalForm@Quiet@Trace[#[z]][[2]] &) /@ transformationFunctions,
       ControlType -> SetterBar},
  originalC = Control[{original, {True, False}}],
  circleC = Control[{circle, {True, False}}],
  opacityC = Control[{{opacity, 1}, 0, 1, ImageSize -> Small}],
  sizeC = Control[{{size, 1}, .001, 4, ImageSize -> Small}],
  offsetC = {{offset, {0, 0}}, Locator}},

 Manipulate[Module[{polygons},
   polygons = polys[vertices,
     ControlActive[Min[3, iterations - 2], iterations],
     offset, size, rot];

   Graphics[{
     If[circle, {LightGray, Circle[]}],
     If[original, {EdgeForm[LightGray], {Transparent, polygons}}],
     {Black, polygons /. p_Polygon :> invertPoly[p, tf]}},
    PlotRange -> range]],

  Row[{verticesC, iterationsC}],
  Row[{rangeC, rotC}], functionC,
  Row[{originalC, circleC, sizeC, opacityC}, "  "], offsetC,
  SynchronousUpdating -> False]]

Oops. This hilarious function doesn't allow anything inside the unit disk. It's just waiting for someone to make a Yakety Sax movie about shapes crashing into the circle and crawling around it.

Someone on the internet asked an interesting question: Are there "zoom out" fractals? We know that if we zoom in on the Sierpinski triangle, we'll continue seeing detail endlessly. But are there fractals that no matter how far you zoom out, you can't get out of them?

Of course there are. We can just take a quote-unquote "zoom in" fractal and place one of its points of detail right at the origin, and then invert the fractal. Because the inverse of the origin is some kind of crazy infinity, we know that no matter how far we zoom out, we won't reach the end of the fractal. This example is really a formality though. You have a lot of liberty to make things up in math.

Cornucopia.

enter image description here

enter image description here

invert[p_ /; Norm[p] < .0001] := {0, 0};
invert[p_] := p/Norm[p]^2;

draw[v_, df_, n_] := Module[{ring},
   ring[c_, r_, depth_] := Module[{ps},
     ps = c + r {Cos[#], Sin[#]} & /@ (2. Pi Range[v]/v);

     If[depth == 0, Polygon[invert /@ ps],
      ring[(c + #)/2, df[0, r], depth - 1] & /@ ps]];

   Graphics[{EdgeForm[White], Opacity[.4],
     RGBColor[.4, 1(*;.6*), 1], ring[0., 1., n]}]];

candy[g_, res_: 600, upsc_: 1, style_: EdgeForm[Thick]] :=
  Module[{a = Show[g, ImageSize -> upsc res, Background -> White]},

   a = a /. p_Polygon :>
      {RGBColor[.6, RandomReal[], RandomReal[]], style, p};
   a = Rasterize[a];

   (*move this downscale to end for different quality*)
   a = ImageResize[a, Scaled[1/upsc]];
   ImageDifference[a, ImageReflect[a, Left]] // ColorNegate];

g = With[{f = (#1 + #2)/RandomChoice[Prime[Range[4]]] &},
  Show[
   Table[draw[(*repeatedly draw to cover more possibilities*)
      RandomChoice[{1, 1, .25} -> {3, 4, 5}], f,
      RandomChoice[{.1, 1.5} -> {2, 3}]]
     /. p_Polygon :> Rotate[p, 0(*;Pi/4*), {0, 0}],
    {12}], Background -> Black, ImageSize -> Medium]];

(*note you can edit g in-place*)
Defer[candy][g, 1280, 4]

enter image description here

enter image description here

ring[c_, r_, depth_] := Module[{ps},
   ps = c + r {Cos[#], Sin[#]} & /@ (-Pi/10 + 2. Pi Range[5]/5);

   If[depth == 0, Polygon[ps],
    ring[#, r/2, depth - 1] & /@ ps]];

invert[p_] := p/Norm[p]^2;

Graphics[{Opacity[.15], Black,
   ring[0., 1., #] & /@ Range[5(*;8*)]}] /.
 Polygon[pts_] :> Polygon[invert /@ pts, VertexColors ->
   (ColorData["AvocadoColors"] /@ (#^1.7 &) /@ Norm /@ pts)]

From what I can tell, one of the settings used to deal with division by 0 is the so-called Riemann sphere, which is where we take a space shuttle and use it to fly over and drop a cow on top of a biodome, and then have the cow indiscriminately fire laser beams at the grass inside and around the biodome. That's my intuitive understanding of it anyway.

enter image description here

shuttle = ExampleData[{"Geometry3D", "SpaceShuttle"}, "GraphicsComplex"];
cow = ExampleData[{"Geometry3D", "Cow"}, "GraphicsComplex"];
grass = ExampleData[{"Texture", "Grass4"}];
drop = Sound[Play[Sin[1000 (2 - t)^2.3], {t, 0, .9}]];
lazer[] := EmitSound[Sound[Play[{TriangleWave[10 (2 - t)^5],
      Sin[500 (2 - t)^5] + SawtoothWave[20 (2 - t)^4.99]},
     {t, 0, .2}, SampleRate -> 4000]]];

Animate[Module[{dir, angle},
  (*xs animates from +30 to -60*)
  If[xs > 25, dropTrigger = False];
  If[dropTrigger == False && xs < 10, dropTrigger = True; EmitSound[drop]];
  If[xs < -10,
   lazer[];
   dir = RandomReal[{-15, 15}, 2]~Join~{-20};
   angle = ArcTan @@ Take[dir, 2], angle = 0];

  Labeled[#, Style["Understanding the Riemann Sphere", FontFamily -> "Verdana"], Top] &@
   With[{
     cowLoc = {Clip[xs, {1.3, 50}], 0, Clip[8 + (-(.25 xs - 5)^2), {-13, -.25}]},
     greenLight = Lighting -> {{"Directional", Green, {{5, 5, 5}, {0, 0, 0}}}}},

    Graphics3D[{EdgeForm[None],
      (*shuttle*){Specularity[White, 7], Translate[shuttle, {xs, 0, 0}]},
      (*cow*) Rotate[Translate[Scale[cow, 5], cowLoc], angle, {0, 0, 1}],

      (*grass*) Translate[{If[False, Sequence @@ {greenLight, Texture[grass]}, Green],
        Polygon[50 {{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}},
         VertexTextureCoordinates -> 1.1 {{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}]},
        {0, 0, -19}],

      (*biodome*){Specularity[White, 3], Opacity[.5], Sphere[.95 {0, 0, -20}, 5]},
      (*sun*){Glow[White], Sphere[{18, 18, 18}, 2]},
      (*laser*) If[xs < -10, {Red, Glow[Red], Opacity[.5], Tube[{{0, 0, -12.4}, dir}]}]},
     (*sky*)Background -> LightBlue, PlotRange -> 20, Boxed -> False]]],
 {xs, 30, -60, 3.2}, AnimationRate -> 10, DisplayAllSteps -> True]

(Note the cow cannot be spherical or it will roll off). Personally I don't have any beef with Riemann or any of his manifolds, but for our purposes the Riemann sphere is inadequate since it maps our inverses vertically. One interesting consequence of this is that in the 2D cross section where the imaginary component is zero (essentially the 'Weierstrass circle'), it maps multiplicative inverses vertically and additive inverses horizontally. This all seems mathematically expedient, but it's otherwise boring.

The Riemann sphere does give one explanation though about 'why' our circles and lines are inverses. In the Riemann sphere, the inverse of a circle that crosses the origin is a circle that crosses the North Pole, and since the lasers are being shot from the North Pole, they're limited to tracing out a line as they follow the circle. I was going to make a simple 3D diagram demonstrating this, but I accidentally made this:

toRiemann = Compile[{{pts, _Real, 2}}, Module[{k},
    Map[(k = 2/(1 + #.#);
       {k #[[1]], k #[[2]], 1 - k}) &, pts]]];

fromRiemann[pts_] := (-1/(#3 - 1)) {#1, #2, 0} & @@@ pts;

shuttle = ExampleData[{"Geometry3D", "SpaceShuttle"}, "GraphicsComplex"];
cow = Append[ExampleData[{"Geometry3D", "Cow"}, "GraphicsComplex"],
   VertexTextureCoordinates -> 1/500 ExampleData[{"Geometry3D", "Cow"}, "PolygonData"]];
sphere = SphericalPlot3D[1, {\[Theta], 0, Pi}, {\[Phi], 0, 2 Pi},
   MeshStyle -> Opacity[.05], PlotStyle -> Opacity[.1]];
plane = {Lighting -> "Neutral", Opacity[.5], LightGray, EdgeForm[None],
   Line[{{-50, 0, 0}, {50, 0, 0}}], Line[{{0, -50, 0}, {0, 50, 0}}],
   Polygon[15 {{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}}]};

numPoints = 160/4;
circle = .25 {1 + Cos[#], Sin[#], 0} & /@ (-Pi + 2 Pi Range[numPoints]/numPoints);
rCircle = toRiemann[circle];
invRCircle = {#1, #2, -#3} & @@@ rCircle;
invCircle = Quiet[fromRiemann[invRCircle]];

ind = Indeterminate {1, 1, 1};
transform[\[Theta]_] := Composition[
   RotationTransform[\[Theta], {0, 0, 1}],
   TranslationTransform[{0, 0, 1.2}]];
{l, r} = {{.335, -.044, .1894}, {.335, .044, .1894}};

slides = ParallelTable[Module[{angle},
    angle = Quiet[ArcTan @@ (invCircle /. ind -> {0, 0})[[pti, 1 ;; 2]] /.  ArcTan[0, 0] -> 0];
    Show[sphere,
     Graphics3D[{
       plane, Opacity[.7], Sphere[{0, 0, 1}, .01],
       (*cow*) Rotate[#, angle, {0, 0, 1}] &@Translate[#, {0, 0, 1.2}] &@
        {EdgeForm[None],(*Opacity[.999],*)Texture[Graphics[Disk[]]],
         Lighting -> "Neutral", Lighting -> {{"Point", Darker[Red], l}},
         cow, Red, Glow[Red], Sphere[{l, r}, .01]},
       (* keep shuttle in orbit in case need more cows *)
       {EdgeForm[None], Translate[shuttle, {0, 0, 100}]},
       (*lazerz*) If[invCircle[[pti]] =!= ind, {Red,
         Line[{transform[angle][l], invCircle[[pti]]}],
         Line[{transform[angle][r], invCircle[[pti]]}],
         {Red, Glow[Red], Opacity[.1], Sphere[invCircle[[pti]], .02 RandomReal[]]}}],
       (*etc*) {Lighter[Gray], Dashed, Line[{rCircle[[pti]], invRCircle[[pti]]}]},
       {Opacity[.1], Lighter[Blue], Line[circle]},
       {Opacity[.5], Lighter[Blue], Line[Take[circle, pti]]},
       {Lighter[Blue], Line[{{0, 0, 1}, 50 (-{0, 0, 1} + circle[[pti]])}]},
       {Lighter[Blue], Line[Take[rCircle, pti]]},
       {Red, Line[Take[invRCircle, pti]]},
       (*Purple,Line[{{0,0,1},50(-{0,0,1}+invCircle[[pti]])}/.tride->{0,0,1}],*)
       (*burn mark*) Thick, Red, Line[DeleteCases[Take[invCircle, pti], ind]]}],
     ImageSize -> 1/4 {16, 9} (1080/9),
     ViewAngle -> 4 Degree, PlotRange -> 10, Boxed -> False, Axes -> False]],
   {pti, 1, Length[circle]}];
ListAnimate[slides]

Oops. But since we now have this tool, let's see what other plots look like:

enter image description here

enter image description here

enter image description here

enter image description here

toRiemann = Compile[{{pts, _Real, 2}}, Module[{k},
    Map[(k = 2/(1 + #.#);
       {k #[[1]], k #[[2]], 1 - k}) &, pts]]];

shuttle = ExampleData[{"Geometry3D", "SpaceShuttle"}, "GraphicsComplex"];
cow = Append[ExampleData[{"Geometry3D", "Cow"}, "GraphicsComplex"],
   VertexTextureCoordinates -> 1/500 ExampleData[{"Geometry3D", "Cow"}, "PolygonData"]];
sphere = SphericalPlot3D[1, {\[Theta], 0, Pi}, {\[Phi], 0, 2 Pi},
   MeshStyle -> Opacity[.05], PlotStyle -> Opacity[.1]];
plane = {Lighting -> "Neutral", Opacity[.5], LightGray, EdgeForm[None],
   Line[{{-50, 0, 0}, {50, 0, 0}}], Line[{{0, -50, 0}, {0, 50, 0}}],
   Polygon[15 {{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}}]};

listRiemannPlot[pts_] := Module[{rPts = toRiemann[pts]},
   Show[
    sphere,
    Graphics3D[{
      plane, Opacity[.7], Sphere[{0, 0, 1}, .01],

      (*cow*)Translate[#, {0, 0, 1.2}] &@
       {EdgeForm[None],(*Opacity[.999],*)
        Texture[Graphics[Disk[]]], Lighting -> "Neutral", cow},

      (*shuttle*){EdgeForm[None], Translate[shuttle, {0, 0, 100}]},
      (*original*){Opacity[2 .2], Lighter[Blue], Line[pts]},
      (*riemannized*){Opacity[.8], Blue, Line[rPts]}}],

    ViewAngle -> 4 Degree, PlotRange -> 10, Boxed -> False, Axes -> False]];

listRiemannPlot[Table[{x, Sin[2 x], 0}, {x, -40 Pi, 40 Pi, .01}]]

enter image description here

enter image description here

toRiemann = Compile[{{pts, _Real, 2}}, Module[{k},
    Map[(k = 2/(1 + #.#);
       {k #[[1]], k #[[2]], 1 - k}) &, pts]]];

shuttle = ExampleData[{"Geometry3D", "SpaceShuttle"}, "GraphicsComplex"];
cow = Append[ExampleData[{"Geometry3D", "Cow"}, "GraphicsComplex"],
   VertexTextureCoordinates -> 1/500 ExampleData[{"Geometry3D", "Cow"}, "PolygonData"]];
sphere = SphericalPlot3D[1, {\[Theta], 0, Pi}, {\[Phi], 0, 2 Pi},
   MeshStyle -> Opacity[.05], PlotStyle -> Opacity[.1]];
plane = {Lighting -> "Neutral", Opacity[.5], LightGray, EdgeForm[None],
   Line[{{-50, 0, 0}, {50, 0, 0}}], Line[{{0, -50, 0}, {0, 50, 0}}],
   Polygon[15 {{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}}]};

riemannize[Graphics[g_, rest___], options___] :=
  Show[sphere,
   Graphics3D[{plane,
     (*cow*)Translate[#, {0, 0, 1.2}] &@
      {EdgeForm[None],
       Texture[Graphics[Disk[]]], Lighting -> "Neutral", cow},
     (*shuttle*)Translate[shuttle, {0, 0, 100}],
     (*curves*)g /. Line[pts_] :> {Line[toRiemann[pts]],
        Opacity[.25], Line[{#1, #2, 0} & @@@ pts]}}],
   Boxed -> False, Axes -> None, PlotRange -> 5, options];

plot = Plot[
   Evaluate[y /. Solve[y^2 == x^3 - 3 x + 1, y]],
   {x, -100, 100}, PlotPoints -> 10000];

riemannize[plot, ViewAngle -> 15 Degree]

This is one of those fangled elliptic curves. Apparently they do form pairs of circle things on the Riemann sphere. I thought that was just an old wive's tale. The nice thing about this program is that it works on Graphics structures, such as those returned by Plot. That means you can plug arbitrary 2D plots and graphics into this function and have them automatically Riemannized. Like say you're trying to educe from without your incorrigible students' crania some particular factoid:

enter image description here

enter image description here

enter image description here

toRiemann = Compile[{{pts, _Real, 2}}, Module[{k},
    Map[(k = 2/(1 + #.#); {k #[[1]], k #[[2]], 1 - k}) &, pts]]];

shuttle = ExampleData[{"Geometry3D", "SpaceShuttle"}, "GraphicsComplex"];
cow = Append[ExampleData[{"Geometry3D", "Cow"}, "GraphicsComplex"],
   VertexTextureCoordinates -> 1/500 ExampleData[{"Geometry3D", "Cow"}, "PolygonData"]];
sphere = SphericalPlot3D[1, {\[Theta], 0, Pi}, {\[Phi], 0, 2 Pi},
   MeshStyle -> Opacity[.05], PlotStyle -> Opacity[.1]];
plane = {Lighting -> "Neutral", Opacity[.5], LightGray, EdgeForm[None],
   Line[{{-50, 0, 0}, {50, 0, 0}}], Line[{{0, -50, 0}, {0, 50, 0}}],
   Polygon[15 {{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}}]};

riemannize[Graphics[g_, ___], options___] := Show[sphere,
   Graphics3D[{plane,
     (*cow*)Translate[#, {0, 0, 1.2}] &@
      {EdgeForm[None],(*Opacity[.999],*)
       Texture[Graphics[Disk[]]], Lighting -> "Neutral", cow},
     (*shuttle*)Translate[shuttle, {0, 0, 100}],
     (*curves*)g /. Line[pts_] :> {Line[toRiemann[pts]],
        Opacity[.25], Line[{#1, #2, 0} & @@@ pts]}}],
   Boxed -> False, Axes -> None, PlotRange -> 5, options];

vint = Sqrt[1 - 3/2 (1 + Sqrt[21]) + 1/8 (1 + Sqrt[21])^3];
plot = Show[Plot[y /. Solve[y^2 == x^3 - 3 x + 1, y], {x, -100, 100}, PlotPoints -> 10000],
   Graphics[{Orange, Line[Table[{x, x + 1}, {x, -500, 500, .1}]],
     Black, Dashing[1/10 {0.08, 0.02}],
     Line[Table[{1/2 (1 + Sqrt[21]), y}, {y, -vint, vint, .1}]]}]];

riemannize[plot, ViewAngle -> 8 Degree]

You've set this up with 2D graphics. But you can just plug the output of this into our Riemannizer to get this. In fact in Mathematica you can even copy/paste the 2D plot (itself an interactwithable vector object) like this. And you can vector-edit that plot in-place and when you re-evaluate the expression, the differences will appear in the Riemannization. Not bad for what essentially amounts to one line of code:

g /. Line[pts_] :> Line[toRiemann[pts]]

This is the power of Mathematica's macro-at-will symbolic semantics and well-curated architecture. Specifically in this case, it's the fact that the built-in plotting functions return the same laid-bare Graphics vector structures that your own versions of those functions would return. This Riemannizer only does a direct endpoint conversion of lines, but you can easily have it 3Dify whatever you want in a more thorough fashion.

After I made my Cyclotron 4000 masterpiece, I considered what a version 2 might be. Now I know. With some adjustments to the contraption, we now have the Cycowtron 4800 Deluxe (pronounced psy-cow-tron forty-eight-hundred de-lux):

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

toRiemann = Compile[{{pts, _Real, 2}}, Module[{k},
    Map[(k = 2/(1 + #.#); {k #[[1]], k #[[2]], 1 - k}) &, pts]]];

invertRiemann[pts_] := {#1, #2, -#3} & @@@ pts;
(*fromRiemann[pts_]:=(-1/(#3-1)) {#1,#2,0}&@@@pts;*)

shuttle = With[{shuttleGC = ExampleData[{"Geometry3D", "SpaceShuttle"}, "GraphicsComplex"]},
   Translate[#, {0, 0, 100}] &@
    {EdgeForm[None], Lighting -> "Neutral",
     Append[shuttleGC, VertexColors -> RandomReal[.65 + {0, 1}, Length[shuttleGC[[1]]]]^2]}];

cow = Translate[#, {0, 0, 1.2}] &@
   {EdgeForm[None],(*Opacity[.999],*)Texture[Graphics[Disk[]]], Lighting -> "Neutral",
    Append[ExampleData[{"Geometry3D", "Cow"}, "GraphicsComplex"],
     VertexTextureCoordinates -> 1/500 ExampleData[{"Geometry3D", "Cow"}, "PolygonData"]]};

allSettings = {"sphere", "cow", "original", "riemann", "inverse", "shuttle"};

bookmarks = {
   "Random" :> (
     With[{R := RandomReal[]},
      color = RGBColor[R, R, R];
      inflection = RandomChoice[{1, -1}];
      effect = .25*R^8; thickness = 10*R^8;
      spirality = R^3;
      If[spirality < .035, spirality = 0];
      x = 12 R; y = 2 R];

     Module[{weights = If[x > 3, {.1, .03, .87}, {.1, .07, .83}]},
      \[Zeta] = RandomChoice[{-1, 1}]*
        RandomChoice[weights -> {
           RandomReal[{0, 7.5}],
           RandomChoice[{0, .5, .5}],
           Round[RandomReal[{1, 7.5}], .5]}]]),

   "Rose" :> {color = Red, effect = 0, inflection = -1, x = 0, y = 1.1343, spirality = 0, \[Zeta] = 3},
   "Glyph" :> {color = Black, effect = 0.198, inflection = 1, x = 5.2, y = 0, spirality = 0, \[Zeta] = 2},
   "Mass Atomic" :> { effect = 0, inflection = -1, x = 5.84, y = 0.412, spirality = 0, \[Zeta] = -4.2504},
   "Jello" :> {color = Red, effect = 0, inflection = -1, x = 12, y = 0.846, spirality = 1, \[Zeta] = -1},
   "Grim" :> {thickness = 3.35, color = Black, effect = 0.0675, inflection = 1, x = 8, y = 0.296, spirality = 1, \[Zeta] = -1},
   "Angelwings" :> {color = RGBColor[.07694, .39046, 1], effect = 0, inflection = 1, spirality = 1, x = 12, y = 0, \[Zeta] = -5.4947},
   "Rollers" :> {color = Black, effect = 0, inflection = -1, spiral3ity = 0, x = 5.5, y = .1, \[Zeta] = -0.984032039033508},
   "Lifespark" :> {color = RGBColor[.1026, .9878, .0201], effect = 0, inflection = 1, spirality = .0995, x = 3.2757, y = .2002, \[Zeta] = -5.5}};

With[{
  colorC = Control[{{color, Black, "line color"}, ColorSlider}],
  backgroundC = Control[{{background, White}, ColorSlider}],
  thicknessC = Control[{{thickness, .001, "line thickness"}, .001, 10, Appearance -> "Labeled", ImageSize -> Small}],
  effectC = Control[{{effect, 0., "charcoal effect"}, 0, .25, Appearance -> "Labeled", ImageSize -> Small}],
  inflectionC = Control[{{inflection, 1}, {1 -> " concave ", -1 -> " convex "}, Appearance -> "Vertical"}],
  angularityC = Control[{{\[Zeta], 2., "angularity"}, -7.5, 7.5, .5, Appearance -> "Labeled", ImageSize -> Small}],
  tensionC = Control[{{x, 8., "tension"}, 0, 12, Appearance -> "Labeled", ImageSize -> Small}],
  yC = Control[{{y, 2., "cycle width"}, 0, 2, ImageSize -> Tiny}],
  spiralityC = Control[{{spirality, 0.}, 0, 1, ImageSize -> Tiny}],
  scaleC = Control[{{scale, 3.157, "sphere size"}, .00001, 15, ImageSize -> Medium}],
  settingsC = Control[{{settings, Take[allSettings, 4], "view"}, allSettings, ControlType -> TogglerBar}],
  opacityC = Control[{{opacity, .43}, 0, 1, Appearance -> "Labeled", ImageSize -> Small}],
  resetC = DynamicWrapper[
    Tooltip[Setter[Dynamic[reset], "reset"], "reset perspective", TooltipDelay -> .3],
    If[reset === "reset", (reset = False; vp = {1.3, -2.4, 2}; vv = {0, 0, 1}; {va, vc} = Automatic {1, 1})]]},

 With[{
   controls = Sequence[
     OpenerView[{"Style",
       Column[{
         Row[{
           Column[{backgroundC, colorC}, Alignment -> Right],
           Column[{effectC, thicknessC, opacityC}, Alignment -> Right]},
          Spacer[30]],
         Style[\[HorizontalLine], Lighter[LightGray]]}, Spacings -> 0]}],
     Row[{scaleC, Spacer[30], settingsC}],
     Row[{inflectionC, Spacer[30],
       Column[{angularityC, tensionC}],
       Column[{yC, spiralityC}], Spacer[30], resetC}]],

   storedVars = Sequence @@ ({{#, Automatic}, ControlType -> None} & /@ {vp, vv, va, vc}),
   dynamicView = Sequence[
     ViewPoint -> Dynamic[vp], ViewVertical -> Dynamic[vv],
     ViewAngle -> Dynamic[va], ViewCenter -> Dynamic[vc]]},

  (# /. switch[a_, b_] :> (*macro*)
       Unevaluated[Dynamic[If[MemberQ[settings, a], b, {}]]] &)@
   Manipulate[
    DynamicModule[{g, lines, riemannLines, invertedLines,
      \[Psi] = Round[Abs[FractionalPart[\[Zeta]]]*1., .25] /. {
         0. -> y, .5 -> y/2, .25 | .75 -> y/4}},

     g = ParametricPlot[
       1/(scale^1.4) (1. + spirality*(Log[\[Theta] + 1.] - 1.))*
        {\[Psi] Cos[\[Theta]] + x  Cos[64. \[Theta]] + (1 - effect*RandomReal[])*\[Zeta]* (Cos[512. \[Theta]] + Cos[64. \[Zeta] \[Theta]]),
         \[Psi] Sin[\[Theta]] - x Sin[64. \[Theta]] + (1 - effect*RandomReal[])* inflection*\[Zeta]* (Sin[512. \[Theta]] + Sin[64. \[Zeta] \[Theta]])}

       , {\[Theta], 0, 2 \[Pi]}, ImageSize -> {640, 480}, PerformanceGoal -> "Quality",
       Epilog -> {Gray, Thick, Circle[{0, 0}, 1]},
       PlotStyle -> Dynamic[{{color, Opacity[.43]}}], PlotRange -> Full,
       Background -> Dynamic[background], PlotPoints -> 270, Axes -> None];

     lines = Cases[g, Line[pts_] :> pts, Infinity];
     riemannLines = toRiemann /@ lines;
     invertedLines = invertRiemann /@ riemannLines;
     lines = Map[{##, 0} & @@@ # &, lines];

     ControlActive[g,
      Graphics3D[{
        switch[ "sphere", {Lighting -> "Neutral", Opacity[.1], Sphere[]}],
        switch["shuttle", shuttle], switch["cow", cow],
        Dynamic[color], Dynamic[Opacity[opacity]],
        Dynamic[AbsoluteThickness[thickness]],
        switch["original", Line /@ lines],
        switch["riemann", Line /@ riemannLines],
        switch["inverse", Line /@ invertedLines]}, Boxed -> False,
       dynamicView, Background -> Dynamic[background],
       ImageSize -> {640, 480}]]]

    , controls,
    storedVars,
    {{reset, "reset"}, ControlType -> None},

    Bookmarks -> bookmarks, Alignment -> Center]]]

This thing's almost as curly as my hair. Note that in Mathematica these aren't static renderings. They're regular Graphics3D panes that you can spin and move around every which way. But let's not forget why we're here:

enter image description here

enter image description here

enter image description here

enter image description here

toRiemann = Compile[{{pts, _Real, 2}}, Module[{k},
    Map[(k = 2/(1 + #.#); {k #[[1]], k #[[2]], 1 - k}) &, pts]]];

cow = {EdgeForm[None], Texture[Graphics[Disk[]]], Lighting -> "Neutral",
   Append[ExampleData[{"Geometry3D", "Cow"}, "GraphicsComplex"],
    VertexTextureCoordinates -> 1/500 ExampleData[{"Geometry3D", "Cow"}, "PolygonData"]]};

riemannize[Graphics[g_, ___], options___] := Graphics3D[{
    (*sphere*){Lighting -> "Neutral", Opacity[.07], Sphere[{0, 0, 0}, 1]},
    (*cow*)Rotate[Rotate[Translate[cow, {0, 0, 1.2}], -Pi/2, {0, 0, 1}], -Pi/ 2, {1, 0, 0}],
    (*curves*)g /. (h : Line | Polygon)[pts_] :> {h[{##, 0} & @@@ pts], h[toRiemann[pts]]}},
   options, Boxed -> False];

axiom = Polygon[2 {Cos[#], Sin[#]} & /@ (Pi/2 - 2 Pi Range[3]/3)];

next[prev_] := prev /. Polygon[{p1_, p2_, p3_}] :> {
     Polygon[{p1, (p1 + p2)/2, (p1 + p3)/2}],
     Polygon[{p2, (p2 + p3)/2, (p1 + p2)/2}],
     Polygon[{p3, (p1 + p3)/2, (p2 + p3)/2}]};

draw[n_] := Graphics[{Transparent, EdgeForm[Black], Nest[next, N@axiom, n]}];

riemannize[draw[5], ViewPoint -> Top]
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard