{ "format": "srwb", "version": "1.0", "notebook": { "name": "Markov Groups: A Random Walk on Cube Moves", "cells": [ { "name": "intro", "type": "markdown", "language": "markdown", "code": "# Markov Groups: A Random Walk on Cube Moves\n\nThis lesson separates three ideas:\n\n1. A legal cube move is a **permutation** of sticker positions.\n2. Repeating one quarter turn gives powers with exponent **modulo 4**.\n3. Choosing legal moves at random creates a **Markov chain** on reachable cube states.\n\nWe use a standard 3×3×3 Rubik's Cube with 54 labelled stickers. Labels `1..54` identify individual stickers, not just colours. All six face centres stay fixed. A quarter turn is clockwise when you look directly at the outside of the turned face; see [Rubik's official move guide](https://rubiks.com/solve-guide).\n\n## Runtime and run order\n\nRun the Python cells in order: `cube_moves`, `check_moves`, `show_turn`, `transition_matrix`, `sticker_step`, then `random_walk`. They share the Python session. NumPy is the only package used and SciREPL loads it with its Pyodide runtime; first use may need a runtime download. No AI account, prompt execution, extra package installation, or external data is needed. This catalog copy has no saved outputs.\n\nBefore running `check_moves`, predict: which stickers must never change position, and how many repeated quarter turns should restore every label?" }, { "name": "coordinates", "type": "markdown", "language": "markdown", "code": "## Stage 1 — fix the layout and matrix convention\n\nFace order is **U, D, F, B, L, R** (up, down, front, back, left, right). Each face occupies nine consecutive **zero-based positions**, read row-major while looking at that face from outside:\n\n```\n0 1 2\n3 4 5\n6 7 8\n```\n\nAdd `9 * face_index` for a particular face. The centre positions are `4, 13, 22, 31, 40, 49`. Sticker labels are one-based, so the label initially at position `j` is `j + 1`.\n\n### Global axes and face-view axes\n\nThe **global axes stay fixed** for the whole cube: `+x` points right, `+y` points up, and `+z` points toward the front. Thus `(0, 0, -1)` points toward the back.\n\n\nFixed global cube axes\nThe cube has front F, top U and right R faces. Global positive x points right, positive y points up, and positive z points toward the front.\n\n\n\nFUR\n\n\n\n\n\n\n+x+y+z\n\n\n\nThe table uses a separate **local frame** for each face. Imagine a small sheet of paper on that face, viewed from outside the cube: `r` points right along the paper and `u` points up along the paper. These are not necessarily global right and global up. The outward normal `n` points perpendicular to the paper, away from the cube, rather than along it.\n\nEach row satisfies `r × u = n`. By the right-hand rule, curl the fingers of your right hand from the local right direction toward local up; your thumb points along the outward normal. On the front face F, these directions are `r = +x`, `u = +y`, and `n = +z`.\n\n| Face | Outward normal (`n`) | Right in face view (`r`) | Up in face view (`u`) |\n| --- | --- | --- | --- |\n| U | (0, 1, 0) | (1, 0, 0) | (0, 0, -1) |\n| D | (0, -1, 0) | (1, 0, 0) | (0, 0, 1) |\n| F | (0, 0, 1) | (1, 0, 0) | (0, 1, 0) |\n| B | (0, 0, -1) | (-1, 0, 0) | (0, 1, 0) |\n| L | (-1, 0, 0) | (0, 0, 1) | (0, 1, 0) |\n| R | (1, 0, 0) | (0, 0, -1) | (0, 1, 0) |\n\n### Why top-face up points toward the back\n\nStart with the front-facing paper. Tilt it about a line parallel to its right direction, as if on a hinge, then reposition it on top of the cube without another rotation. In this **front-to-top tilt**, `r` stays `+x`, while `u` changes from `+y` to `-z` and `n` changes from `+z` to `+y`. The diagram compares the two frame orientations; it does not depict a sheet folding inward while attached to the actual cube edge.\n\n\nPaper tilted from front to top\nA front sheet has right r along positive x, up u along positive y, and outward normal n along positive z, shown by a circle with a central dot pointing toward the outside viewer. In the front-to-top hinge tilt, right stays positive x, up becomes negative z, and the normal becomes positive y. The arrows show r cross u equals n.\n\n\n\n\n\n\n\n\n\n\nr = +xu = +yn = +z\nr = +xu = -zn = +y\nr = +x+y → -z+z → +y\n\n\nFUr × u = n\n\n\n\nSo on U, up along the paper points toward the back (`-z`), while global up (`+y`) points out of the top face, perpendicular to the paper. This explains the U row without changing the global axes. The tilt illustrates a face-view frame, not a legal cube face move.\n\nThe table fixes **one consistent drawing convention**, not a path-independent “no-twist” rule. Different routes between faces can leave a transported sheet with different in-plane rotations. Use the stated table to choose each face view.\n\nA permutation `p` maps a **source position** `j` to its **destination** `p[j]`. For a column state vector `s`, set `P[p[j], j] = 1`. Then `s_next = P @ s`, equivalently `s_next[p[j]] = s[j]`. Rows are destinations; columns are sources. The next cell uses disjoint cycles, each written in source-to-destination order.\n\n### Constructing every face frame from F\n\nStart with F: `r = +x`, `u = +y`, `n = +z`. Keep the global axes fixed and rotate the whole paper frame. Positive angles follow the right-hand rule about the named positive global axis:\n\n- Starting afresh from F for each face, about global `+y`, obtain R by `+90°`, B by `180°`, and L by `-90°`.\n- Independently from F, about global `+x`, obtain U by `-90°` and D by `+90°`.\n\nAll three frame directions rotate together. On B, this gives `r = -x`, `u = +y`, `n = -z`: viewed from outside the back face, drawing-right points toward global left. The U and D paper planes are horizontal, with normals `+y` and `-y`; “up” within those planes is not global up.\n\nThese specified routes construct the table’s chosen drawing convention. They do not claim that every route between faces gives the same in-plane orientation, and they are not legal cube face moves.\n\n### Face numbering\n\n`FACE_NAMES = \"UDFBLR\"` defines the numbering below. Starting the frame construction from F does not make F the first numbered face. `MOVE_CYCLES` happens to list its keys in the same order as `FACE_NAMES`; reordering only that dictionary does not renumber sticker positions.\n\n| Face (index) | Positions | Printed |\n| --- | --- | --- |\n| `U` — up (`0`) | `0–8` | `1–9` |\n| `D` — down (`1`) | `9–17` | `10–18` |\n| `F` — front (`2`) | `18–26` | `19–27` |\n| `B` — back (`3`) | `27–35` | `28–36` |\n| `L` — left (`4`) | `36–44` | `37–45` |\n| `R` — right (`5`) | `45–53` | `46–54` |\n\nPositions are zero-based. The last column gives the one-based position numbers printed by `show_turn`. In the solved state these also equal the initial sticker labels, but sticker labels move when you scramble.\n" }, { "name": "cube_moves", "type": "code", "language": "python", "code": "import numpy as np\n\nN = 54\nFACE_NAMES = \"UDFBLR\"\nCENTRES = np.array([9 * f + 4 for f in range(6)])\n\n# Five disjoint 4-cycles per clockwise face turn: two on the face,\n# three on the surrounding strips. Indices are zero-based positions.\nMOVE_CYCLES = {\n \"U\": [\n # U: Corner stickers\n [\n 0,\n 2,\n 8,\n 6\n ],\n # U: Edge-middle stickers\n [\n 1,\n 5,\n 7,\n 3\n ],\n # Strip cycles: F → L → B → R → F\n [\n 18,\n 36,\n 27,\n 45\n ],\n # Strip cycles: F → L → B → R → F\n [\n 19,\n 37,\n 28,\n 46\n ],\n # Strip cycles: F → L → B → R → F\n [\n 20,\n 38,\n 29,\n 47\n ]\n ],\n \"D\": [\n # D: Corner stickers\n [\n 9,\n 11,\n 17,\n 15\n ],\n # D: Edge-middle stickers\n [\n 10,\n 14,\n 16,\n 12\n ],\n # Strip cycles: F → R → B → L → F\n [\n 24,\n 51,\n 33,\n 42\n ],\n # Strip cycles: F → R → B → L → F\n [\n 25,\n 52,\n 34,\n 43\n ],\n # Strip cycles: F → R → B → L → F\n [\n 26,\n 53,\n 35,\n 44\n ]\n ],\n \"F\": [\n # Strip cycles: U → R → D → L → U\n [\n 6,\n 45,\n 11,\n 44\n ],\n # Strip cycles: U → R → D → L → U\n [\n 7,\n 48,\n 10,\n 41\n ],\n # Strip cycles: U → R → D → L → U\n [\n 8,\n 51,\n 9,\n 38\n ],\n # F: Corner stickers\n [\n 18,\n 20,\n 26,\n 24\n ],\n # F: Edge-middle stickers\n [\n 19,\n 23,\n 25,\n 21\n ]\n ],\n \"B\": [\n # Strip cycles: U → L → D → R → U\n [\n 0,\n 42,\n 17,\n 47\n ],\n # Strip cycles: U → L → D → R → U\n [\n 1,\n 39,\n 16,\n 50\n ],\n # Strip cycles: U → L → D → R → U\n [\n 2,\n 36,\n 15,\n 53\n ],\n # B: Corner stickers\n [\n 27,\n 29,\n 35,\n 33\n ],\n # B: Edge-middle stickers\n [\n 28,\n 32,\n 34,\n 30\n ]\n ],\n \"L\": [\n # Strip cycles: U → F → D → B → U\n [\n 0,\n 18,\n 9,\n 35\n ],\n # Strip cycles: U → F → D → B → U\n [\n 3,\n 21,\n 12,\n 32\n ],\n # Strip cycles: U → F → D → B → U\n [\n 6,\n 24,\n 15,\n 29\n ],\n # L: Corner stickers\n [\n 36,\n 38,\n 44,\n 42\n ],\n # L: Edge-middle stickers\n [\n 37,\n 41,\n 43,\n 39\n ]\n ],\n \"R\": [\n # Strip cycles: U → B → D → F → U\n [\n 2,\n 33,\n 11,\n 20\n ],\n # Strip cycles: U → B → D → F → U\n [\n 5,\n 30,\n 14,\n 23\n ],\n # Strip cycles: U → B → D → F → U\n [\n 8,\n 27,\n 17,\n 26\n ],\n # R: Corner stickers\n [\n 45,\n 47,\n 53,\n 51\n ],\n # R: Edge-middle stickers\n [\n 46,\n 50,\n 52,\n 48\n ]\n ]\n}\n\ndef turn_perm(name):\n \"\"\"Return p: source position j moves to destination p[j].\"\"\"\n p = np.arange(N)\n for cycle in MOVE_CYCLES[name]:\n for source, destination in zip(cycle, cycle[1:] + cycle[:1]):\n p[source] = destination\n return p\n\ndef perm_matrix(p):\n \"\"\"Rows are destinations, columns are sources.\"\"\"\n p = np.asarray(p)\n if p.shape != (N,) or not np.array_equal(np.sort(p), np.arange(N)):\n raise ValueError(\"Expected a permutation of positions 0..53\")\n P = np.zeros((N, N), dtype=int)\n P[p, np.arange(N)] = 1\n return P\n\nPERMS = {name: turn_perm(name) for name in FACE_NAMES}\nM = {name: perm_matrix(PERMS[name]) for name in FACE_NAMES}\nIDENTITY = np.eye(N, dtype=int)\nSOLVED = np.arange(1, N + 1)\n\ndef sticker_state(state):\n values = np.asarray(state)\n if values.shape != (N,) or not np.array_equal(np.sort(values), SOLVED):\n raise ValueError(\"Expected each sticker label 1..54 exactly once\")\n return values.astype(int, copy=True)\n\ndef apply(state, *moves):\n \"\"\"Apply moves left to right; an apostrophe means the inverse.\"\"\"\n result = sticker_state(state)\n for move in moves:\n if move == \"I\":\n continue\n if move in M:\n matrix = M[move]\n elif move.endswith(\"'\") and move[:-1] in M:\n matrix = M[move[:-1]].T\n else:\n raise ValueError(\"Unknown move: \" + move)\n result = matrix @ result\n return result\n\ndef stickers_to_colours(state):\n \"\"\"Return home-face letters as colour labels, not physical paint names.\"\"\"\n return [FACE_NAMES[(int(label) - 1) // 9] for label in sticker_state(state)]\n\nprint(\"Built six clockwise quarter-turn matrices with fixed face centres.\")" }, { "name": "check_moves", "type": "code", "language": "python", "code": "# Stage 2: verify properties that colours alone cannot reveal.\nfor name in FACE_NAMES:\n p, matrix = PERMS[name], M[name]\n assert np.array_equal(np.sort(p), np.arange(N)), name + \": not bijective\"\n assert np.array_equal(p[CENTRES], CENTRES), name + \": centre moved\"\n assert np.all(matrix.sum(axis=0) == 1), name + \": bad source column\"\n assert np.all(matrix.sum(axis=1) == 1), name + \": bad destination row\"\n assert np.array_equal(np.linalg.matrix_power(matrix, 4), IDENTITY), name + \": fourth power is not identity\"\n assert np.array_equal(matrix.T @ matrix, IDENTITY), name + \": inverse failed\"\n assert np.array_equal(apply(SOLVED, name, name, name, name), SOLVED)\n assert np.array_equal(apply(SOLVED, name, name + \"'\"), SOLVED)\n print(name + \": bijection, fixed centres, four turns, and inverse passed\")\n\nassert not np.array_equal(M[\"U\"] @ M[\"R\"], M[\"R\"] @ M[\"U\"])\nprint(\"All six moves passed. U and R do not commute.\")" }, { "name": "cycles", "type": "markdown", "language": "markdown", "code": "## Stage 3 — circles, cycles, and modular arithmetic\n\nThe lists in `MOVE_CYCLES` describe **sticker positions**. Each entry moves to the next entry in one quarter turn, and the last entry moves back to the first. All five cycles act simultaneously: each sticker advances one step, not around the entire loop in one move.\n\nA turn is **clockwise when viewed directly from outside the turned face**. Its outward normal points toward you; the face turns around that normal axis. Using the zero-based face grid above, the first two U cycles are:\n\n- **Corner stickers:** `[0, 2, 8, 6]` follows top-left → top-right → bottom-right → bottom-left → top-left.\n- **Edge-middle stickers:** `[1, 5, 7, 3]` follows top → right → bottom → left → top.\n\nThese are the clockwise paths you would draw around the face centre. The next cell prints one-based position numbers, so these become `1 → 3 → 9 → 7 → 1` and `2 → 6 → 8 → 4 → 2`. Those numbers identify positions, not necessarily the sticker labels currently occupying them after scrambling.\n\nThe other three U cycles move stickers in the adjacent faces’ top rows around `F → L → B → R → F`. For example, `[18, 36, 27, 45]` sends the sticker at position `18` to position `36` in one U turn; repeated U turns take it to `27`, then `45`, then back to `18`. The other two cycles track the corresponding middle and right positions in those rows.\n\nA physical corner piece (cubie) carries three stickers, and an edge piece carries two. The two face cycles track only their stickers on the turned face; their side stickers occur in the surrounding-strip cycles. Together these sticker paths describe one physical face move, not five independent moves of whole pieces. All face centres stay fixed.\n\nDraw one listed cycle as four positions around a circle. One quarter turn advances one step; two turns advance two steps; four turns restore every sticker position, so `P^4 = I`. For a fixed move `P`, `P^k = P^(k mod 4)`. Its inverse is `P^3 = P.T`, and a half turn is `P^2`.\n\nThis is a cyclic subgroup of order 4. It does **not** make the entire cube group modular arithmetic: different face turns need not commute. With column vectors, “U, then R” is `M[\"R\"] @ M[\"U\"] @ s`, not the reverse matrix product. In the next cell, compare the same two moves in opposite orders.\n\n### How the stored cycle lists are grouped\n\nEach `MOVE_CYCLES` face entry contains five disjoint four-cycles: two on the turned face (corner stickers and edge-middle stickers), and three on neighbouring strips. The order of those groups in the stored list is not universal:\n\n| Face | Face cycles | Strip cycles |\n| --- | --- | --- |\n| `U`, `D` | `1–2` | `3–5` |\n| `F`, `B`, `L`, `R` | `4–5` | `1–3` |\n\nHere the cycle numbers are one-based positions within the list of five cycles, not sticker positions. Each stored cycle begins with its smallest flat sticker index, and the five cycles are sorted by those starting indices. This is a deterministic index-based presentation, not a choice to start at local drawing-right. Within each cycle, entries follow the clockwise source-to-destination path; they are not numerically sorted.\n\nThe U corner and edge examples above are its first two cycles. For F, the first stored cycle is `[6, 45, 11, 44]`: U bottom-left → R top-left → D top-right → L bottom-right → U bottom-left. All four positions lie on neighbouring faces, not F; F’s own face cycles are fourth and fifth.\n\nA flat index is `9 * face_index + 3 * row + column`, with `row = 0` at the top and `column = 0` at the left in that face’s outside view. Each listed integer is an absolute zero-based sticker position across all six faces, not a component of local `r` or `u`.\n\nMathematically, the starting entry can be shifted: `[a, b, c, d]` and `[b, c, d, a]` describe the same cycle. The source data chooses the minimum-index rule above. Reversing the source-to-destination order gives the inverse cycle.\n\nThe arrows always mean source → destination for one clockwise quarter turn viewed from outside the turned face, about its outward normal. `show_turn` adds `1` to each stored position when printing it; this changes the displayed numbering, not the move.\n" }, { "name": "show_turn", "type": "code", "language": "python", "code": "after_u = apply(SOLVED, \"U\")\nprint(\"U cycles, shown as one-based sticker positions:\")\nfor cycle in MOVE_CYCLES[\"U\"]:\n print(\" -> \".join(str(position + 1) for position in cycle + cycle[:1]))\n\n# F is face index 2, so its top row is at positions 18, 19, 20.\nfront_top = np.arange(18, 21)\nprint(\"\\nF top row labels before U:\", SOLVED[front_top].tolist())\nprint(\"F top row labels after U: \", after_u[front_top].tolist())\nprint(\"Face-letter colours after U:\", stickers_to_colours(after_u))\nprint(\"U then R equals R then U:\",\n np.array_equal(apply(SOLVED, \"U\", \"R\"), apply(SOLVED, \"R\", \"U\")))\nassert not np.array_equal(apply(SOLVED, \"U\", \"R\"), apply(SOLVED, \"R\", \"U\"))" }, { "name": "markov_bridge", "type": "markdown", "language": "markdown", "code": "## Stage 4 — choose a move at random\n\n### Build the sticker-location transition matrix\n\nChoose independently and uniformly among **13 options** at each step: stay still (`I`), the six quarter turns, and their six inverses. Each has probability `1/13`.\n\nFor the **whole cube**, this is a random walk on a graph. Vertices are reachable configurations of the original labelled cube; a face turn follows an edge, and the identity makes a self-loop. Next-state probabilities depend only on the current configuration: this is a Markov chain.\n\nFirst build a smaller **54×54** matrix `T`, tracking **one sticker's position**, not the entire configuration. Every chosen move still acts on the whole cube; we simply record where one chosen sticker ends up.\n\n`M[name]` contains zeros and ones, not destination indices. Entry `M[name][d, j]` is 1 when that turn sends source position `j` to destination position `d`. Rows are destinations; columns are sources. The transpose represents the inverse permutation, so `M[name].T` reverses the turn.\n\nThe next cell starts with an identity-matrix copy and adds each turn and inverse. Entry `move_counts[d, j]` counts how many choices send source position `j` to destination position `d`. We add these matrices entry by entry because they are alternatives for one step, not moves performed in sequence. Powers of `T` account for longer sequences. Dividing by 13 gives the transition probability:\n\n$$\nT_{d,j}=\\frac{1}{13}\\sum_{a\\in\\mathrm{MOVE\\_OPTIONS}}(P_a)_{d,j}.\n$$\n\nHere `a` is a move choice and `P_a` its permutation matrix, including the identity. Different choices can have the same sticker destination. Each permutation matrix has one 1 per column and one 1 per row, so averaging them gives column sums of 1 and row sums of 1. Separately, equal weighting of each turn and its inverse makes `T` symmetric." }, { "name": "transition_matrix", "type": "code", "language": "python", "code": "MOVE_OPTIONS = [\"I\", \"U\", \"U'\", \"D\", \"D'\", \"F\", \"F'\", \"B\", \"B'\", \"L\", \"L'\", \"R\", \"R'\"]\nmove_counts = IDENTITY.copy()\nfor name in FACE_NAMES:\n move_counts += M[name]\n move_counts += M[name].T\nT = move_counts / len(MOVE_OPTIONS)\nassert np.all(T >= 0)\nassert np.allclose(T.sum(axis=0), 1)\nassert np.allclose(T.sum(axis=1), 1)\nassert np.allclose(T, T.T)" }, { "name": "sticker_bridge", "type": "markdown", "language": "markdown", "code": "### Follow one chosen sticker for one step\n\nChoose **sticker label 1**, initially at zero-based position **0**. `SOLVED` stores the solved cube's sticker labels: `SOLVED = [1, 2, ..., 54]`. The index is a position; the value is the label occupying it. For example, `SOLVED[0] = 1` and `SOLVED[5] = 6`. Thus `SOLVED[j]` identifies a sticker at position `j`, whereas `location_probability[j]` is the probability that our chosen sticker occupies that position. Same vector length, different meanings.\n\nWrite `location_probability` as `x` in the equations, with superscripts counting steps. For **any starting probability distribution**, the update is:\n\n$$\nx_d^{(1)}=\\sum_{i=0}^{53}T_{d,i}x_i^{(0)}.\n$$\n\nIf the starting position is certainly `j`, the distribution is **one-hot at position `j`**: entry `j` is 1 and every other entry is zero. Only that column contributes, so the result is column `j` of `T`:\n\n$$\n\\begin{gathered}\nx_i^{(0)}=\\begin{cases}1,&i=j,\\\\0,&i\\ne j,\\end{cases}\\\\\n\\Longrightarrow\\quad x_d^{(1)}=T_{d,j}.\n\\end{gathered}\n$$\n\nThe code uses the particular case `j = 0`: `location_probability[0] = 1`, so sticker **1**, which starts at position 0, is the one we follow. Then `after_one_step = T @ location_probability` equals `T[:, 0]`, the first column of `T` (in NumPy, `:` means all rows). Entry `d` of that column, `after_one_step[d] = T[d, 0]`, is the probability that sticker 1 is at position `d` after one random move. Thus `T[:, 0]` is the distribution of destinations for the sticker starting at position 0. This equality relies on the one-hot start; an uncertain starting position gives a weighted combination of columns instead.\n\nThe choices `I`, `D`, `D'`, `F`, `F'`, `R`, and `R'` leave position 0 unchanged, so `T[0, 0] = 7/13`. The other choices send it to these positions: `U` → `2`, `U'` → `6`, `L` → `18`, `L'` → `35`, `B` → `42`, and `B'` → `47`. Each has probability `1/13`.\n\nThe printed mass of 1 checks **normalization only**, not uniform sticker locations or whole-cube mixing. `T` is generally not a permutation: applying it to `SOLVED` can give fractional labels, not a legal configuration." }, { "name": "sticker_step", "type": "code", "language": "python", "code": "# A distribution for the location of one particular sticker.\nlocation_probability = np.zeros(N)\nlocation_probability[0] = 1\nafter_one_step = T @ location_probability\nassert np.isclose(after_one_step.sum(), 1)\nprint(\"One-sticker probability mass after one step:\", round(float(after_one_step.sum()), 6))" }, { "name": "trajectory_bridge", "type": "markdown", "language": "markdown", "code": "### Sample an actual whole-cube trajectory\n\nNow `rng.choice` samples 12 actual choices, and `apply(SOLVED, *moves)` applies those permutations to the whole cube. This gives one valid configuration, not an average of labels. The seed makes it reproducible, not uniformly mixed. Assertions check preservation of every sticker label and all six centres.\n\nFor `n` steps in the **sticker-location model**, use `np.linalg.matrix_power(T, n) @ location_probability`. This averages over all ordered sequences of `n` independent choices, each with probability `1 / 13**n`. For two steps:\n\n$$\nT^2=\\frac{1}{13^2}\\sum_{a,b\\in\\mathrm{MOVE\\_OPTIONS}}P_bP_a.\n$$\n\nHere `a` is chosen first, then `b`. Both “U then R” and “R then U” contribute: `M[\"R\"] @ M[\"U\"]` and `M[\"U\"] @ M[\"R\"]`. Averaging does not make different move orders equivalent.\n\nThe whole-cube walk has a uniform stationary distribution because every move permutes its configurations. The graph is finite and connected on reachable configurations (irreducible), and the identity self-loop makes it aperiodic. Together these give eventual convergence to that distribution, but neither a mixing time nor uniform scrambling after 12 choices." }, { "name": "random_walk", "type": "code", "language": "python", "code": "# Sample a valid cube trajectory. The seed makes this example reproducible,\n# not uniformly mixed over all reachable cube configurations.\nrng = np.random.default_rng(7)\nmoves = rng.choice(MOVE_OPTIONS, size=12).tolist()\nwalk_state = apply(SOLVED, *moves)\nassert np.array_equal(np.sort(walk_state), SOLVED)\nassert np.array_equal(walk_state[CENTRES], SOLVED[CENTRES])\nprint(\"Sampled moves:\", \" \".join(moves))\nprint(\"Final face-letter colours:\", stickers_to_colours(walk_state))\nprint(\"The sampled path preserved every sticker label and all six centres.\")" }, { "name": "takeaways", "type": "markdown", "language": "markdown", "code": "## Check your understanding\n\n1. Why does checking only colours fail to reveal some incorrect permutations within a face?\n2. Why must a quarter turn fix the centre and satisfy `P^4 = I`?\n3. Why is the matrix product for “U, then R” written in reverse chronological order?\n4. Why does modulo 4 describe powers of one face turn, but not arbitrary sequences of different face turns?\n5. What does the 54×54 stochastic matrix describe? Why is it not the full cube-state Markov chain?\n6. Why does a reproducible 12-step random walk not prove uniform scrambling?\n\nThe full cube group is generated by the six face moves, with inverse moves already obtainable as third powers. We do not claim that U, R, and F alone generate the full group: all three leave the opposite D–B–L corner untouched.\n\n**Main lesson:** distinguish a legal permutation, its cyclic powers, and a probability mixture of moves. Those are connected ideas, not interchangeable objects.\n\n**Hint for question 5:** The chosen sticker is a corner sticker. Face turns keep it among the 24 corner-sticker positions; it cannot move into an edge or centre position. This smaller model tracks that sticker's location, not the joint positions of all stickers. Its long-run distribution is therefore not uniform across all 54 positions." } ] } }