[{"content":" Note\nThis is a work in progress. I am by no means an expert in either Go or Python, so don\u0026rsquo;t be too harsh on me if you find some mistakes. But you can point out anything in the comment section.\nWhat is Go? First of all, If you\u0026rsquo;re really unfamiliar with even the name, this post may not be for you. You can take a look at the Wikipedia page to get some basic ideas.\nSome of you may have heard of it from the famous match between AlphaGo and Lee Sedol. And you may be wondering if this game would be hard to implement in Python. Nah, I guess the hardest part is only about the AI, that is, how to decide which move to go, among the many valid moves. But we are not going that far. A basic implementation of the game is quite a standard practice in Python.\nLet\u0026rsquo;s start!\nI have no plan to implement all of the game today. At this moment let\u0026rsquo;s quickly go thorough several ideas playing a center role in the logic of the game.\nBasic setting Assuming the board is $n\\times n$, where n is a positive integer, for example $n=9, 13, 19.$ Now let\u0026rsquo;s denote the set $\\{1,\\dots,9\\}$ by $N$. A state or configuration is, mathematically speaking, a mapping $\\phi$: $N\\times N$ to $\\{-1, 0, 1\\}$, where $-1$ means black, $1$ means white, and $0$ means no stones. Note that is just a definition, and not all such states are legal. Therefore: A valid state is only proper subset of all states. The rules are too detailed as it\u0026rsquo;s about the different rule sets of the game (Chinese rules, Japanese rules\u0026hellip;). Key ideas so at this moment let\u0026rsquo;s just list some most important ones:\nStones which are neighbors and of the same color are called clusters (that is, a connected component, to put it mathematically). When counting liberties, stones in the same cluster are regarded as sharing the same liberties. A cluster having no liberties is called dead, and they cannot appear in a valid state. Therefore, the liberty of a given single stone is defined to be the liberty of the cluster it belongs to. Now let\u0026rsquo;s translate the 3 things listed to programming language. We need to:\nDefine a function to tell if two stones are neighbors (regardless of color). This is very easy.\nInput: a stone, Output: the cluster it belongs to. This is not easy. I believe it should has some important algorithm (I used Breadth-first search for this attempt, but I have no idea on if it\u0026rsquo;s the best for our purpose, nor, if there exist some pre-build libraries for doing this) behind it. Naively, we may use some for-loops to do it but it can be very non-efficient. This would be a key part of the main program. Then: given a state, we need to find all clusters. This is based on the previous item. Given a cluster, we need to count its liberties. This is less difficult than 2.\nToday, let\u0026rsquo;s try a little bit. First of all we need to denote a state. The most math way of denoting a state should be a matrix of $n\\times n$ whose entries are $-1, 0, 1$. But in Python, we can use a list of lists to denote it, thanks to the numpy library.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import numpy as np # first let\u0026#39;s look at an example of a 9x9 state. # we can represent the board as a 9x9 numpy array state = np.array([[0, 0, 0, 0, 0, 0, 0, 0, -1], [0, 1, 1, 0, 0, -1, -1, -1, 0], [0, 0, 1, -1, -1, -1, -1, 0, 0], [0, 0, 0, 1, -1, -1, 0, 0, 0], [0, 0, 1, 1, -1, 0, 0, 0, 0], [1, 1, 1, 1, 0, -1, 0, 0, 0], [0, 0, 1, 0, 0, -1, 1, 0, 0], [0, 1, 1, 0, 0, 0, 0, 1, 0], [-1, 0, 0, 0, 0, 0, 0, 0, -1]]) print(state) print(state[(0,0)]) print(state[(0,8)]) print(state[(2,2)]) [[ 0 0 0 0 0 0 0 0 -1] [ 0 1 1 0 0 -1 -1 -1 0] [ 0 0 1 -1 -1 -1 -1 0 0] [ 0 0 0 1 -1 -1 0 0 0] [ 0 0 1 1 -1 0 0 0 0] [ 1 1 1 1 0 -1 0 0 0] [ 0 0 1 0 0 -1 1 0 0] [ 0 1 1 0 0 0 0 1 0] [-1 0 0 0 0 0 0 0 -1]] 0 -1 1 To precisely implement a state would be out of today\u0026rsquo;s range. Now we only need to remember this kind of structure and write a function to tell if two coordinates are neighbors. Input: $x=(x_1,x_2)$ and $y=(y_1,y_2)$, output: True or False.\nFirst we need to check if a coordinate $(x_1,x_2)$ is valid. Two things to check:\nif this is a tuple of length 2; if the entries are integers of $0,\\dots,8$. 1 2 3 4 5 def is_valid_coordinate(coord): if isinstance(coord, tuple) and len(coord) == 2: if 0 \u0026lt;= coord[0] \u0026lt; 9 and 0 \u0026lt;= coord[1] \u0026lt; 9: return True return False 1 2 3 4 5 6 7 8 # test print(is_valid_coordinate((0, 0))) # True print(is_valid_coordinate((0, 9))) # False print(is_valid_coordinate((9, 0))) # False print(is_valid_coordinate((9, 9))) # False print(is_valid_coordinate((0, 0, 0))) # False print(is_valid_coordinate(0)) # False print(is_valid_coordinate((0, 0.0))) # True True False False False False False True Now we do is_neighbor. We say two valid coordinates are neighbors if they are adjacent in the horizontal or vertical direction, that is, if $|x_1-y_1|+|x_2-y_2|=1$. (Note that one stone has no way to neighbor itself.)\n1 2 3 4 def is_neighbor(coord_x, coord_y): if not is_valid_coordinate(coord_x) or not is_valid_coordinate(coord_y): raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) return abs(coord_x[0] - coord_y[0]) + abs(coord_x[1] - coord_y[1]) == 1 1 2 3 4 5 6 7 # test print(is_neighbor((0, 0), (0, 1))) # True print(is_neighbor((0, 0), (1, 0))) # True print(is_neighbor((0, 0), (1, 1))) # False print(is_neighbor((0, 0), (0, 0))) # False print(is_neighbor((0, 0), (0, 2))) # False print(is_neighbor((0, 0), (2, 0))) # False True True False False False False Then we define a function to tell if two stones of a state is of the same color. Input: a state and two coordinates, output: True or False.\n1 2 3 4 5 6 7 8 9 10 def is_same_color(coord_x, coord_y, state): if not is_valid_coordinate(coord_x) or not is_valid_coordinate(coord_y): raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) # note that two coordinates should be different if coord_x == coord_y: raise ValueError(\u0026#34;Two coordinates should be different.\u0026#34;) # note that \u0026#39;no stone\u0026#39; is not a color if state[coord_x] == 0 or state[coord_y] == 0: raise ValueError(\u0026#34;No stone at the given coordinates.\u0026#34;) return state[coord_x] == state[coord_y] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # Test 1 try: print(is_same_color((3, 5), (5, 5), state)) # Expected output: False except ValueError as e: print(\u0026#34;Error:\u0026#34;, str(e)) # Test 2 try: print(is_same_color((1, 6), (1, 7), state)) # Expected output: False except ValueError as e: print(\u0026#34;Error:\u0026#34;, str(e)) # Test 3 try: print(is_same_color((0, 0), (1, 1), state)) # Expected output: False except ValueError as e: print(\u0026#34;Error:\u0026#34;, str(e)) # Test 4 try: print(is_same_color((0, 0), (0, 0), state)) # Expected output: True except ValueError as e: print(\u0026#34;Error:\u0026#34;, str(e)) # Test 5 try: print(is_same_color((0, 0), (0, 2), state)) # Expected output: False except ValueError as e: print(\u0026#34;Error:\u0026#34;, str(e)) True True Error: No stone at the given coordinates. Error: Two coordinates should be different. Error: No stone at the given coordinates. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def is_valid_coordinates(coords, state): if not isinstance(coords, set): raise ValueError(\u0026#34;Input should be a set of coordinates.\u0026#34;) return False if len(coords) == 0: raise ValueError(\u0026#34;Input should not be empty.\u0026#34;) return False for coord in coords: if not is_valid_coordinate(coord): raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) return False return True # the difference here is, the next function checks if all stones belong to the same player, while the previous function only checks if all stones are legal on the board, regardless of color. def is_valid_one_color_coordinates(coords, state): if not isinstance(coords, set): raise ValueError(\u0026#34;Input should be a set of coordinates.\u0026#34;) return False if len(coords) == 0: raise ValueError(\u0026#34;Input should not be empty.\u0026#34;) return False color = state[next(iter(coords))] if color == 0: raise ValueError(\u0026#34;No stone at some given coordinates.\u0026#34;) return False for coord in coords: if state[coord] != color: raise ValueError(\u0026#34;All stones should have the same color.\u0026#34;) return False if not is_valid_coordinate(coord): raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) return False return True Neighbors: frontiers, enemy frontiers and empty neighbors For each stone on the board, define its frontier as the all coordinates which shares the same color and is a neighbor of the stone.\nTo do this, it\u0026rsquo;s convenient for us to define a more general function.\nDefine find_neighbors, where neighbors are all coordinates which are valid, regardless of the color. Among the neighbors, filter the coordinates with the same color as the given coordinate. Then according to different color, we can find find_frontiers (which are the neighbors with the same color as the given coordinate), find_enemy_frontiers (which are the neighbors with the different color as the given coordinate), and find_empty_neighbors (which are the neighbors with no stone). 1 2 3 4 5 6 7 8 9 10 11 12 def find_neighbors(coords, state): if is_valid_coordinates(coords, state): neighbors = set() for coord in coords: n = state.shape[0] for i in range(n): for j in range(n): if is_neighbor((i, j), coord) and (i, j) not in coords: neighbors.add((i, j)) return neighbors else: raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) 1 2 3 4 5 # test print(find_neighbors({(0, 0)}, state)) # test for a set of coordinates of many isolated stones print(find_neighbors({(0, 0), (1, 1), (8, 8)}, state)) print(find_neighbors({(0, 0), (1, 1)}, state)) {(0, 1), (1, 0)} {(0, 1), (1, 2), (2, 1), (8, 7), (1, 0), (7, 8)} {(0, 1), (1, 0), (1, 2), (2, 1)} find_frontiers is nothing but the coordinates with the same color as the given coordinates in the neighbors. Only thing you need to check is, if the input set is of the same color or not.\n1 2 3 4 5 6 7 8 9 10 11 12 def find_frontiers(coords, state): if is_valid_one_color_coordinates(coords, state): color = state[next(iter(coords))] neighbors = find_neighbors(coords, state) frontiers = set() for neighbor in neighbors: if state[neighbor] == color: frontiers.add(neighbor) return frontiers else: raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) Similarly, we can find find_enemy_frontiers as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 def find_enemy_frontiers(coords, state): if is_valid_one_color_coordinates(coords, state): color = state[next(iter(coords))] neighbors = find_neighbors(coords, state) enemy_frontiers = set() for neighbor in neighbors: if state[neighbor] == -color: enemy_frontiers.add(neighbor) return enemy_frontiers else: raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) Lastly, we can find_empty_neighbors as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 def find_empty_neighbors(coords, state): if is_valid_one_color_coordinates(coords, state): color = state[next(iter(coords))] neighbors = find_neighbors(coords, state) empty_neighbors = set() for neighbor in neighbors: if state[neighbor] == 0: empty_neighbors.add(neighbor) return empty_neighbors else: raise ValueError(\u0026#34;Invalid coordinates.\u0026#34;) 1 2 3 # test coords = {(1,6), (1,7)} print(find_frontiers(coords, state)) {(2, 6), (1, 5)} Breadth-first search When we say cluster, we means one maximal path-connected component. That is, a cluster is\nWhose size cannot be further increased: if you try to find frontier of a cluster, than you can find nothing. Whose stones are closely connected: that is, from any stone A and stone B inside a cluster, there is always a path connecting A to B. This gives us an idea to find a cluster given a starting point. You try to find frontiers of the starting point, visit it, then set the visited point as the new starting point, keeping doing this until you have no where to go: that is, no unvisited frontiers for you to go. This is the time to exit the process.\nWhat described is in fact, the Breadth-first search. Let\u0026rsquo;s do this.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def find_cluster(coord, state): if not is_valid_coordinate(coord): raise ValueError(\u0026#34;Invalid coordinate.\u0026#34;) if state[coord] == 0: raise ValueError(\u0026#34;the starting coordinate has no stone.\u0026#34;) visited = set() visitable = find_frontiers({coord}, state) while len(visitable) \u0026gt; 0: current = visitable visited.update(current) frontiers = find_frontiers(current, state) visitable = frontiers - visited # lastly, the cluster should include the starting points visited.update({coord}) return visited 1 2 3 4 5 cluster = find_cluster((1,7), state) print(cluster) cluster = find_cluster((1,6), state) print(cluster) {(4, 4), (2, 4), (3, 4), (1, 5), (2, 3), (1, 7), (2, 6), (1, 6), (2, 5), (3, 5)} {(4, 4), (2, 4), (3, 4), (1, 5), (2, 3), (1, 7), (2, 6), (1, 6), (2, 5), (3, 5)} So what\u0026rsquo;s the difference from empty-neighbor to liberty? Well, when the set we input is a cluster, that is, a connected component of stones, the empty-neighbor is the liberty of the cluster.\nIf we start from any set of stone of the same color, to get its liberties,\nfind_cluster; find_empty_neighbors; count the number of empty neighbors. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def find_liberties(coord, state): if not is_valid_coordinate(coord): raise ValueError(\u0026#34;Invalid coordinate.\u0026#34;) if state[coord] == 0: raise ValueError(\u0026#34;the starting coordinate has no stone.\u0026#34;) color = state[coord] # 1. find the cluster cluster = find_cluster(coord, state) # 2. find the empty neighbors of the cluster emtpy_neighbors = find_empty_neighbors(cluster, state) # 3. count the number of empty neighbors return len(emtpy_neighbors) 1 2 3 4 5 6 print(find_liberties((1,7), state)) print(find_liberties((0,8), state)) print(find_liberties((7,7), state)) print(find_liberties((1,1), state)) print(find_liberties((3,3), state)) print(find_liberties((8,8), state)) 10 2 4 6 11 2 ","permalink":"https://tchaiku.github.io/post/go-python-1/","summary":"Yet another practice in Python.","title":"Go in Python I: Liberties"},{"content":"This is a note based on a talk1 by Oded Schramm in ICM(International Congress of Mathematicians) 2006, which features a quite intuitive approach to the idea of scaling limit appearing a lot in today\u0026rsquo;s math/physics literature. The illustration features mainly the case of triangular lattice. In this specific situation, thanks to the theorem of Smirnov2, we are able to characterize the behavior of the limit very well.\nThis particular discovery is one of the motivations at very beginning of the idea Schramm–Loewner evolution.\nThe note attached here is nothing new but all from the reference. As I made some effort on drawing the pictures step by step, I hope this will make you get some good ideas behind it.\nSchramm: Conformally invariant scaling limits (an overview and a collection of problems), arXiv:math/0602151\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nS. Smirnov: Critical percolation in the plane, arXiv:0909.4499\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://tchaiku.github.io/math/scaling-limit-on-triangular-lattice/","summary":"an easy way to explain on scaling limits.","title":"From percolation on triangular lattice to SLE"},{"content":" Note\nThis article below is only for showcasing the features of the thm-environments in progress over hugo-notice. The math presented here is segmented from several math papers and as a whole it should make no sense from the end of current sentence on.\nIntroduction With these definitions in place we can state the main definition of this section.\nDefinition\nA polarised variety $(X,L)$ is K-semistable if for any test configuration ${\\cal X}$ we have ${\\rm Fut}({\\cal X})\\geq 0$. It is $K$-stable if equality holds only when ${\\cal X}$ is a product $X\\times {\\bf C}$.\nNote that in the last clause we allow the ${\\bf C}^{*}$-action on $X\\times {\\bf C}$ to be induced from a non-trivial action1 on $X$. What we have called K-stability is often called K-polystability in the literature. The precise statement of the result mentioned in the previous section, verifying Yau’s conjecture, is\nTheorem\nA Fano manifold $X$ admits a Kahler-Einstein metric if and only if $(X,K_{X}^{-1})$ is K-stable.\nHere the \u0026ldquo;only if\u0026rdquo; is usually regarded as the easier direction and is due to Berman, following related results of various authors The uniqueness of the metric, modulo holomorphic automorphisms, is a relatively old result of Bando and Mabuchi. We will not say more about these results here but focus on the ‘‘if” direction.\nBackground to the technical aspects To give some background to the technical aspects of the proofs sketched in Section 4 below we will now try to explain why Theorem 1 is plausible.\nProposition\nConsider the map $\\mathbf{G}_z:\\mathbf{Q}_* \\ni [(V,E,L,W, \\beta, U,\\mathbf{x_0}) ] \\mapsto G_z(\\mathbf{x_0},\\mathbf{x_0})\\in \\mathbb C$. Let $M\u003em\u003e0$, $D\\in \\mathbb N$. For all $z\\in \\mathbb C\\setminus \\mathbb R$, $\\mathbf{G}_z$ is continuous on $\\mathbf{Q}_*^{D,m,M}$.\nFirst we go back to the definition of the Futaki invariant of $(Z,\\Lambda)$ in the case when $Z$ is a manifold, which was in fact the original context for Futaki’s definition. Choose a Kähler metric $\\omega$ on $Z$ in the class $c_{1}(\\Lambda)$ preserved by the action of $S^{1}\\subset {\\bf C}^{*}$.\nLemma\nSuppose $f: R → R$ is twice continuously diﬀerentiable and $\\mathrm dX = a_t \\mathrm dt + b_t \\mathrm dW$. Then $f (X)$ is the Ito process, moreover, we have $$ \\begin{aligned} f (X_t) = \u0026 f (X_0) + \\int_0^t f'(X_s) a_s \\mathrm d s \\\\ + \u0026 \\int_0^t f'(Xs) b_s \\mathrm dW + \\frac 1 2 \\int_0^t f^{''}(X_s) b_s^2 \\mathrm d s,\\\\ \\end{aligned} $$ for $t ≥ 0$.\nViewing $\\omega$ as a symplectic structure, this action is generated by a Hamiltonian function $H$ on $Z$. Then the Futaki invariant can be given by a differential geometric formula $$\\int_{Z} (R-\\hat{R}) H \\frac{\\omega^{n}}{n!},$$ where $R$ is the scalar curvature of $\\omega$ and $\\hat{R}$ is the average value of $R$ over $Z$.\nRemark\nFor chaotic billiards, Ingremeau recently and independently formulated a conjecture of the same nature. Using results of Bourgain, Buckley and Wigman he also proved that certain deterministic families of eigenfunctions on the $2$-torus satisfy the conclusion of Berry\u0026rsquo;s conjecture. Note that in this case, the curvature is 0 and no chaotic dynamics are present.\nThe Futaki invariant This formula can be derived from the equivariant Riemann-Roch theorem and can also be understood in terms of the asymptotic geometry of sections of $L^{k}$ as $k\\rightarrow \\infty$, in the vein of quasi-classical asymptotics in quantisation theory. What this formula shows immediately is that if $\\omega$ can be chosen to have constant scalar curvature—in particular if it is a Kähler-Einstein metric—then the Futaki invariant vanishes2. This given another way, different from the Matshusima theorem, of ruling out Kähler-Einstein metrics on 1 or 2 point blow-ups of ${\\bf C}{\\bf P}^{2}$. The definition of K-stability employs the Futaki invariant in a more subtle way; it is not just the automorphisms of $X$ which need to be considered but of the degenerations. The Mabuchi functional gives a way to understand this phenomenon.\nCorollary\nLet $D\\in\\mathbb N$, $M\u003em\u003e0$, and let $\\mathcal Q _N=(V_N,E_N,L_N,W_N, \\beta_N, U_N)$ be a sequence of quantum graphs satisfying cool equation for all $N\\in \\mathbb N$. Then there is a subsequence $\\mathcal Q_{N_k}$ which converges in the sense of Benjamini-Schramm (i.e. there exists $\\mathbb{P}\\in \\mathcal{P}(\\mathbf{Q}_*^{D,m,M})$ such that $\\nu_{{Q}_{N_k}}\\xrightarrow{w^*} \\mathbb{P}$).\nThis is a functional ${\\cal F}$ on the space ${\\cal H}$ of Kähler metrics in a given cohomology class on a manifold $X$ defined via its first variation $$\\delta {\\cal F} = \\int_{X} (R-\\hat{R}) \\delta \\phi \\frac{\\omega_{\\phi}^{n}}{n!}.$$ Here $\\delta \\phi$ is an infinitesimal variation in the Kähler potential and one shows that such a functional ${\\cal F}$ is well-defined, up to the addition of an arbitrary constant.\nConjecture\nThe rank of an elliptic curve is equal to the order to which the associated $L$-function $L(s)$ vanishes at $s = 1$. In particular, the rank is $0$ (so there are only ﬁnitely many rational solutions) if the graph of $L(s)$ does not meet the $s$-axis at $s = 1$ ; the rank is $1$ if the graph meets the $s$-axis at $s = 1$ with non-zero slope; and the rank is $≥ 2$ if the graph is tangent to the $s$-axis at $s = 1$.\nThe three situations are illustrated for the $L$-functions of the Sylvester curves $x^3 + y^3 = p$ for $p = 5, 13$ and $19$ $(r = 0, 1 \\text{ and } 2)$.\nThe conjecture of Birch and Swinnerton-Dyer is still open, and the million dollars have not been claimed. All that one knows in general is that $r = 0$ if $L(1) \\not = 0$ (as explained in the last section) and that $r = 1$ if $L(s)$ vanishes simply at $s = 1$ (Gross-Zagier (1983) and Kolyvagin (1988)). The solution of the problem, one of the deepest and most beautiful in all of number theory, will constitute a huge step forward in our understanding of the mysteries of numbers.\nBy construction a critical point of ${\\cal F}$ is exactly a constant scalar curvature metrics which, in the setting of Theorem 1 can be shown to be Kähler-Einstein. (We mention here that there is another functional, the Ding functional which has many similar properties to the Mabuchi functional and plays an important part in many developments. This is only defined for manifolds polarised by $K^{\\pm 1}$.)\nThere are three possibilities:\n${\\cal F}$ is bounded below on ${\\cal H}$ and attains its infimum;\n${\\cal F}$ is bounded below but does not attain its infimum;\n${\\cal F}$ is not bounded below.\nAn extension of Theorem 1 is the statement that these three possibilities correspond to $X$ being respectively $K$-stable, $K$-semistable (but not $K$-stable) and not $K$-semistable.\nM Anderson, Convergence and rigidity of manifolds under Ricci curvature bounds Invent. Math. 102 (1990) 429-445.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nS. Bando and T. Mabuchi, Uniqueness of Einstein Kahler metrics modulo connected group actions, Adv. Stud. Pure Math. 10 North Holland 1987 11-40.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://tchaiku.github.io/demo/theorem-in-hugo/","summary":"On the Doe conjecture relating the existence of Alpha-beta metrics on Nuno manifolds to L-stability.","title":"Theorem-like blocks for academic purpose in Hugo"},{"content":"Sketch of the ideas: Consider each sampling of the balls as a 1-dimensional random walk, which takes only two possible directions: left or right, at each step. The directional path (i.e., we don\u0026rsquo;t consider its length but only direction) of the ball is then equivalent to a sequence of L/R (or -1/1, or 0/1, whatever you like) of length $n$, where $n$ is the number of rows of the Galton board (the size of the map).\nTo implement a simulation where the principles are so described, we use the object-oriented programming (OOP) in Python. We implement each trial as a class, where the attributes of it consisting of\nthe size of the board (number of rows) the number of balls and some other parameters that we may need. Then for each sampling of the balls:\nthe method sample() will be called, which return a list of dicts as the result of the sampling, where the keys are: the path the final position of the ball, which is the sum of the path not only the return value, but also result will be appended and stored as attributes trial.sample_results of the trial object. Use a for loop to simulate every ball\u0026rsquo;s movement one by one, so that it\u0026rsquo;s then statistically `independent\u0026rsquo; from each other.\nGather the results of those simulations and store them as attributes of the trial object.\nIt\u0026rsquo;s then another new task to analyze the results, for example, to verify the central limit theorem, or to calculate the probability of the balls falling into each bin, etc.\nRemark The so called \u0026lsquo;sequence\u0026rsquo; in 1., is a mathematical concept, and in Python we shall use a list to do it. If we denote the step at each of the two directions as $-1/2$ and $1/2$, the the sum of this sequence will give us the final position of the ball, assuming the starting point is $0$. I am still thinking if we need any advanced libraries to simulate this random walk process. But I guess it may not be so, because the overall math involved here is very simple. Maybe only random would be sufficient. Important attributes that must be included in 2. : the size of the board, and the number of the samplings. Because those two, when going to infinite, would give a mathematical approximation of the normal distribution. There is one important difference between the two parameters. While the first one, the size, is fixed, that is, you cannot change it after starting the trial, the second one is not, because the independent property of the distribution. Which means, from a point of view of coding, we can have a slider to interactively change the second one and see the result, but the we cannot do so for the first one. Now let\u0026rsquo;s start coding.\nThe code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import random class trial(): # attributes: 1. sample_results 2. size def __init__(self, sample_results=[], size=10): self.sample_results = sample_results self.size = size def single_sample(self): # for each step, randomly choose a number from -1 and 1. # if the number is -1, the step goes to the left. # if the number is 1, the step goes to the right. directional_path = [] for i in range(self.size): directional_path.append(random.choice([-1,1])) # the actual path is the same as the directional path, but each step is timed by 1/2. path = [step * 0.5 for step in directional_path] # the destination is the sum of the path. destination = sum(path) result = {\u0026#39;path\u0026#39;: path, \u0026#39;destination\u0026#39;: destination} self.sample_results.append(result) return result def sample(self, n): for i in range(n): self.single_sample() return self.sample_results 1 2 3 4 5 6 7 8 9 10 11 12 13 # create a trial object t = trial(size=30) t.sample(10000) # print the first 10 results and the last 10 results. for i in range(10): print(\u0026#39;Ball \u0026#39; + str(i) + \u0026#39; path: \u0026#39; + str(t.sample_results[i][\u0026#39;path\u0026#39;]) + \u0026#39; destination: \u0026#39; + str(t.sample_results[i][\u0026#39;destination\u0026#39;])) print(\u0026#39;\\n\u0026#39;) for i in range(9990, 10000): print(\u0026#39;Ball \u0026#39; + str(i) + \u0026#39; path: \u0026#39; + str(t.sample_results[i][\u0026#39;path\u0026#39;]) + \u0026#39; destination: \u0026#39; + str(t.sample_results[i][\u0026#39;destination\u0026#39;])) print(\u0026#39;\\n\u0026#39;) Ball 0 path: [0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5] destination: 1.0 Ball 1 path: [-0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5] destination: 0.0 Ball 2 path: [0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5] destination: -1.0 Ball 3 path: [0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5] destination: 3.0 Ball 4 path: [0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5] destination: 0.0 Ball 5 path: [0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5] destination: 0.0 Ball 6 path: [-0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5] destination: -2.0 Ball 7 path: [-0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5] destination: 2.0 Ball 8 path: [-0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5] destination: -2.0 Ball 9 path: [0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5] destination: 4.0 Ball 9990 path: [-0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5] destination: 1.0 Ball 9991 path: [-0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5] destination: -2.0 Ball 9992 path: [-0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5] destination: -2.0 Ball 9993 path: [0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5] destination: 6.0 Ball 9994 path: [0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5] destination: -3.0 Ball 9995 path: [0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5] destination: 1.0 Ball 9996 path: [-0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5] destination: 2.0 Ball 9997 path: [0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5] destination: 2.0 Ball 9998 path: [0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5] destination: 2.0 Ball 9999 path: [0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5] destination: 2.0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # count the number of balls that end up at some specific destination def count_balls(destination): count = 0 for i in range(len(t.sample_results)): if t.sample_results[i][\u0026#39;destination\u0026#39;] == destination: count += 1 return count destination = 11.0 count = count_balls(destination) print(\u0026#39;The number of balls that end up at the destination \u0026#39; + str(destination) + \u0026#39; is: \u0026#39; + str(count)) destination = -12.0 count = count_balls(destination) print(\u0026#39;The number of balls that end up at the destination \u0026#39; + str(destination) + \u0026#39; is: \u0026#39; + str(count)) The number of balls that end up at the destination 11.0 is: 0 The number of balls that end up at the destination -12.0 is: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import numpy as np import matplotlib.pyplot as plt # use stem to plot the distribution of the destination of the balls: # x-axis: destination # y-axis: frequency destination_range = range(-16,17) # for each destination, calculate the frequency frequency = [] for d in destination_range: frequency.append(count_balls(d)) plt.stem(destination_range, frequency) plt.xlabel(\u0026#39;Destination\u0026#39;) plt.ylabel(\u0026#39;Frequency\u0026#39;) plt.title(\u0026#39;Distribution of the destination of the balls\u0026#39;) # Set the x-axis ticks to show every integer plt.xticks(np.arange(min(destination_range), max(destination_range)+1, 1)) plt.yticks(np.arange(0, max(frequency)+1, 50)) plt.show() ","permalink":"https://tchaiku.github.io/post/galton_board/","summary":"A simple simulation of the Galton board in Python, using the object-oriented programming, verifying the central limit theorem.","title":"Galton board simulation in Python"}]