Group Abstract Group Abstract

Message Boards Message Boards

RandomSierpinskiMaze via Wolfram Function Repository

Posted 3 years ago

MODERATOR NOTE: this work was submitted to Computational Art Contest 2022. Check out the contest and other submissions here: https://wolfr.am/CompArt-22


fixed path counts
Fig. 1. Knight's walks, colored by path count summed over all possible starting points along the four exterior edges.

We are happy to announce the release of RandomSierpinskiMaze via Wolfram Function Repository. The documentation already gives an example how to chart a knight's walk through the maze, but for this post, let's take our explanation and plotting to the next fractal level!

The states adjacency graph is produced quite readily as:

RandomSierpinskiMaze = ResourceFunction["RandomSierpinskiMaze"];
SeedRandom["Compare"]; maze0 = RandomSierpinskiMaze[3, AdjacencyGraph];
Graph[#, VertexCoordinates -> (# -> # & /@ VertexList[#])] &@maze0

states adjacency

This graph assumes that the maze explorer moves by $\pm (0,1)$ or by $\pm (1,0)$. With knight's walks we allow and require that steps move by $(\pm1,\pm2)$ or $(\pm2,\pm1)$ for eight different choices of signs. (Knights are sometimes described as moving along a circle with radius $\sqrt{5}$.) The documentation also shows how to superimpose the states adjacency graph assuming the knight-walk transition function:

GraphUnion[maze0, kmaze0, 
 VertexCoordinates -> (# -> # & /@ VertexList[maze0]),
 EdgeStyle ->   Join[# -> Black & /@ EdgeList[maze0], # -> Orange & /@ 
    EdgeList[kmaze0]]] 

Knights graph

And if you look closely, you will see that knights can not achieve an ergodic limit when exploring possibilities of the maze. Their particular movement pattern locks them out of some corners, and makes it difficult to pass through narrow corridors.

Now we can find a difficult looking Knight's walk (in coordinate-free form):

KnightWalkGraph[graph0_] :=  With[{knightsMoves = 
    Flatten[  Outer[#1@{#2 2, #3 1} &, {Reverse, Identity}, {1, -1}, {1, -1}], 2]}, 
  Graph[Select[EdgeList[#], GraphDistance[graph0, #[[1]], #[[2]]] == 3 &]] &@
   Graph[ Union[ Sort /@ Select[
       Flatten[ Outer[UndirectedEdge[#1, #1 + #2] &, VertexList[maze0], 
         knightsMoves, 1]], MemberQ[VertexList[maze0], #[[2]]] &]]]]

kmaze0 =  With[{graph0 = KnightWalkGraph[maze0]}, 
  Graph[HighlightGraph[graph0, 
    PathGraph[FindShortestPath[graph0, {58, 13}, {83, 67}]]]]]

free form knights graph

Plenty of opportunities in there to get lost and take a wrong turn, but if you do, who knows what you will find! Even shortest paths between point A and point B can involve a fair amount of exploration:

With[{knightPath = FindShortestPath[kmaze0, {71, 1}, {83, 67}],
  arrayMaze = (SeedRandom["Compare"]; 
    RandomSierpinskiMaze[3][[1, 1]])  },
 animationData = ArrayPlot[ReplacePart[
       Reverse[arrayMaze], 
       Join[Reverse[# {1, -1} + {1, -1}] -> 2 & /@ Flatten[
            FindShortestPath[maze0, #1, #2][[2 ;; -2]] & @@@ 
             Partition[#, 2, 1], 1],
          Reverse[# {1, -1} + {1, -1}] -> 1 & /@ #] &@
        knightPath[[1 ;; #]]],
      PixelConstrained -> 8, ColorRules -> {
        4 -> LightGray, 5 -> White, 0 -> Black, 1 -> Orange, 
        2 -> LightOrange
        }] & /@ Range[Length[knightPath]];]

ListAnimate[animationData]

knights walk animation

This is just one particularly nice path. There are still many more paths to explore through this maze, and through other mazes generated from different seeds. And yes, if you have a metaverse strategy, mazes can be depicted in three dimensions (also thank the referees asking for extra revisions):

CarpetFractal[n_] :=  Nest[ArrayFlatten[{{#, #, #}, {#, 1, #}, {#, #, #}}] &, {{0, 0, 
    0}, {0, 1, 0}, {0, 0, 0}}, n]

ArrayMesh[Join[With[
    {mazebase = RandomSierpinskiMaze[3][[1, 1, 3 ;; -3, 3 ;; -3]
     ] /. {5 -> 0, 0 -> 1}}, Table[mazebase, {5}]], NestList[
    RotateRight[ Map[First[Sort@Commonest[Flatten[#][[{2, 4, 6, 8}]]]] &, 
       Partition[#, {3, 3}, {1, 1}, {1, 1}], {2}], {1, 1}] &, 
    CarpetFractal[3], 3^3 - 1]][[1 ;; -1 ;; 2]], ImageSize -> 500, 
 ViewVertical -> {1, 0, 0}, ViewPoint -> 2 {1.5, 1.5, 1}]

enter image description here

Have fun & use more colors as desired! For example, here's another interesting plot, where colors indicate that wall topology is different around top center:

another awesome plot

POSTED BY: Brad Klee
5 Replies
Posted 3 years ago

MODERATOR NOTE: a submission to computations art contest, see more: https://wolfr.am/CompArt-22


enter image description here

Actually there is no obvious definition for the center of a maze, so we might as well try a few more calculations using DirectedAcyclicEvaluate. The graphs above are made with three different choices of vertex function:

nnbgDAG = ToDirectedAcyclicGraph[nnbg, Cases[VertexList[nnbg],
    {1, _} | {_, 1} | {83, _} | {_, 83}]];

pathCount = Function[{values, edgeWeights, edges},Total[values] ];

distance = Function[{values, edgeWeights, edges},  values[[1]] + 1 ];

probability = Function[{values, edgeWeights, edges},
   Total[Times[values,1/VertexOutDegree[nnbgDAG, 
      #] & /@ edges[[All, 1]]  ] ] ]; 

It would be a little more sophisticated to calculate probabilities using edge weights, but presently the source code is missing an important optimization using Association (see end note below). It's interesting to compare these functions, and such analysis can possibly help us find our way past the center of the maze and to escape, whatever that actually means.

Assuming a maze explorer can start anywhere on the boundary and moves with constant velocity, minimum distance from the wall to any interior point is proportional to minimal time. Thus distance acts as a time axis, which increases toward magenta. The probability function is correlated to the distance / time function because, as time goes on, maze explorers run out of space to explore. More definitely said, the probability function equally splits values when through-putting along directed edges, so a partial probability amplitude is conserved from time $t$ to $t+1$ only if its location in the maze graph has at least one out component as a next move. As this is not the case for the entire graph, we observe the probability amplitude falling off exponentially with distance / time:

leveledVertices = SortBy[GatherBy[
     DirectedAcyclicEvaluate[nnbgDAG,
       {{_, _} -> 1}, distance]["VertexWeights"],
     #[[2]] &], #[[1, 2]] &][[All, All, 1]];

probs = Total /@ (leveledVertices /. DirectedAcyclicEvaluate[ nnbgDAG,
      {{_, _} -> 1}, prob]["VertexWeights"]);

ListPlot[probs/276, PlotRange -> All, 
 AxesLabel -> {"distance/time", "probability"}]

maze tunneling

This plot somewhat reminds us of quantum mechanical tunneling, but I'll refrain from speculating about possible future physics directions. The graph functions don't account for Schroedinger phases (yet), so there's really no need to mention Quantum.

We previously mentioned a limitation to the metrics above is that, for any chosen starting point, the underlying directed acyclic graph does not reach out to every other location of the maze. In practice, this limits the drama from what it could possibly be when allowing characters to take paths across the entire maze.

One way to correct this deficit is to use only one starting location when calculating the shortest path map of the maze. If we then make shortest path graphs for every possible initial condition, we can sum over all initial conditions to obtain a new set of more detailed measurements of the maze. Now for a little more code:

compGraphs = WeaklyConnectedGraphComponents[nnbg];
initConditions = Cases[VertexList[#],
     {1, _} | {_, 1} | {83, _} | {_, 83}] & /@ compGraphs;

dags1 = ToDirectedAcyclicGraph[compGraphs[[1]], {# },
     VertexCoordinates -> Automatic
     ] & /@ initConditions[[1]];

dags2 = ToDirectedAcyclicGraph[compGraphs[[2]], {# },
     VertexCoordinates -> Automatic
     ] & /@ initConditions[[2]];

matrixPathData = Function[{graph},
    ReplacePart[Array[0 &, {83, 83}],
     DirectedAcyclicEvaluate[graph,
       {{_, _} -> 1}]["VertexWeights"]]] /@ dags;

matrixProbData = Function[{graph},
    ReplacePart[Array[0 &, {83, 83}],
     DirectedAcyclicEvaluate[graph,
       {{_, _} -> 1},
       Function[{values, edgeWeights, edges},
        Total[Times[values,
          1/VertexOutDegree[graph, #] & /@ edges[[All, 1]] ] ] ]
       ]["VertexWeights"]]] /@ dags;

matrixDistData = Function[{graph},
    ReplacePart[Array[0 &, {83, 83}],
     DirectedAcyclicEvaluate[graph,
       {{_, _} -> 1},
       Function[{values, edgeWeights, edges},
        values[[1]] + 1 ]
       ]["VertexWeights"]]] /@ dags;

All told it takes about 5-10 minutes to do a similar calculation over 250+ initial conditions, and then we can plot the revised metrics:

HueScaleArrayPlot[summedData_] := With[
  {countsRep = MapIndexed[#1 -> #2[[1]] &,
     DeleteCases[Union[Flatten[summedData]], 0 | -1]]},
  ArrayPlot[Transpose@summedData,
   ColorRules -> Join[# -> Lighter[Hue[
          (# /. countsRep)/Length[countsRep[[All, 1]]]/1.1
          ], .5] & /@ countsRep[[All, 1]], {0 -> Black, -1 -> Red}],
   Frame -> None, PixelConstrained -> 3]]

HueScaleArrayPlot[Total[#]] & /@ {matrixPathData, matrixDistData, 
  matrixProbData}

measurement 2

Higher precision path count and distance measurements are more sensitive to the difficult of accessing certain exterior sections of the maze. The probability metric changes less significantly, continuing to highlight north center as the most difficult region to reach.

Let's assume for the next play, the bishop and the actress are going to enter the maze from different directions, and attempt to synchronize their movements so as to end up leaving the maze together on a distinct third direction. For one path, It is possible to enter South East and exit North center, choosing both endpoints in bottlenecked subspaces. We can plot the multiway subgraph as follows:

VertexKeep[graph_, vertices_, OptionsPattern[Graph]
  ] := VertexDelete[graph,
  Complement[VertexList[graph], vertices],
  Sequence @@ (#[[1]] -> OptionValue[#[[1]]] & /@ Options[Graph])]

Graph[HighlightGraph[compGraphs[[1]],
  Style[VertexKeep[compGraphs[[1]],
    VertexInComponent[dags1[[139]], {28, 1}, Infinity]],
   Orange, Thick]], VertexCoordinates -> (
   # -> Times[#, {1, -1}
       ] & /@ VertexList[dags1[[139]]]), ImageSize -> 500]

path 1

Now by fixing one component of matrix distance data and scanning over components on the second sublattice, we can look through pair correlations to try and find a synchronous partner function:

unsorted = # - Mean[initConditions[[2]]] & /@ initConditions[[2]];
sorted = SortBy[unsorted, N[(ArcTan @@ # + Pi)] &];
inds = Flatten[Position[unsorted, #] & /@ sorted];

correlationData = 
  HueScaleArrayPlot[matrixDistData[[139]] + matrixDistData[[140 + #]]
     ] & /@ Join[Range[139][[inds]], Table[272 - 140, {50}]];
ListAnimate[%]

pair correlations

In this image player 1's starting point is fixed while player 2's starting point rotates clockwise around the boundary. In the end, we find one particularly nice and synchronous convergence of separate paths:

Image[HueScaleArrayPlot[
  matrixDistData[[139]] + matrixDistData[[272]]
], ImageSize -> 6*83]

two paths

And the $(A_1,A_2) \rightarrow B$ multiway subgraph function looks as follows:

paths graphs

It looks like a pretty good plan, but not if king so-and-so (or perhaps one of the saintly seven) happens to hear about it. Whatever pre-existing powers may not see it in their interest that the Bishop and the Actress should meet in the maze and end up leaving together. The scene is much more dramatic if the players have a chance of being intercepted by knights patrolling around maze cycles:

no new meeting

Of course, it's always possible to choose another random seed, and then the meet-and-escape can happen despite the menacing presence of the blue knights:

meeting does happen

These plots prove a concept, but leave more to desire. Shouldn't the players have more of an ability to avoid knights? And shouldn't the knights make more of an effort to give chase? Yes, of course, but please don't discredit incremental progress on account of a future that doesn't yet exist.

End Note

Pay attention to this timing test, now not to use replace rules:

rules = # -> # & /@ Range[1000];
rulesf = Association[rules];
t1 = RepeatedTiming[rulesf[RandomInteger[1000]]];
t2 = RepeatedTiming[RandomInteger[1000] /. rules];
t2[[1]]/t1[[1]]

Out[] > 75
POSTED BY: Brad Klee
Posted 3 years ago
POSTED BY: Brad Klee
Posted 3 years ago
POSTED BY: Brad Klee

Interesting. The mazes can be used into to texturize polyhedra:

img = Import["https://community.wolfram.com//c/portal/getImageAttachment?\filename=FindPartyMap.png&userId=234448"];
ResourceFunction["TexturizePolygons"][#, img, ImageSize -> Medium] & /@ {"Cube", "Octahedron", "SnubCube"}

enter image description here


img2 = Import["https://community.wolfram.com//c/portal/getImageAttachment?\filename=DifficultPath.gif&userId=234448"];
ResourceFunction["TexturizePolygons"][{#, "Net"}, img2, ImageSize -> Medium] & /@ {"Cube", "Octahedron"}

enter image description here


Similar effects can be achieved with RandomMondrian.

POSTED BY: Anton Antonov

enter image description here -- you have earned Featured Contributor Badge enter image description here Your exceptional post has been selected for our editorial column Staff Picks http://wolfr.am/StaffPicks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

POSTED BY: EDITORIAL BOARD